@configdirector/client-sdk 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"configdirector-client-testing.mjs","names":["encodeTestValue","encodeShared","createTestClient"],"sources":["../../shared/src/testing/values.ts","../../js-client-core/src/testing/values.ts","../../js-client-core/src/testing/InMemoryConnection.ts","../../js-client-core/src/testing/TestClient.ts","../src/testing.ts"],"sourcesContent":["import type { ConfigType } from \"../types\";\nimport { ConfigDirectorValidationError } from \"../errors\";\n\nexport type TestJsonValue = boolean | number | string | null | TestJsonValue[] | { [key: string]: TestJsonValue };\n\nexport type TestValue = boolean | number | string | TestJsonValue[] | { [key: string]: TestJsonValue };\n\nexport type TestValues = Record<string, TestValue>;\n\nexport type EncodedTestValue = { type: ConfigType; value: string };\n\nexport const encodeTestValue = (key: string, value: TestValue): EncodedTestValue => {\n if (key.trim().length == 0) {\n throw new ConfigDirectorValidationError(\"Invalid config key. The key of a test value cannot be empty.\");\n }\n return encode(key, value);\n};\n\nconst encode = (key: string, value: TestValue): EncodedTestValue => {\n switch (typeof value) {\n case \"boolean\":\n return { type: \"boolean\", value: value ? \"true\" : \"false\" };\n case \"number\":\n return encodeNumber(key, value);\n case \"string\":\n return { type: \"string\", value };\n case \"object\":\n if (value !== null) {\n assertJsonContents(key, value, \"\");\n return { type: \"json\", value: JSON.stringify(value) };\n }\n }\n throw new ConfigDirectorValidationError(\n `Invalid test value for '${key}': ${describe(value)}. Use a boolean, a number, a string, a plain object, or an array.`,\n );\n};\n\nconst encodeNumber = (key: string, value: number): EncodedTestValue => {\n if (!Number.isFinite(value)) {\n throw new ConfigDirectorValidationError(`Invalid test value for '${key}': ${describe(value)} is not a finite number.`);\n }\n if (Number.isInteger(value)) {\n if (!Number.isSafeInteger(value)) {\n throw new ConfigDirectorValidationError(\n `Invalid test value for '${key}': ${value} is not a safe integer, so the SDK could not read it back exactly.`,\n );\n }\n return { type: \"integer\", value: String(value) };\n }\n return { type: \"float\", value: toPlainDecimal(value) };\n};\n\nconst toPlainDecimal = (value: number): string => {\n const text = String(value);\n const exponentIndex = text.indexOf(\"e-\");\n if (exponentIndex < 0) {\n return text;\n }\n const sign = text.startsWith(\"-\") ? \"-\" : \"\";\n const [integerDigits = \"\", fractionDigits = \"\"] = text.slice(sign.length, exponentIndex).split(\".\");\n const leadingZeros = Number(text.slice(exponentIndex + 2)) - integerDigits.length;\n return `${sign}0.${\"0\".repeat(leadingZeros)}${integerDigits}${fractionDigits}`;\n};\n\nconst assertJsonContents = (key: string, value: unknown, path: string): void => {\n if (value === null || typeof value === \"boolean\" || typeof value === \"string\") {\n return;\n }\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) {\n throw invalidJsonContents(key, path, `${describe(value)} is not a finite number`);\n }\n return;\n }\n if (Array.isArray(value)) {\n value.forEach((item, index) => assertJsonContents(key, item, `${path}[${index}]`));\n return;\n }\n if (isPlainObject(value)) {\n for (const [property, item] of Object.entries(value)) {\n assertJsonContents(key, item, path ? `${path}.${property}` : property);\n }\n return;\n }\n throw invalidJsonContents(key, path, `${describe(value)} cannot be encoded as JSON`);\n};\n\nconst invalidJsonContents = (key: string, path: string, problem: string) =>\n new ConfigDirectorValidationError(\n `Invalid test value for '${key}'${path ? ` at '${path}'` : \"\"}: ${problem}. JSON contents can hold booleans, finite numbers, strings, null, arrays, and plain objects.`,\n );\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n};\n\nconst describe = (value: unknown): string => {\n if (value === null) {\n return \"null\";\n }\n if (typeof value === \"function\") {\n return \"a function\";\n }\n if (typeof value === \"object\") {\n return `a ${value.constructor?.name ?? \"object\"} instance`;\n }\n if (typeof value === \"symbol\") {\n return \"a symbol\";\n }\n return String(value);\n};\n","import type { ConfigState } from \"../types\";\nimport { encodeTestValue as encodeShared } from \"@shared/testing/values\";\nimport type { TestValue } from \"@shared/testing/values\";\n\nexport type { TestJsonValue, TestValue, TestValues } from \"@shared/testing/values\";\n\nexport const encodeTestValue = (key: string, value: TestValue, revision: number): ConfigState => {\n const encoded = encodeShared(key, value);\n return {\n id: `test-config:${key}`,\n key,\n type: encoded.type,\n value: encoded.value,\n valueId: `test-value:${key}:${revision}`,\n };\n};\n","import type {\n ClientConnectAction,\n ConfigDirectorContext,\n ConfigDirectorLogger,\n ConfigSet,\n ConfigState,\n Transport,\n TransportEvents,\n} from \"../types\";\nimport { Emitter } from \"../Emitter\";\nimport { ConfigDirectorConnectionError } from \"../errors\";\nimport { encodeTestValue, type TestValue, type TestValues } from \"./values\";\n\ntype ControlledAttempt = \"initialize\" | \"updateContext\";\n\ntype Armed = \"nothing\" | \"hold\" | \"failure\";\n\ntype PendingHold = {\n attempt: ControlledAttempt;\n end: () => void;\n complete: () => void;\n fail: () => void;\n};\n\nconst TEST_ENVIRONMENT = { environmentId: \"test-environment\", projectId: \"test-project\" };\n\nconst controlledAttemptFor = (reason: ClientConnectAction): ControlledAttempt | undefined => {\n switch (reason) {\n case \"initialization\":\n return \"initialize\";\n case \"context update\":\n return \"updateContext\";\n default:\n return undefined;\n }\n};\n\nexport class InMemoryConnection implements Transport {\n private readonly emitter = new Emitter<TransportEvents>();\n private readonly configs = new Map<string, ConfigState>();\n private readonly recordedContexts: ConfigDirectorContext[] = [];\n private readonly pendingDeliveries: ConfigSet[] = [];\n private readonly armed: Record<ControlledAttempt, Armed> = { initialize: \"nothing\", updateContext: \"nothing\" };\n private pendingHold: PendingHold | undefined;\n private connected = false;\n private delivering = false;\n private revision = 0;\n private attemptGeneration = 0;\n\n constructor(\n private readonly logger: ConfigDirectorLogger,\n values: TestValues,\n ) {\n this.store(values);\n }\n\n public get contextUpdates(): ConfigDirectorContext[] {\n return [...this.recordedContexts];\n }\n\n public setValue(key: string, value: TestValue) {\n const configState = encodeTestValue(key, value, ++this.revision);\n this.configs.set(key, configState);\n this.deliverIfConnected({ ...TEST_ENVIRONMENT, kind: \"delta\", configs: { [key]: configState } });\n }\n\n public removeValue(key: string) {\n this.configs.delete(key);\n this.deliverIfConnected(this.fullUpdate());\n }\n\n public replaceValues(values: TestValues) {\n const encoded = this.encodeAll(values);\n this.configs.clear();\n encoded.forEach((configState, key) => this.configs.set(key, configState));\n this.armed.initialize = \"nothing\";\n this.armed.updateContext = \"nothing\";\n this.deliverIfConnected(this.fullUpdate());\n }\n\n public hold(attempt: ControlledAttempt) {\n this.armed[attempt] = \"hold\";\n }\n\n public complete(attempt: ControlledAttempt) {\n if (this.pendingHold?.attempt === attempt) {\n this.pendingHold.complete();\n } else if (this.armed[attempt] === \"hold\") {\n this.armed[attempt] = \"nothing\";\n }\n }\n\n public fail(attempt: ControlledAttempt) {\n if (this.pendingHold?.attempt === attempt) {\n this.pendingHold.fail();\n } else {\n this.armed[attempt] = \"failure\";\n }\n }\n\n public connect(context: ConfigDirectorContext, timeout: number, reason: ClientConnectAction): Promise<this> {\n this.endAttempt();\n const attempt = controlledAttemptFor(reason);\n if (!attempt) {\n return this.connectNow();\n }\n this.recordedContexts.push(context);\n const armed = this.armed[attempt];\n this.armed[attempt] = \"nothing\";\n switch (armed) {\n case \"failure\":\n return this.failNow(reason);\n case \"hold\":\n return this.holdNow(attempt, reason, timeout);\n default:\n return this.connectNow();\n }\n }\n\n public on: Transport[\"on\"] = (name, handler) => this.emitter.on(name, handler);\n\n public off: Transport[\"off\"] = (name, handler) => this.emitter.off(name, handler);\n\n public clear() {\n this.emitter.clear();\n }\n\n public close() {\n this.endAttempt();\n }\n\n public dispose() {\n this.close();\n this.clear();\n }\n\n private endAttempt() {\n this.attemptGeneration++;\n this.connected = false;\n this.pendingHold?.end();\n }\n\n private connectNow(): Promise<this> {\n const connected = Promise.resolve(this);\n this.scheduleFirstDelivery();\n return connected;\n }\n\n private holdNow(attempt: ControlledAttempt, reason: ClientConnectAction, timeout: number): Promise<this> {\n return new Promise<this>((resolve, reject) => {\n const timer = setTimeout(() => this.pendingHold?.end(), timeout);\n const release = () => {\n clearTimeout(timer);\n this.pendingHold = undefined;\n };\n this.pendingHold = {\n attempt,\n end: () => {\n release();\n resolve(this);\n },\n complete: () => {\n release();\n resolve(this);\n this.scheduleFirstDelivery();\n },\n fail: () => {\n release();\n const error = this.fatalError(reason);\n this.emitter.emit(\"connectionError\", error);\n reject(error);\n },\n };\n });\n }\n\n private failNow(reason: ClientConnectAction): Promise<this> {\n const error = this.fatalError(reason);\n this.emitter.emit(\"connectionError\", error);\n return Promise.reject(error);\n }\n\n private fatalError(reason: ClientConnectAction) {\n return new ConfigDirectorConnectionError(\n `Connection failed with status: 401. Error: the test client failed this ${reason}. This is an unrecoverable error, will not attempt to reconnect.`,\n 401,\n );\n }\n\n private scheduleFirstDelivery() {\n const generation = this.attemptGeneration;\n void Promise.resolve()\n .then(() => {})\n .then(() => {\n if (generation !== this.attemptGeneration) {\n return;\n }\n this.connected = true;\n this.deliver(this.fullUpdate());\n });\n }\n\n private deliverIfConnected(configSet: ConfigSet) {\n if (this.connected) {\n this.deliver(configSet);\n }\n }\n\n private deliver(configSet: ConfigSet) {\n this.pendingDeliveries.push(configSet);\n if (this.delivering) {\n return;\n }\n this.delivering = true;\n try {\n let next = this.pendingDeliveries.shift();\n while (next) {\n this.emitCaught(next);\n next = this.pendingDeliveries.shift();\n }\n } finally {\n this.delivering = false;\n }\n }\n\n private emitCaught(configSet: ConfigSet) {\n try {\n this.emitter.emit(\"configSetReceived\", configSet);\n } catch (error) {\n this.logger.error(\"[InMemoryConnection] Error dispatching a config update from the test client: \", error);\n }\n }\n\n private fullUpdate(): ConfigSet {\n return { ...TEST_ENVIRONMENT, kind: \"full\", configs: Object.fromEntries(this.configs) };\n }\n\n private store(values: TestValues) {\n this.encodeAll(values).forEach((configState, key) => this.configs.set(key, configState));\n }\n\n private encodeAll(values: TestValues): Map<string, ConfigState> {\n const encoded = new Map<string, ConfigState>();\n for (const [key, value] of Object.entries(values)) {\n encoded.set(key, encodeTestValue(key, value, ++this.revision));\n }\n return encoded;\n }\n}\n","import { DefaultConfigDirectorClient } from \"../DefaultConfigDirectorClient\";\nimport { createDefaultLogger } from \"../logger\";\nimport type { ConfigDirectorClient, ConfigDirectorContext, ConfigDirectorLogger, IdentifyingSdkOptions } from \"../types\";\nimport type { TelemetryClient } from \"../telemetry\";\nimport { InMemoryConnection } from \"./InMemoryConnection\";\nimport type { TestValue, TestValues } from \"./values\";\n\nconst TEST_CLIENT_SDK_KEY = \"test-client\";\n\n/**\n * Options for {@link createTestClient}. Every option is optional.\n */\nexport type TestClientOptions = {\n /**\n * The config values the test client serves once its client connects, keyed by config key. The\n * config type follows from each value: a boolean, an integral number (`integer`), any other\n * number (`float`), a string, or a plain object or array (`json`).\n */\n values?: TestValues;\n /**\n * The connection timeout of the SDK client, in milliseconds. It bounds how long a held\n * `initialize` or `updateContext` waits. Defaults to the SDK's production timeout.\n */\n timeout?: number;\n /**\n * The logger the SDK client uses. Defaults to the SDK's console logger at `warn` level.\n */\n logger?: ConfigDirectorLogger;\n};\n\n/**\n * A real ConfigDirector client connected to an in-memory server that the test controls.\n *\n * The client under {@link TestClient.client} is the SDK's production client: value parsing, watchers,\n * events, and readiness behave as they do against ConfigDirector. Only the network connection and\n * telemetry are replaced, so no request is ever made and nothing is left running after the client is\n * disposed.\n */\nexport interface TestClient {\n /**\n * The SDK client to hand to the code under test. It starts uninitialized, like a production\n * client; `initialize` completes at once with the seeded values unless initialization is held.\n */\n readonly client: ConfigDirectorClient;\n\n /**\n * Stores `value` under `key` and, when the client is connected, delivers it as an update, so\n * reads, watchers, and `configsUpdated` handlers see it.\n */\n setValue(key: string, value: TestValue): void;\n\n /**\n * Removes `key` and, when the client is connected, delivers a full update without it, so reads\n * fall back to the in-code default value and watchers of `key` receive that default.\n */\n removeValue(key: string): void;\n\n /**\n * Replaces every stored value with `values`, disarms any armed hold or failure, and, when the\n * client is connected, delivers the new values as a full update.\n */\n replaceValues(values: TestValues): void;\n\n /**\n * Makes the next `initialize` wait until {@link completeInitialization} or\n * {@link failInitialization} is called, or until the client's timeout elapses.\n */\n holdInitialization(): void;\n\n /**\n * Delivers the stored values to a held `initialize`, so it completes with the client ready. When\n * a hold is armed but no `initialize` has started, it disarms the hold instead.\n */\n completeInitialization(): void;\n\n /**\n * Fails a held `initialize` with an unrecoverable connection error, or arms the next `initialize`\n * to fail. `initialize` completes promptly with the client not ready and `connectionError` fires.\n */\n failInitialization(): void;\n\n /**\n * Makes the next `updateContext` wait until {@link completeContextUpdate} or\n * {@link failContextUpdate} is called, or until the client's timeout elapses.\n */\n holdContextUpdate(): void;\n\n /**\n * Delivers the stored values to a held `updateContext`, or disarms an armed hold that no\n * `updateContext` has picked up.\n */\n completeContextUpdate(): void;\n\n /**\n * Fails a held `updateContext` with an unrecoverable connection error, or arms the next\n * `updateContext` to fail.\n */\n failContextUpdate(): void;\n\n /**\n * The context of every `initialize` and `updateContext` call, in call order. `initialize` without\n * a context records an empty context. Resumes are not recorded.\n */\n readonly contextUpdates: ConfigDirectorContext[];\n}\n\nconst noopTelemetryClient: TelemetryClient = {\n updateContext: async () => {},\n evaluatedConfig: () => {},\n close: async () => {},\n};\n\nclass InMemoryTestClient implements TestClient {\n constructor(\n public readonly client: ConfigDirectorClient,\n private readonly connection: InMemoryConnection,\n ) {}\n\n public setValue(key: string, value: TestValue) {\n this.connection.setValue(key, value);\n }\n\n public removeValue(key: string) {\n this.connection.removeValue(key);\n }\n\n public replaceValues(values: TestValues) {\n this.connection.replaceValues(values);\n }\n\n public holdInitialization() {\n this.connection.hold(\"initialize\");\n }\n\n public completeInitialization() {\n this.connection.complete(\"initialize\");\n }\n\n public failInitialization() {\n this.connection.fail(\"initialize\");\n }\n\n public holdContextUpdate() {\n this.connection.hold(\"updateContext\");\n }\n\n public completeContextUpdate() {\n this.connection.complete(\"updateContext\");\n }\n\n public failContextUpdate() {\n this.connection.fail(\"updateContext\");\n }\n\n public get contextUpdates(): ConfigDirectorContext[] {\n return this.connection.contextUpdates;\n }\n}\n\nexport const createTestClient = (sdkOptions: IdentifyingSdkOptions, options?: TestClientOptions): TestClient => {\n const logger = options?.logger ?? createDefaultLogger();\n const connection = new InMemoryConnection(logger, options?.values ?? {});\n const client = new DefaultConfigDirectorClient(\n noopTelemetryClient,\n TEST_CLIENT_SDK_KEY,\n sdkOptions,\n { logger, connection: { timeout: options?.timeout } },\n { transport: connection },\n );\n return new InMemoryTestClient(client, connection);\n};\n","import { createTestClient as createCoreTestClient } from \"@js-client-core/testing/index\";\nimport type { TestClient, TestClientOptions } from \"@js-client-core/testing/index\";\n\nexport type { TestClient, TestClientOptions, TestValue, TestValues, TestJsonValue } from \"@js-client-core/testing/index\";\nexport { ConfigDirectorConnectionError, ConfigDirectorValidationError } from \"@shared/errors\";\n\n/**\n * Creates a {@link TestClient}: a real `ConfigDirectorClient` connected to an in-memory server\n * that the test controls. No network connection is opened and no telemetry is sent.\n *\n * @param options {@link TestClientOptions}: the initial values, the client's timeout, and its logger\n * @returns A {@link TestClient} whose `client` is passed to the code under test\n *\n * @example\n * import { createTestClient } from \"@configdirector/client-sdk/testing\";\n *\n * const testClient = createTestClient({ values: { \"new-checkout\": true } });\n * await testClient.client.initialize();\n * expect(testClient.client.getValue(\"new-checkout\", false)).toBe(true);\n *\n * testClient.setValue(\"new-checkout\", false);\n */\nexport const createTestClient = (options?: TestClientOptions): TestClient => {\n return createCoreTestClient({ sdkName: \"js-client-sdk\", sdkVersion: \"__VERSION__\" }, options);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"configdirector-client-testing.mjs","names":["encodeTestValue","encodeShared","createTestClient"],"sources":["../../shared/src/testing/values.ts","../../js-client-core/src/testing/values.ts","../../js-client-core/src/testing/InMemoryConnection.ts","../../js-client-core/src/testing/TestClient.ts","../src/testing.ts"],"sourcesContent":["import type { ConfigType } from \"../types\";\nimport { ConfigDirectorValidationError } from \"../errors\";\n\nexport type TestJsonValue = boolean | number | string | null | TestJsonValue[] | { [key: string]: TestJsonValue };\n\nexport type TestValue = boolean | number | string | TestJsonValue[] | { [key: string]: TestJsonValue };\n\nexport type TestValues = Record<string, TestValue>;\n\nexport type EncodedTestValue = { type: ConfigType; value: string };\n\nexport const encodeTestValue = (key: string, value: TestValue): EncodedTestValue => {\n if (key.trim().length == 0) {\n throw new ConfigDirectorValidationError(\"Invalid config key. The key of a test value cannot be empty.\");\n }\n return encode(key, value);\n};\n\nconst encode = (key: string, value: TestValue): EncodedTestValue => {\n switch (typeof value) {\n case \"boolean\":\n return { type: \"boolean\", value: value ? \"true\" : \"false\" };\n case \"number\":\n return encodeNumber(key, value);\n case \"string\":\n return { type: \"string\", value };\n case \"object\":\n if (value !== null) {\n assertJsonContents(key, value, \"\");\n return { type: \"json\", value: JSON.stringify(value) };\n }\n }\n throw new ConfigDirectorValidationError(\n `Invalid test value for '${key}': ${describe(value)}. Use a boolean, a number, a string, a plain object, or an array.`,\n );\n};\n\nconst encodeNumber = (key: string, value: number): EncodedTestValue => {\n if (!Number.isFinite(value)) {\n throw new ConfigDirectorValidationError(`Invalid test value for '${key}': ${describe(value)} is not a finite number.`);\n }\n if (Number.isInteger(value)) {\n if (!Number.isSafeInteger(value)) {\n throw new ConfigDirectorValidationError(\n `Invalid test value for '${key}': ${value} is not a safe integer, so the SDK could not read it back exactly.`,\n );\n }\n return { type: \"integer\", value: String(value) };\n }\n return { type: \"float\", value: toPlainDecimal(value) };\n};\n\nconst toPlainDecimal = (value: number): string => {\n const text = String(value);\n const exponentIndex = text.indexOf(\"e-\");\n if (exponentIndex < 0) {\n return text;\n }\n const sign = text.startsWith(\"-\") ? \"-\" : \"\";\n const [integerDigits = \"\", fractionDigits = \"\"] = text.slice(sign.length, exponentIndex).split(\".\");\n const leadingZeros = Number(text.slice(exponentIndex + 2)) - integerDigits.length;\n return `${sign}0.${\"0\".repeat(leadingZeros)}${integerDigits}${fractionDigits}`;\n};\n\nconst assertJsonContents = (key: string, value: unknown, path: string): void => {\n if (value === null || typeof value === \"boolean\" || typeof value === \"string\") {\n return;\n }\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) {\n throw invalidJsonContents(key, path, `${describe(value)} is not a finite number`);\n }\n return;\n }\n if (Array.isArray(value)) {\n value.forEach((item, index) => assertJsonContents(key, item, `${path}[${index}]`));\n return;\n }\n if (isPlainObject(value)) {\n for (const [property, item] of Object.entries(value)) {\n assertJsonContents(key, item, path ? `${path}.${property}` : property);\n }\n return;\n }\n throw invalidJsonContents(key, path, `${describe(value)} cannot be encoded as JSON`);\n};\n\nconst invalidJsonContents = (key: string, path: string, problem: string) =>\n new ConfigDirectorValidationError(\n `Invalid test value for '${key}'${path ? ` at '${path}'` : \"\"}: ${problem}. JSON contents can hold booleans, finite numbers, strings, null, arrays, and plain objects.`,\n );\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n};\n\nconst describe = (value: unknown): string => {\n if (value === null) {\n return \"null\";\n }\n if (typeof value === \"function\") {\n return \"a function\";\n }\n if (typeof value === \"object\") {\n return `a ${value.constructor?.name ?? \"object\"} instance`;\n }\n if (typeof value === \"symbol\") {\n return \"a symbol\";\n }\n return String(value);\n};\n","import type { ConfigState } from \"../types\";\nimport { encodeTestValue as encodeShared } from \"@shared/testing/values\";\nimport type { TestValue } from \"@shared/testing/values\";\n\nexport type { TestJsonValue, TestValue, TestValues } from \"@shared/testing/values\";\n\nexport const encodeTestValue = (key: string, value: TestValue, revision: number): ConfigState => {\n const encoded = encodeShared(key, value);\n return {\n id: `test-config:${key}`,\n key,\n type: encoded.type,\n value: encoded.value,\n valueId: `test-value:${key}:${revision}`,\n };\n};\n","import type {\n ClientConnectAction,\n ConfigDirectorContext,\n ConfigDirectorLogger,\n ConfigSet,\n ConfigState,\n Transport,\n TransportEvents,\n} from \"../types\";\nimport { Emitter } from \"../Emitter\";\nimport { ConfigDirectorConnectionError } from \"../errors\";\nimport { encodeTestValue, type TestValue, type TestValues } from \"./values\";\n\ntype ControlledAttempt = \"initialize\" | \"updateContext\";\n\ntype Armed = \"nothing\" | \"hold\" | \"failure\";\n\ntype PendingHold = {\n attempt: ControlledAttempt;\n end: () => void;\n complete: () => void;\n fail: () => void;\n};\n\nconst TEST_ENVIRONMENT = { environmentId: \"test-environment\", projectId: \"test-project\" };\n\nconst controlledAttemptFor = (reason: ClientConnectAction): ControlledAttempt | undefined => {\n switch (reason) {\n case \"initialization\":\n return \"initialize\";\n case \"context update\":\n return \"updateContext\";\n default:\n return undefined;\n }\n};\n\nexport class InMemoryConnection implements Transport {\n private readonly emitter = new Emitter<TransportEvents>();\n private readonly configs = new Map<string, ConfigState>();\n private readonly recordedContexts: ConfigDirectorContext[] = [];\n private readonly pendingDeliveries: ConfigSet[] = [];\n private readonly armed: Record<ControlledAttempt, Armed> = { initialize: \"nothing\", updateContext: \"nothing\" };\n private pendingHold: PendingHold | undefined;\n private connected = false;\n private delivering = false;\n private revision = 0;\n private attemptGeneration = 0;\n\n constructor(\n private readonly logger: ConfigDirectorLogger,\n values: TestValues,\n ) {\n this.store(values);\n }\n\n public get contextUpdates(): ConfigDirectorContext[] {\n return [...this.recordedContexts];\n }\n\n public setValue(key: string, value: TestValue) {\n const configState = encodeTestValue(key, value, ++this.revision);\n this.configs.set(key, configState);\n this.deliverIfConnected({ ...TEST_ENVIRONMENT, kind: \"delta\", configs: { [key]: configState } });\n }\n\n public removeValue(key: string) {\n this.configs.delete(key);\n this.deliverIfConnected(this.fullUpdate());\n }\n\n public replaceValues(values: TestValues) {\n const encoded = this.encodeAll(values);\n this.configs.clear();\n encoded.forEach((configState, key) => this.configs.set(key, configState));\n this.armed.initialize = \"nothing\";\n this.armed.updateContext = \"nothing\";\n this.deliverIfConnected(this.fullUpdate());\n }\n\n public hold(attempt: ControlledAttempt) {\n this.armed[attempt] = \"hold\";\n }\n\n public complete(attempt: ControlledAttempt) {\n if (this.pendingHold?.attempt === attempt) {\n this.pendingHold.complete();\n } else if (this.armed[attempt] === \"hold\") {\n this.armed[attempt] = \"nothing\";\n }\n }\n\n public fail(attempt: ControlledAttempt) {\n if (this.pendingHold?.attempt === attempt) {\n this.pendingHold.fail();\n } else {\n this.armed[attempt] = \"failure\";\n }\n }\n\n public connect(context: ConfigDirectorContext, timeout: number, reason: ClientConnectAction): Promise<this> {\n this.endAttempt();\n const attempt = controlledAttemptFor(reason);\n if (!attempt) {\n return this.connectNow();\n }\n this.recordedContexts.push(context);\n const armed = this.armed[attempt];\n this.armed[attempt] = \"nothing\";\n switch (armed) {\n case \"failure\":\n return this.failNow(reason);\n case \"hold\":\n return this.holdNow(attempt, reason, timeout);\n default:\n return this.connectNow();\n }\n }\n\n public on: Transport[\"on\"] = (name, handler) => this.emitter.on(name, handler);\n\n public off: Transport[\"off\"] = (name, handler) => this.emitter.off(name, handler);\n\n public clear() {\n this.emitter.clear();\n }\n\n public close() {\n this.endAttempt();\n }\n\n public dispose() {\n this.close();\n this.clear();\n }\n\n private endAttempt() {\n this.attemptGeneration++;\n this.connected = false;\n this.pendingHold?.end();\n }\n\n private connectNow(): Promise<this> {\n const connected = Promise.resolve(this);\n this.scheduleFirstDelivery();\n return connected;\n }\n\n private holdNow(attempt: ControlledAttempt, reason: ClientConnectAction, timeout: number): Promise<this> {\n return new Promise<this>((resolve, reject) => {\n const timer = setTimeout(() => this.pendingHold?.end(), timeout);\n const release = () => {\n clearTimeout(timer);\n this.pendingHold = undefined;\n };\n this.pendingHold = {\n attempt,\n end: () => {\n release();\n resolve(this);\n },\n complete: () => {\n release();\n resolve(this);\n this.scheduleFirstDelivery();\n },\n fail: () => {\n release();\n const error = this.fatalError(reason);\n this.emitter.emit(\"connectionError\", error);\n reject(error);\n },\n };\n });\n }\n\n private failNow(reason: ClientConnectAction): Promise<this> {\n const error = this.fatalError(reason);\n this.emitter.emit(\"connectionError\", error);\n return Promise.reject(error);\n }\n\n private fatalError(reason: ClientConnectAction) {\n return new ConfigDirectorConnectionError(\n `Connection failed with status: 401. Error: the test client failed this ${reason}. This is an unrecoverable error, will not attempt to reconnect.`,\n 401,\n );\n }\n\n private scheduleFirstDelivery() {\n const generation = this.attemptGeneration;\n void Promise.resolve()\n .then(() => {})\n .then(() => {\n if (generation !== this.attemptGeneration) {\n return;\n }\n this.connected = true;\n this.deliver(this.fullUpdate());\n });\n }\n\n private deliverIfConnected(configSet: ConfigSet) {\n if (this.connected) {\n this.deliver(configSet);\n }\n }\n\n private deliver(configSet: ConfigSet) {\n this.pendingDeliveries.push(configSet);\n if (this.delivering) {\n return;\n }\n this.delivering = true;\n try {\n let next = this.pendingDeliveries.shift();\n while (next) {\n this.emitCaught(next);\n next = this.pendingDeliveries.shift();\n }\n } finally {\n this.delivering = false;\n }\n }\n\n private emitCaught(configSet: ConfigSet) {\n try {\n this.emitter.emit(\"configSetReceived\", configSet);\n } catch (error) {\n this.logger.error(\"[InMemoryConnection] Error dispatching a config update from the test client: \", error);\n }\n }\n\n private fullUpdate(): ConfigSet {\n return { ...TEST_ENVIRONMENT, kind: \"full\", configs: Object.fromEntries(this.configs) };\n }\n\n private store(values: TestValues) {\n this.encodeAll(values).forEach((configState, key) => this.configs.set(key, configState));\n }\n\n private encodeAll(values: TestValues): Map<string, ConfigState> {\n const encoded = new Map<string, ConfigState>();\n for (const [key, value] of Object.entries(values)) {\n encoded.set(key, encodeTestValue(key, value, ++this.revision));\n }\n return encoded;\n }\n}\n","import { DefaultConfigDirectorClient } from \"../DefaultConfigDirectorClient\";\nimport { createDefaultLogger } from \"../logger\";\nimport type { ConfigDirectorClient, ConfigDirectorContext, ConfigDirectorLogger, IdentifyingSdkOptions } from \"../types\";\nimport type { TelemetryClient } from \"../telemetry\";\nimport { InMemoryConnection } from \"./InMemoryConnection\";\nimport type { TestValue, TestValues } from \"./values\";\n\nconst TEST_CLIENT_SDK_KEY = \"test-client\";\n\n/**\n * Options for {@link createTestClient}. Every option is optional.\n */\nexport type TestClientOptions = {\n /**\n * The config values the test client serves once its client connects, keyed by config key. The\n * config type follows from each value: a boolean, an integral number (`integer`), any other\n * number (`float`), a string, or a plain object or array (`json`).\n */\n values?: TestValues;\n /**\n * The connection timeout of the SDK client, in milliseconds. It bounds how long a held\n * `initialize` or `updateContext` waits. Defaults to the SDK's production timeout.\n */\n timeout?: number;\n /**\n * The logger the SDK client uses. Defaults to the SDK's console logger at `warn` level.\n */\n logger?: ConfigDirectorLogger;\n};\n\n/**\n * A real ConfigDirector client connected to an in-memory server that the test controls.\n *\n * The client under {@link TestClient.client} is the SDK's production client: value parsing, watchers,\n * events, and readiness behave as they do against ConfigDirector. Only the network connection and\n * telemetry are replaced, so no request is ever made and nothing is left running after the client is\n * disposed.\n */\nexport interface TestClient {\n /**\n * The SDK client to hand to the code under test. It starts uninitialized, like a production\n * client; `initialize` completes at once with the seeded values unless initialization is held.\n */\n readonly client: ConfigDirectorClient;\n\n /**\n * Stores `value` under `key` and, when the client is connected, delivers it as an update, so\n * reads, watchers, and `configsUpdated` handlers see it.\n */\n setValue(key: string, value: TestValue): void;\n\n /**\n * Removes `key` and, when the client is connected, delivers a full update without it, so reads\n * fall back to the in-code default value and watchers of `key` receive that default.\n */\n removeValue(key: string): void;\n\n /**\n * Replaces every stored value with `values`, disarms any armed hold or failure, and, when the\n * client is connected, delivers the new values as a full update.\n */\n replaceValues(values: TestValues): void;\n\n /**\n * Makes the next `initialize` wait until {@link completeInitialization} or\n * {@link failInitialization} is called, or until the client's timeout elapses.\n */\n holdInitialization(): void;\n\n /**\n * Delivers the stored values to a held `initialize`, so it completes with the client ready. When\n * a hold is armed but no `initialize` has started, it disarms the hold instead.\n */\n completeInitialization(): void;\n\n /**\n * Fails a held `initialize` with an unrecoverable connection error, or arms the next `initialize`\n * to fail. `initialize` completes promptly with the client not ready and `connectionError` fires.\n */\n failInitialization(): void;\n\n /**\n * Makes the next `updateContext` wait until {@link completeContextUpdate} or\n * {@link failContextUpdate} is called, or until the client's timeout elapses.\n */\n holdContextUpdate(): void;\n\n /**\n * Delivers the stored values to a held `updateContext`, or disarms an armed hold that no\n * `updateContext` has picked up.\n */\n completeContextUpdate(): void;\n\n /**\n * Fails a held `updateContext` with an unrecoverable connection error, or arms the next\n * `updateContext` to fail.\n */\n failContextUpdate(): void;\n\n /**\n * The context of every `initialize` and `updateContext` call, in call order. `initialize` without\n * a context records an empty context, and so does a call whose context has an invalid kind, since\n * the client connects without it. Resumes are not recorded.\n */\n readonly contextUpdates: ConfigDirectorContext[];\n}\n\nconst noopTelemetryClient: TelemetryClient = {\n updateContext: async () => {},\n evaluatedConfig: () => {},\n recordInvalidContext: () => {},\n close: async () => {},\n};\n\nclass InMemoryTestClient implements TestClient {\n constructor(\n public readonly client: ConfigDirectorClient,\n private readonly connection: InMemoryConnection,\n ) {}\n\n public setValue(key: string, value: TestValue) {\n this.connection.setValue(key, value);\n }\n\n public removeValue(key: string) {\n this.connection.removeValue(key);\n }\n\n public replaceValues(values: TestValues) {\n this.connection.replaceValues(values);\n }\n\n public holdInitialization() {\n this.connection.hold(\"initialize\");\n }\n\n public completeInitialization() {\n this.connection.complete(\"initialize\");\n }\n\n public failInitialization() {\n this.connection.fail(\"initialize\");\n }\n\n public holdContextUpdate() {\n this.connection.hold(\"updateContext\");\n }\n\n public completeContextUpdate() {\n this.connection.complete(\"updateContext\");\n }\n\n public failContextUpdate() {\n this.connection.fail(\"updateContext\");\n }\n\n public get contextUpdates(): ConfigDirectorContext[] {\n return this.connection.contextUpdates;\n }\n}\n\nexport const createTestClient = (sdkOptions: IdentifyingSdkOptions, options?: TestClientOptions): TestClient => {\n const logger = options?.logger ?? createDefaultLogger();\n const connection = new InMemoryConnection(logger, options?.values ?? {});\n const client = new DefaultConfigDirectorClient(\n noopTelemetryClient,\n TEST_CLIENT_SDK_KEY,\n sdkOptions,\n { logger, connection: { timeout: options?.timeout } },\n { transport: connection },\n );\n return new InMemoryTestClient(client, connection);\n};\n","import { createTestClient as createCoreTestClient } from \"@js-client-core/testing/index\";\nimport type { TestClient, TestClientOptions } from \"@js-client-core/testing/index\";\n\nexport type { TestClient, TestClientOptions, TestValue, TestValues, TestJsonValue } from \"@js-client-core/testing/index\";\nexport { ConfigDirectorConnectionError, ConfigDirectorValidationError } from \"@shared/errors\";\n\n/**\n * Creates a {@link TestClient}: a real `ConfigDirectorClient` connected to an in-memory server\n * that the test controls. No network connection is opened and no telemetry is sent.\n *\n * @param options {@link TestClientOptions}: the initial values, the client's timeout, and its logger\n * @returns A {@link TestClient} whose `client` is passed to the code under test\n *\n * @example\n * import { createTestClient } from \"@configdirector/client-sdk/testing\";\n *\n * const testClient = createTestClient({ values: { \"new-checkout\": true } });\n * await testClient.client.initialize();\n * expect(testClient.client.getValue(\"new-checkout\", false)).toBe(true);\n *\n * testClient.setValue(\"new-checkout\", false);\n */\nexport const createTestClient = (options?: TestClientOptions): TestClient => {\n return createCoreTestClient({ sdkName: \"js-client-sdk\", sdkVersion: \"__VERSION__\" }, options);\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./DefaultConfigDirectorClient-D-Dl3XYG.cjs`),t=e=>{if(e.type===`json`||e.type==null&&typeof e.value==`object`){if(e.valueId)return{valueId:e.valueId,type:`json`};try{return{value:JSON.stringify(e.value),type:`json`}}catch{return{value:e.value.toString(),type:`json`}}}let t=e.value?.toString();return t&&t.length<=500?{value:t}:e.valueId?{valueId:e.valueId}:{value:t}};var n=class{constructor(e){this.pendingEvaluations=[],this.logger=e.logger,this.workerCloseTimeout=e.workerCloseTimeout??6e3,this.pendingEvaluationLimit=e.evaluationQueueLimit??1e3,this.initializeMessage={type:`Initialize`,payload:{sdkKey:e.sdkKey,sdkIdentity:e.sdkIdentity,metaContext:e.metaContext,baseUrl:e.baseUrl.toString(),evaluationQueueLimit:this.pendingEvaluationLimit,initialFlushIntervalDelay:e.initialFlushIntervalDelay??5e3,flushIntervalDelay:e.flushIntervalDelay??3e4}},this.visibilityHandler=()=>{document.visibilityState===`hidden`&&this.flush()}}startWorker(){if(this.worker)return this.worker;let e=new Worker(URL.createObjectURL(new Blob(["(function(){let e=async(e,t)=>e.valueId?{valueId:e.valueId}:e.value?e.value.length<=500&&e.type!=`json`?{value:e.value}:{valueId:await t(e.value)}:e,t=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},n=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0},r=async(e,t,n,r)=>{let i=new AbortController,a=setTimeout(()=>{r.debug(`[fetchWithTimeout] Reached timeout, aborting request`),i.abort()},e);try{let e=await fetch(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}},i=e=>e?.name===`NotAllowedError`?!0:e instanceof TypeError;var a=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.sdkIdentity=e.sdkIdentity,this.metaContext=e.metaContext,this.url=e.urlFactory(`client/telemetry/v1`,e.baseUrl)}async report({context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r}){if(!this.executeRequests)return{success:!1,fatalError:!0};let i={clientSdkKey:this.sdkKey,metaContext:{sdkName:this.sdkIdentity.sdkName,sdkVersion:this.sdkIdentity.sdkVersion,appName:this.metaContext.appName,appVersion:this.metaContext.appVersion},context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r};if(this.isReportEmpty(i))return{success:!0,fatalError:!1};let a=await this.sendReport(i);return a.fatalError&&(this.executeRequests=!1),a}isReportEmpty(e){return t(e.discreteEvents)&&t(e.aggregatedEvents)&&n(e.droppedEvents)}async sendReport(e){try{let t=await r(5e3,this.url,{method:`POST`,body:JSON.stringify(e),keepalive:!0},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){let t=i(e);return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:t}}}isStatusFatal(e){return!!e&&e>=400&&e<500&&e!=429}},o=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},s=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||=new Date;let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},c=class{constructor(e){this.aggregator=new o,this.collectEvents=!0,this.started=!1,this.logger=e.logger,this.evaluationEventQueue=new s(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4,this.initialFlushIntervalDelay=e.initialFlushIntervalDelay??5e3}start(){this.started||!this.collectEvents||(this.started=!0,this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.initialFlushIntervalDelay))}async flushAndScheduleNext(){if(!this.collectEvents)return;let e;try{e=await this.flush()}catch(t){this.logger.warn(`[TelemetryEventCollector] Error flushing telemetry events: `,t),e={success:!1,fatalError:!1}}e.fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.collectEvents&&(this.cancelScheduledFlush(),this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay))}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout);try{await this.flush()}catch(e){this.logger.warn(`[TelemetryEventCollector] Error flushing telemetry events during close: `,e)}this.evaluationEventQueue.clear()}dispose(){this.close()}},l=class{constructor(e){this.valueIdGenerator=e,this.valueIdGenerator=e}async process(t){let n=await Promise.all(t.events.map(async t=>({...t,defaultValue:await e(t.defaultValue,this.valueIdGenerator),evaluatedValue:await e(t.evaluatedValue,this.valueIdGenerator)})));return{...t,events:n}}},u=class extends c{constructor(e){super(e),this.reporter=new a(e),this.evaluationEventSnapshotPreprocessor=new l(e.valueIdGenerator)}async updateContext(e){this.cancelScheduledFlush(),await this.flush(),this._context=e,await this.flushAndScheduleNext()}evaluatedConfig(e){this.collectEvents&&this.evaluationEventQueue.push(e)}async forceFlush(){this.cancelScheduledFlush(),await this.flushAndScheduleNext()}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=await this.evaluationEventSnapshotPreprocessor.process(this.evaluationEventQueue.takeSnapshot());return await this.reporter?.report({context:this._context,discreteEvents:{},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount}})}};let d=(e,t)=>new URL(e,t?.toString()),f=(e,t)=>{let n=BigInt(0),r=BigInt(62),i=Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),a=BigInt(`0x`+i),o=``;for(;a>n;)o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`[Number(a%r)]+o,a/=r;return o.padStart(t,`0`)},p=Math.ceil(128/Math.log2(62)),m=new TextEncoder,h=async e=>{if(typeof crypto>`u`||crypto.subtle==null)return;let t=await crypto.subtle.digest(`SHA-256`,m.encode(e?.toString()??``));return f(new Uint8Array(t,0,16),p)},g,_=(e,t,...n)=>({type:`Log`,payload:{level:e,message:t,args:n}}),v=e=>{try{self.postMessage(e)}catch{self.postMessage(_(e.payload.level,e.payload.message))}},y={debug:function(e,...t){v(_(`debug`,e,t))},info:function(e,...t){v(_(`info`,e,t))},warn:function(e,...t){v(_(`warn`,e,t))},error:function(e,...t){v(_(`error`,e,t))}};addEventListener(`message`,e=>{let t=e.data;if(t.type!=`Initialize`&&!g){y.info(`[TelemetryWorker] Ignoring ${t.type} because no collector is configured`);return}switch(y.debug(`[TelemetryWorker] Received '${t.type}' event:`,t),t.type){case`Initialize`:if(g){y.warn(`[TelemetryWorker] Received a duplicate Initialize event, ignoring it`);break}g=new u({...t.payload,baseUrl:d(t.payload.baseUrl),urlFactory:d,valueIdGenerator:h,logger:y}),g.start();break;case`EvaluatedConfigEvent`:g?.evaluatedConfig(t.payload);break;case`UpdateContext`:g?.updateContext(t.payload.context);break;case`Flush`:g?.forceFlush();break;case`Close`:(g?.close()??Promise.resolve()).then(()=>{self.postMessage({type:`Closed`}),self.close()});break}})})();"],{type:`application/javascript`})));e.onmessage=e=>{this.handleWorkerEvent(e.data)},e.postMessage(this.initializeMessage),this.worker=e;try{document.addEventListener(`visibilitychange`,this.visibilityHandler)}catch(e){this.logger.warn(`[TelemetryEventCollector] Could not configure 'visibilitychange' listener: `,e)}for(let t of this.pendingEvaluations)e.postMessage(t);return this.pendingEvaluations.length=0,e}async updateContext(e){if(this.closePromise)return;let t={type:`UpdateContext`,payload:{context:e}};this.startWorker().postMessage(t)}evaluatedConfig(e){let t={type:`EvaluatedConfigEvent`,payload:this.sanitizeEvaluatedConfigEvent(e)};if(this.worker){this.worker.postMessage(t);return}this.pendingEvaluations.length>=this.pendingEvaluationLimit&&this.pendingEvaluations.shift(),this.pendingEvaluations.push(t)}sanitizeEvaluatedConfigEvent(e){return{...e,defaultValue:t({value:e.defaultValue,type:e.type}),evaluatedValue:t({value:e.evaluatedValue,valueId:e.evaluatedValueId,type:e.type})}}handleWorkerEvent(e){if(e.type===`Closed`){this.finishClose();return}if(!e.payload?.level||!e.payload?.message)return;let{level:t,message:n,args:r}=e.payload;switch(t){case`debug`:this.logger.debug(n,...r);break;case`info`:this.logger.info(n,...r);break;case`warn`:this.logger.warn(n,...r);break;case`error`:this.logger.error(n,...r);break}}flush(){this.worker?.postMessage({type:`Flush`})}close(){return this.closePromise?this.closePromise:this.performClose()}performClose(){let e=this.worker;if(!e)return this.pendingEvaluations.length=0,this.closePromise=Promise.resolve(),this.closePromise;try{document.removeEventListener(`visibilitychange`,this.visibilityHandler)}catch(e){this.logger.warn(`[WebWorkerTelemetryClient] Could not remove 'visibilitychange' listener: `,e)}return this.closePromise=new Promise(t=>{this.closeResolve=t,this.closeTimer=setTimeout(()=>{this.logger.warn(`[WebWorkerTelemetryClient] Timed out waiting for the telemetry worker to close. Terminating the worker.`),this.finishClose()},this.workerCloseTimeout),e.postMessage({type:`Close`})}),this.closePromise}finishClose(){clearTimeout(this.closeTimer),this.closeTimer=void 0,this.worker?.terminate(),this.closeResolve?.()}};const r=(e,t)=>{let n=BigInt(0),r=BigInt(62),i=Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),a=BigInt(`0x`+i),o=``;for(;a>n;)o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`[Number(a%r)]+o,a/=r;return o.padStart(t,`0`)},i=Math.ceil(128/Math.log2(62)),a=new TextEncoder,o=async e=>{if(typeof crypto>`u`||crypto.subtle==null)return;let t=await crypto.subtle.digest(`SHA-256`,a.encode(e?.toString()??``));return r(new Uint8Array(t,0,16),i)},s=(t,r,i)=>{let a=i?.logger??e.a(),s=e.i(i?.connection?.url)??`https://client-sdk-api.configdirector.com`;return new e.t(new n({sdkKey:t,sdkIdentity:{sdkName:r.sdkName,sdkVersion:r.sdkVersion},metaContext:{appName:i?.metadata?.appName,appVersion:i?.metadata?.appVersion},logger:a,baseUrl:s,urlFactory:e.r,valueIdGenerator:o}),t,r,{...i,logger:a})},c=(e,t)=>s(e,{sdkName:`js-client-sdk`,sdkVersion:`1.6.0`},t),l=(t,n)=>e.a(t,n);exports.createClient=c,exports.createConsoleLogger=l;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./DefaultConfigDirectorClient-a1pnvte2.cjs`),t=e=>{if(e.type===`json`||e.type==null&&typeof e.value==`object`){if(e.valueId)return{valueId:e.valueId,type:`json`};try{return{value:JSON.stringify(e.value),type:`json`}}catch{return{value:e.value.toString(),type:`json`}}}let t=e.value?.toString();return t&&t.length<=500?{value:t}:e.valueId?{valueId:e.valueId}:{value:t}};var n=class{constructor(){this.countsByKindSample=new Map,this.countBeyondSampleLimit=0}record(e){return this.startTime??=new Date,this.add(e,1)}absorb(e){for(let t of e)(this.startTime===void 0||t.startTime<this.startTime)&&(this.startTime=t.startTime),this.add(t.event.kindSample,t.count)}add(e,t){if(e!==void 0){let n=this.countsByKindSample.get(e);if(n!==void 0)return this.countsByKindSample.set(e,n+t),`known-kind-sample`;if(this.countsByKindSample.size<20)return this.countsByKindSample.set(e,t),`new-kind-sample`}let n=this.countBeyondSampleLimit===0;return this.countBeyondSampleLimit+=t,n?`first-beyond-sample-limit`:`beyond-sample-limit`}takeSnapshot(){let e=new Date,t=this.startTime??e,n=(n,r)=>({startTime:t,endTime:e,count:n,event:{reason:`invalid-context-kind`,kindSample:r}}),r=Array.from(this.countsByKindSample,([e,t])=>n(t,e)),i=this.countBeyondSampleLimit>0?[...r,n(this.countBeyondSampleLimit)]:r;return this.clear(),i}clear(){this.countsByKindSample.clear(),this.countBeyondSampleLimit=0,this.startTime=void 0}},r=class{constructor(e){this.turnedOff=!1,this.pendingEvaluations=[],this.pendingInvalidContextSubmissions=new n,this.logger=e.logger,this.workerCloseTimeout=e.workerCloseTimeout??6e3,this.pendingEvaluationLimit=e.evaluationQueueLimit??1e3,this.initializeMessage={type:`Initialize`,payload:{sdkKey:e.sdkKey,sdkIdentity:e.sdkIdentity,metaContext:e.metaContext,baseUrl:e.baseUrl.toString(),evaluationQueueLimit:this.pendingEvaluationLimit,initialFlushIntervalDelay:e.initialFlushIntervalDelay??5e3,flushIntervalDelay:e.flushIntervalDelay??3e4}},this.visibilityHandler=()=>{document.visibilityState===`hidden`&&this.flush()}}createWorkerOrTurnOff(){if(typeof Worker>`u`){this.turnOff(`[WebWorkerTelemetryClient] Telemetry is off because Web Workers are not available in this environment.`);return}try{return new Worker(URL.createObjectURL(new Blob(["(function(){let e=async(e,t)=>e.valueId?{valueId:e.valueId}:e.value?e.value.length<=500&&e.type!=`json`?{value:e.value}:{valueId:await t(e.value)}:e,t=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},n=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0},r=async(e,t,n,r)=>{let i=new AbortController,a=setTimeout(()=>{r.debug(`[fetchWithTimeout] Reached timeout, aborting request`),i.abort()},e);try{let e=await fetch(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}},i=e=>e?.name===`NotAllowedError`?!0:e instanceof TypeError;var a=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.sdkIdentity=e.sdkIdentity,this.metaContext=e.metaContext,this.url=e.urlFactory(`client/telemetry/v1`,e.baseUrl)}async report({context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r}){if(!this.executeRequests)return{success:!1,fatalError:!0};let i={clientSdkKey:this.sdkKey,metaContext:{sdkName:this.sdkIdentity.sdkName,sdkVersion:this.sdkIdentity.sdkVersion,appName:this.metaContext.appName,appVersion:this.metaContext.appVersion},context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r};if(this.isReportEmpty(i))return{success:!0,fatalError:!1};let a=await this.sendReport(i);return a.fatalError&&(this.executeRequests=!1),a}isReportEmpty(e){return t(e.discreteEvents)&&t(e.aggregatedEvents)&&n(e.droppedEvents)}async sendReport(e){try{let t=await r(5e3,this.url,{method:`POST`,body:JSON.stringify(e),keepalive:!0},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){let t=i(e);return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:t}}}isStatusFatal(e){return!!e&&e>=400&&e<500&&e!=429}},o=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},s=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||=new Date;let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},c=class{constructor(e){this.aggregator=new o,this.collectEvents=!0,this.started=!1,this.logger=e.logger,this.evaluationEventQueue=new s(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4,this.initialFlushIntervalDelay=e.initialFlushIntervalDelay??5e3}start(){this.started||!this.collectEvents||(this.started=!0,this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.initialFlushIntervalDelay))}async flushAndScheduleNext(){if(!this.collectEvents)return;let e;try{e=await this.flush()}catch(t){this.logger.warn(`[TelemetryEventCollector] Error flushing telemetry events: `,t),e={success:!1,fatalError:!1}}e.fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.collectEvents&&(this.cancelScheduledFlush(),this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay))}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout);try{await this.flush()}catch(e){this.logger.warn(`[TelemetryEventCollector] Error flushing telemetry events during close: `,e)}this.evaluationEventQueue.clear()}dispose(){this.close()}},l=class{constructor(e){this.valueIdGenerator=e,this.valueIdGenerator=e}async process(t){let n=await Promise.all(t.events.map(async t=>({...t,defaultValue:await e(t.defaultValue,this.valueIdGenerator),evaluatedValue:await e(t.evaluatedValue,this.valueIdGenerator)})));return{...t,events:n}}},u=class{constructor(){this.countsByKindSample=new Map,this.countBeyondSampleLimit=0}record(e){return this.startTime??=new Date,this.add(e,1)}absorb(e){for(let t of e)(this.startTime===void 0||t.startTime<this.startTime)&&(this.startTime=t.startTime),this.add(t.event.kindSample,t.count)}add(e,t){if(e!==void 0){let n=this.countsByKindSample.get(e);if(n!==void 0)return this.countsByKindSample.set(e,n+t),`known-kind-sample`;if(this.countsByKindSample.size<20)return this.countsByKindSample.set(e,t),`new-kind-sample`}let n=this.countBeyondSampleLimit===0;return this.countBeyondSampleLimit+=t,n?`first-beyond-sample-limit`:`beyond-sample-limit`}takeSnapshot(){let e=new Date,t=this.startTime??e,n=(n,r)=>({startTime:t,endTime:e,count:n,event:{reason:`invalid-context-kind`,kindSample:r}}),r=Array.from(this.countsByKindSample,([e,t])=>n(t,e)),i=this.countBeyondSampleLimit>0?[...r,n(this.countBeyondSampleLimit)]:r;return this.clear(),i}clear(){this.countsByKindSample.clear(),this.countBeyondSampleLimit=0,this.startTime=void 0}},d=class extends c{constructor(e){super(e),this.invalidContextSubmissions=new u,this.reporter=new a(e),this.evaluationEventSnapshotPreprocessor=new l(e.valueIdGenerator)}async updateContext(e){this.cancelScheduledFlush(),await this.flush(),this._context=e,await this.flushAndScheduleNext()}evaluatedConfig(e){this.collectEvents&&this.evaluationEventQueue.push(e)}recordInvalidContext(e){this.invalidContextSubmissions.record(e)}absorbInvalidContextSubmissions(e){this.invalidContextSubmissions.absorb(e)}async forceFlush(){this.cancelScheduledFlush(),await this.flushAndScheduleNext()}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=await this.evaluationEventSnapshotPreprocessor.process(this.evaluationEventQueue.takeSnapshot());return await this.reporter?.report({context:this._context,discreteEvents:{},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e),invalidContext:this.invalidContextSubmissions.takeSnapshot()},droppedEvents:{evaluatedConfig:e.droppedCount}})}};let f=(e,t)=>new URL(e,t?.toString()),p=(e,t)=>{let n=BigInt(0),r=BigInt(62),i=Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),a=BigInt(`0x`+i),o=``;for(;a>n;)o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`[Number(a%r)]+o,a/=r;return o.padStart(t,`0`)},m=Math.ceil(128/Math.log2(62)),h=new TextEncoder,g=async e=>{if(typeof crypto>`u`||crypto.subtle==null)return;let t=await crypto.subtle.digest(`SHA-256`,h.encode(e?.toString()??``));return p(new Uint8Array(t,0,16),m)},_,v=(e,t,...n)=>({type:`Log`,payload:{level:e,message:t,args:n}}),y=e=>{try{self.postMessage(e)}catch{self.postMessage(v(e.payload.level,e.payload.message))}},b={debug:function(e,...t){y(v(`debug`,e,t))},info:function(e,...t){y(v(`info`,e,t))},warn:function(e,...t){y(v(`warn`,e,t))},error:function(e,...t){y(v(`error`,e,t))}};addEventListener(`message`,e=>{let t=e.data;if(t.type!=`Initialize`&&!_){b.info(`[TelemetryWorker] Ignoring ${t.type} because no collector is configured`);return}switch(b.debug(`[TelemetryWorker] Received '${t.type}' event:`,t),t.type){case`Initialize`:if(_){b.warn(`[TelemetryWorker] Received a duplicate Initialize event, ignoring it`);break}_=new d({...t.payload,baseUrl:f(t.payload.baseUrl),urlFactory:f,valueIdGenerator:g,logger:b}),_.start();break;case`EvaluatedConfigEvent`:_?.evaluatedConfig(t.payload);break;case`InvalidContextSubmissions`:_?.absorbInvalidContextSubmissions(t.payload.entries);break;case`UpdateContext`:_?.updateContext(t.payload.context);break;case`Flush`:_?.forceFlush();break;case`Close`:(_?.close()??Promise.resolve()).then(()=>{self.postMessage({type:`Closed`}),self.close()});break}})})();"],{type:`application/javascript`})))}catch(e){this.turnOff(`[WebWorkerTelemetryClient] Telemetry is off because the telemetry Web Worker could not be created: `,e);return}}turnOff(e,...t){this.turnedOff=!0,this.pendingEvaluations.length=0,this.logger.warn(e,...t)}startWorker(){if(this.worker||this.turnedOff)return this.worker;let e=this.createWorkerOrTurnOff();if(e){e.onmessage=e=>{this.handleWorkerEvent(e.data)},e.postMessage(this.initializeMessage),this.worker=e;try{document.addEventListener(`visibilitychange`,this.visibilityHandler)}catch(e){this.logger.warn(`[TelemetryEventCollector] Could not configure 'visibilitychange' listener: `,e)}for(let t of this.pendingEvaluations)e.postMessage(t);return this.pendingEvaluations.length=0,this.forwardInvalidContextSubmissions(e),e}}forwardInvalidContextSubmissions(e){if(!e)return;let t=this.pendingInvalidContextSubmissions.takeSnapshot();if(t.length===0)return;let n={type:`InvalidContextSubmissions`,payload:{entries:t}};e.postMessage(n)}async updateContext(e){if(this.closePromise)return;let t={type:`UpdateContext`,payload:{context:e}};this.startWorker()?.postMessage(t)}evaluatedConfig(e){if(this.turnedOff)return;let t={type:`EvaluatedConfigEvent`,payload:this.sanitizeEvaluatedConfigEvent(e)};if(this.worker){this.worker.postMessage(t);return}this.pendingEvaluations.length>=this.pendingEvaluationLimit&&this.pendingEvaluations.shift(),this.pendingEvaluations.push(t)}recordInvalidContext(e){this.pendingInvalidContextSubmissions.record(e),this.forwardInvalidContextSubmissions(this.worker)}sanitizeEvaluatedConfigEvent(e){return{...e,defaultValue:t({value:e.defaultValue,type:e.type}),evaluatedValue:t({value:e.evaluatedValue,valueId:e.evaluatedValueId,type:e.type})}}handleWorkerEvent(e){if(e.type===`Closed`){this.finishClose();return}if(!e.payload?.level||!e.payload?.message)return;let{level:t,message:n,args:r}=e.payload;switch(t){case`debug`:this.logger.debug(n,...r);break;case`info`:this.logger.info(n,...r);break;case`warn`:this.logger.warn(n,...r);break;case`error`:this.logger.error(n,...r);break}}flush(){this.worker?.postMessage({type:`Flush`})}close(){return this.closePromise?this.closePromise:this.performClose()}performClose(){let e=this.worker;if(!e)return this.pendingEvaluations.length=0,this.closePromise=Promise.resolve(),this.closePromise;try{document.removeEventListener(`visibilitychange`,this.visibilityHandler)}catch(e){this.logger.warn(`[WebWorkerTelemetryClient] Could not remove 'visibilitychange' listener: `,e)}return this.closePromise=new Promise(t=>{this.closeResolve=t,this.closeTimer=setTimeout(()=>{this.logger.warn(`[WebWorkerTelemetryClient] Timed out waiting for the telemetry worker to close. Terminating the worker.`),this.finishClose()},this.workerCloseTimeout),e.postMessage({type:`Close`})}),this.closePromise}finishClose(){clearTimeout(this.closeTimer),this.closeTimer=void 0,this.worker?.terminate(),this.closeResolve?.()}};const i=(e,t)=>{let n=BigInt(0),r=BigInt(62),i=Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),a=BigInt(`0x`+i),o=``;for(;a>n;)o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`[Number(a%r)]+o,a/=r;return o.padStart(t,`0`)},a=Math.ceil(128/Math.log2(62)),o=new TextEncoder,s=async e=>{if(typeof crypto>`u`||crypto.subtle==null)return;let t=await crypto.subtle.digest(`SHA-256`,o.encode(e?.toString()??``));return i(new Uint8Array(t,0,16),a)},c=(t,n,i)=>{let a=i?.logger??e.a(),o=e.i(i?.connection?.url)??`https://client-sdk-api.configdirector.com`;return new e.t(new r({sdkKey:t,sdkIdentity:{sdkName:n.sdkName,sdkVersion:n.sdkVersion},metaContext:{appName:i?.metadata?.appName,appVersion:i?.metadata?.appVersion},logger:a,baseUrl:o,urlFactory:e.r,valueIdGenerator:s}),t,n,{...i,logger:a})},l=(e,t)=>c(e,{sdkName:`js-client-sdk`,sdkVersion:`1.7.0`},t),u=(t,n)=>e.a(t,n);exports.createClient=l,exports.createConsoleLogger=u;
@@ -1,4 +1,4 @@
1
- import { a as ConfigDirectorContext, c as ConfigDirectorLoggingLevel, i as ConfigDirectorClientOptions, l as ConfigValueType, n as ConfigDirectorValidationError, o as ConfigDirectorLogMessageDecorator, r as ConfigDirectorClient, s as ConfigDirectorLogger, t as ConfigDirectorConnectionError } from "./errors-BOgYEeJ6.cjs";
1
+ import { a as ConfigDirectorContext, c as ConfigDirectorLoggingLevel, i as ConfigDirectorClientOptions, l as ConfigValueType, n as ConfigDirectorValidationError, o as ConfigDirectorLogMessageDecorator, r as ConfigDirectorClient, s as ConfigDirectorLogger, t as ConfigDirectorConnectionError } from "./errors-DTlTJsJh.cjs";
2
2
 
3
3
  //#region ../shared/src/logger.d.ts
4
4
  declare class DefaultConsoleLogger implements ConfigDirectorLogger {
@@ -1,4 +1,4 @@
1
- import { a as ConfigDirectorContext, c as ConfigDirectorLoggingLevel, i as ConfigDirectorClientOptions, l as ConfigValueType, n as ConfigDirectorValidationError, o as ConfigDirectorLogMessageDecorator, r as ConfigDirectorClient, s as ConfigDirectorLogger, t as ConfigDirectorConnectionError } from "./errors-zagImFi0.mjs";
1
+ import { a as ConfigDirectorContext, c as ConfigDirectorLoggingLevel, i as ConfigDirectorClientOptions, l as ConfigValueType, n as ConfigDirectorValidationError, o as ConfigDirectorLogMessageDecorator, r as ConfigDirectorClient, s as ConfigDirectorLogger, t as ConfigDirectorConnectionError } from "./errors-T_hDavJF.mjs";
2
2
 
3
3
  //#region ../shared/src/logger.d.ts
4
4
  declare class DefaultConsoleLogger implements ConfigDirectorLogger {
@@ -1,2 +1,2 @@
1
- import{a as e,i as t,n,o as r,r as i,t as a}from"./DefaultConfigDirectorClient-a018lfK_.mjs";const o=e=>{var t;if(e.type===`json`||e.type==null&&typeof e.value==`object`){if(e.valueId)return{valueId:e.valueId,type:`json`};try{return{value:JSON.stringify(e.value),type:`json`}}catch(t){return{value:e.value.toString(),type:`json`}}}let n=(t=e.value)==null?void 0:t.toString();return n&&n.length<=500?{value:n}:e.valueId?{valueId:e.valueId}:{value:n}};var s=class{constructor(e){var t,n,r,i;this.pendingEvaluations=[],this.logger=e.logger,this.workerCloseTimeout=(t=e.workerCloseTimeout)==null?6e3:t,this.pendingEvaluationLimit=(n=e.evaluationQueueLimit)==null?1e3:n,this.initializeMessage={type:`Initialize`,payload:{sdkKey:e.sdkKey,sdkIdentity:e.sdkIdentity,metaContext:e.metaContext,baseUrl:e.baseUrl.toString(),evaluationQueueLimit:this.pendingEvaluationLimit,initialFlushIntervalDelay:(r=e.initialFlushIntervalDelay)==null?5e3:r,flushIntervalDelay:(i=e.flushIntervalDelay)==null?3e4:i}},this.visibilityHandler=()=>{document.visibilityState===`hidden`&&this.flush()}}startWorker(){if(this.worker)return this.worker;let e=new Worker(URL.createObjectURL(new Blob(["(function(){let e=async(e,t)=>e.valueId?{valueId:e.valueId}:e.value?e.value.length<=500&&e.type!=`json`?{value:e.value}:{valueId:await t(e.value)}:e,t=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},n=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0},r=async(e,t,n,r)=>{let i=new AbortController,a=setTimeout(()=>{r.debug(`[fetchWithTimeout] Reached timeout, aborting request`),i.abort()},e);try{let e=await fetch(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}},i=e=>e?.name===`NotAllowedError`?!0:e instanceof TypeError;var a=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.sdkIdentity=e.sdkIdentity,this.metaContext=e.metaContext,this.url=e.urlFactory(`client/telemetry/v1`,e.baseUrl)}async report({context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r}){if(!this.executeRequests)return{success:!1,fatalError:!0};let i={clientSdkKey:this.sdkKey,metaContext:{sdkName:this.sdkIdentity.sdkName,sdkVersion:this.sdkIdentity.sdkVersion,appName:this.metaContext.appName,appVersion:this.metaContext.appVersion},context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r};if(this.isReportEmpty(i))return{success:!0,fatalError:!1};let a=await this.sendReport(i);return a.fatalError&&(this.executeRequests=!1),a}isReportEmpty(e){return t(e.discreteEvents)&&t(e.aggregatedEvents)&&n(e.droppedEvents)}async sendReport(e){try{let t=await r(5e3,this.url,{method:`POST`,body:JSON.stringify(e),keepalive:!0},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){let t=i(e);return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:t}}}isStatusFatal(e){return!!e&&e>=400&&e<500&&e!=429}},o=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},s=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||=new Date;let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},c=class{constructor(e){this.aggregator=new o,this.collectEvents=!0,this.started=!1,this.logger=e.logger,this.evaluationEventQueue=new s(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4,this.initialFlushIntervalDelay=e.initialFlushIntervalDelay??5e3}start(){this.started||!this.collectEvents||(this.started=!0,this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.initialFlushIntervalDelay))}async flushAndScheduleNext(){if(!this.collectEvents)return;let e;try{e=await this.flush()}catch(t){this.logger.warn(`[TelemetryEventCollector] Error flushing telemetry events: `,t),e={success:!1,fatalError:!1}}e.fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.collectEvents&&(this.cancelScheduledFlush(),this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay))}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout);try{await this.flush()}catch(e){this.logger.warn(`[TelemetryEventCollector] Error flushing telemetry events during close: `,e)}this.evaluationEventQueue.clear()}dispose(){this.close()}},l=class{constructor(e){this.valueIdGenerator=e,this.valueIdGenerator=e}async process(t){let n=await Promise.all(t.events.map(async t=>({...t,defaultValue:await e(t.defaultValue,this.valueIdGenerator),evaluatedValue:await e(t.evaluatedValue,this.valueIdGenerator)})));return{...t,events:n}}},u=class extends c{constructor(e){super(e),this.reporter=new a(e),this.evaluationEventSnapshotPreprocessor=new l(e.valueIdGenerator)}async updateContext(e){this.cancelScheduledFlush(),await this.flush(),this._context=e,await this.flushAndScheduleNext()}evaluatedConfig(e){this.collectEvents&&this.evaluationEventQueue.push(e)}async forceFlush(){this.cancelScheduledFlush(),await this.flushAndScheduleNext()}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=await this.evaluationEventSnapshotPreprocessor.process(this.evaluationEventQueue.takeSnapshot());return await this.reporter?.report({context:this._context,discreteEvents:{},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount}})}};let d=(e,t)=>new URL(e,t?.toString()),f=(e,t)=>{let n=BigInt(0),r=BigInt(62),i=Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),a=BigInt(`0x`+i),o=``;for(;a>n;)o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`[Number(a%r)]+o,a/=r;return o.padStart(t,`0`)},p=Math.ceil(128/Math.log2(62)),m=new TextEncoder,h=async e=>{if(typeof crypto>`u`||crypto.subtle==null)return;let t=await crypto.subtle.digest(`SHA-256`,m.encode(e?.toString()??``));return f(new Uint8Array(t,0,16),p)},g,_=(e,t,...n)=>({type:`Log`,payload:{level:e,message:t,args:n}}),v=e=>{try{self.postMessage(e)}catch{self.postMessage(_(e.payload.level,e.payload.message))}},y={debug:function(e,...t){v(_(`debug`,e,t))},info:function(e,...t){v(_(`info`,e,t))},warn:function(e,...t){v(_(`warn`,e,t))},error:function(e,...t){v(_(`error`,e,t))}};addEventListener(`message`,e=>{let t=e.data;if(t.type!=`Initialize`&&!g){y.info(`[TelemetryWorker] Ignoring ${t.type} because no collector is configured`);return}switch(y.debug(`[TelemetryWorker] Received '${t.type}' event:`,t),t.type){case`Initialize`:if(g){y.warn(`[TelemetryWorker] Received a duplicate Initialize event, ignoring it`);break}g=new u({...t.payload,baseUrl:d(t.payload.baseUrl),urlFactory:d,valueIdGenerator:h,logger:y}),g.start();break;case`EvaluatedConfigEvent`:g?.evaluatedConfig(t.payload);break;case`UpdateContext`:g?.updateContext(t.payload.context);break;case`Flush`:g?.forceFlush();break;case`Close`:(g?.close()??Promise.resolve()).then(()=>{self.postMessage({type:`Closed`}),self.close()});break}})})();"],{type:`application/javascript`})));e.onmessage=e=>{this.handleWorkerEvent(e.data)},e.postMessage(this.initializeMessage),this.worker=e;try{document.addEventListener(`visibilitychange`,this.visibilityHandler)}catch(e){this.logger.warn(`[TelemetryEventCollector] Could not configure 'visibilitychange' listener: `,e)}for(let t of this.pendingEvaluations)e.postMessage(t);return this.pendingEvaluations.length=0,e}async updateContext(e){var t=this;if(t.closePromise)return;let n={type:`UpdateContext`,payload:{context:e}};t.startWorker().postMessage(n)}evaluatedConfig(e){let t={type:`EvaluatedConfigEvent`,payload:this.sanitizeEvaluatedConfigEvent(e)};if(this.worker){this.worker.postMessage(t);return}this.pendingEvaluations.length>=this.pendingEvaluationLimit&&this.pendingEvaluations.shift(),this.pendingEvaluations.push(t)}sanitizeEvaluatedConfigEvent(e){return r(r({},e),{},{defaultValue:o({value:e.defaultValue,type:e.type}),evaluatedValue:o({value:e.evaluatedValue,valueId:e.evaluatedValueId,type:e.type})})}handleWorkerEvent(e){var t,n;if(e.type===`Closed`){this.finishClose();return}if(!((t=e.payload)!=null&&t.level)||!((n=e.payload)!=null&&n.message))return;let{level:r,message:i,args:a}=e.payload;switch(r){case`debug`:this.logger.debug(i,...a);break;case`info`:this.logger.info(i,...a);break;case`warn`:this.logger.warn(i,...a);break;case`error`:this.logger.error(i,...a);break}}flush(){var e;(e=this.worker)==null||e.postMessage({type:`Flush`})}close(){return this.closePromise?this.closePromise:this.performClose()}performClose(){let e=this.worker;if(!e)return this.pendingEvaluations.length=0,this.closePromise=Promise.resolve(),this.closePromise;try{document.removeEventListener(`visibilitychange`,this.visibilityHandler)}catch(e){this.logger.warn(`[WebWorkerTelemetryClient] Could not remove 'visibilitychange' listener: `,e)}return this.closePromise=new Promise(t=>{this.closeResolve=t,this.closeTimer=setTimeout(()=>{this.logger.warn(`[WebWorkerTelemetryClient] Timed out waiting for the telemetry worker to close. Terminating the worker.`),this.finishClose()},this.workerCloseTimeout),e.postMessage({type:`Close`})}),this.closePromise}finishClose(){var e,t;clearTimeout(this.closeTimer),this.closeTimer=void 0,(e=this.worker)==null||e.terminate(),(t=this.closeResolve)==null||t.call(this)}};const c=(e,t)=>{let n=BigInt(0),r=BigInt(62),i=Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),a=BigInt(`0x`+i),o=``;for(;a>n;)o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`[Number(a%r)]+o,a/=r;return o.padStart(t,`0`)},l=Math.ceil(128/Math.log2(62)),u=new TextEncoder,d=async e=>{var t;if(typeof crypto>`u`||crypto.subtle==null)return;let n=await crypto.subtle.digest(`SHA-256`,u.encode((t=e==null?void 0:e.toString())==null?``:t));return c(new Uint8Array(n,0,16),l)},f=(o,c,l)=>{var u,f,p,m,h;let g=(u=l==null?void 0:l.logger)==null?e():u,_=(f=t(l==null||(p=l.connection)==null?void 0:p.url))==null?n:f;return new a(new s({sdkKey:o,sdkIdentity:{sdkName:c.sdkName,sdkVersion:c.sdkVersion},metaContext:{appName:l==null||(m=l.metadata)==null?void 0:m.appName,appVersion:l==null||(h=l.metadata)==null?void 0:h.appVersion},logger:g,baseUrl:_,urlFactory:i,valueIdGenerator:d}),o,c,r(r({},l),{},{logger:g}))},p=(e,t)=>f(e,{sdkName:`js-client-sdk`,sdkVersion:`1.6.0`},t),m=(t,n)=>e(t,n);export{p as createClient,m as createConsoleLogger};
1
+ import{a as e,i as t,n,o as r,r as i,t as a}from"./DefaultConfigDirectorClient-DWgN1o7o.mjs";const o=e=>{var t;if(e.type===`json`||e.type==null&&typeof e.value==`object`){if(e.valueId)return{valueId:e.valueId,type:`json`};try{return{value:JSON.stringify(e.value),type:`json`}}catch(t){return{value:e.value.toString(),type:`json`}}}let n=(t=e.value)==null?void 0:t.toString();return n&&n.length<=500?{value:n}:e.valueId?{valueId:e.valueId}:{value:n}};var s=class{constructor(){this.countsByKindSample=new Map,this.countBeyondSampleLimit=0}record(e){return this.startTime!=null||(this.startTime=new Date),this.add(e,1)}absorb(e){for(let t of e)(this.startTime===void 0||t.startTime<this.startTime)&&(this.startTime=t.startTime),this.add(t.event.kindSample,t.count)}add(e,t){if(e!==void 0){let n=this.countsByKindSample.get(e);if(n!==void 0)return this.countsByKindSample.set(e,n+t),`known-kind-sample`;if(this.countsByKindSample.size<20)return this.countsByKindSample.set(e,t),`new-kind-sample`}let n=this.countBeyondSampleLimit===0;return this.countBeyondSampleLimit+=t,n?`first-beyond-sample-limit`:`beyond-sample-limit`}takeSnapshot(){var e;let t=new Date,n=(e=this.startTime)==null?t:e,r=(e,r)=>({startTime:n,endTime:t,count:e,event:{reason:`invalid-context-kind`,kindSample:r}}),i=Array.from(this.countsByKindSample,([e,t])=>r(t,e)),a=this.countBeyondSampleLimit>0?[...i,r(this.countBeyondSampleLimit)]:i;return this.clear(),a}clear(){this.countsByKindSample.clear(),this.countBeyondSampleLimit=0,this.startTime=void 0}},c=class{constructor(e){var t,n,r,i;this.turnedOff=!1,this.pendingEvaluations=[],this.pendingInvalidContextSubmissions=new s,this.logger=e.logger,this.workerCloseTimeout=(t=e.workerCloseTimeout)==null?6e3:t,this.pendingEvaluationLimit=(n=e.evaluationQueueLimit)==null?1e3:n,this.initializeMessage={type:`Initialize`,payload:{sdkKey:e.sdkKey,sdkIdentity:e.sdkIdentity,metaContext:e.metaContext,baseUrl:e.baseUrl.toString(),evaluationQueueLimit:this.pendingEvaluationLimit,initialFlushIntervalDelay:(r=e.initialFlushIntervalDelay)==null?5e3:r,flushIntervalDelay:(i=e.flushIntervalDelay)==null?3e4:i}},this.visibilityHandler=()=>{document.visibilityState===`hidden`&&this.flush()}}createWorkerOrTurnOff(){if(typeof Worker>`u`){this.turnOff(`[WebWorkerTelemetryClient] Telemetry is off because Web Workers are not available in this environment.`);return}try{return new Worker(URL.createObjectURL(new Blob(["(function(){let e=async(e,t)=>e.valueId?{valueId:e.valueId}:e.value?e.value.length<=500&&e.type!=`json`?{value:e.value}:{valueId:await t(e.value)}:e,t=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},n=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0},r=async(e,t,n,r)=>{let i=new AbortController,a=setTimeout(()=>{r.debug(`[fetchWithTimeout] Reached timeout, aborting request`),i.abort()},e);try{let e=await fetch(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}},i=e=>e?.name===`NotAllowedError`?!0:e instanceof TypeError;var a=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.sdkIdentity=e.sdkIdentity,this.metaContext=e.metaContext,this.url=e.urlFactory(`client/telemetry/v1`,e.baseUrl)}async report({context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r}){if(!this.executeRequests)return{success:!1,fatalError:!0};let i={clientSdkKey:this.sdkKey,metaContext:{sdkName:this.sdkIdentity.sdkName,sdkVersion:this.sdkIdentity.sdkVersion,appName:this.metaContext.appName,appVersion:this.metaContext.appVersion},context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r};if(this.isReportEmpty(i))return{success:!0,fatalError:!1};let a=await this.sendReport(i);return a.fatalError&&(this.executeRequests=!1),a}isReportEmpty(e){return t(e.discreteEvents)&&t(e.aggregatedEvents)&&n(e.droppedEvents)}async sendReport(e){try{let t=await r(5e3,this.url,{method:`POST`,body:JSON.stringify(e),keepalive:!0},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){let t=i(e);return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:t}}}isStatusFatal(e){return!!e&&e>=400&&e<500&&e!=429}},o=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},s=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||=new Date;let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},c=class{constructor(e){this.aggregator=new o,this.collectEvents=!0,this.started=!1,this.logger=e.logger,this.evaluationEventQueue=new s(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4,this.initialFlushIntervalDelay=e.initialFlushIntervalDelay??5e3}start(){this.started||!this.collectEvents||(this.started=!0,this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.initialFlushIntervalDelay))}async flushAndScheduleNext(){if(!this.collectEvents)return;let e;try{e=await this.flush()}catch(t){this.logger.warn(`[TelemetryEventCollector] Error flushing telemetry events: `,t),e={success:!1,fatalError:!1}}e.fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.collectEvents&&(this.cancelScheduledFlush(),this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay))}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout);try{await this.flush()}catch(e){this.logger.warn(`[TelemetryEventCollector] Error flushing telemetry events during close: `,e)}this.evaluationEventQueue.clear()}dispose(){this.close()}},l=class{constructor(e){this.valueIdGenerator=e,this.valueIdGenerator=e}async process(t){let n=await Promise.all(t.events.map(async t=>({...t,defaultValue:await e(t.defaultValue,this.valueIdGenerator),evaluatedValue:await e(t.evaluatedValue,this.valueIdGenerator)})));return{...t,events:n}}},u=class{constructor(){this.countsByKindSample=new Map,this.countBeyondSampleLimit=0}record(e){return this.startTime??=new Date,this.add(e,1)}absorb(e){for(let t of e)(this.startTime===void 0||t.startTime<this.startTime)&&(this.startTime=t.startTime),this.add(t.event.kindSample,t.count)}add(e,t){if(e!==void 0){let n=this.countsByKindSample.get(e);if(n!==void 0)return this.countsByKindSample.set(e,n+t),`known-kind-sample`;if(this.countsByKindSample.size<20)return this.countsByKindSample.set(e,t),`new-kind-sample`}let n=this.countBeyondSampleLimit===0;return this.countBeyondSampleLimit+=t,n?`first-beyond-sample-limit`:`beyond-sample-limit`}takeSnapshot(){let e=new Date,t=this.startTime??e,n=(n,r)=>({startTime:t,endTime:e,count:n,event:{reason:`invalid-context-kind`,kindSample:r}}),r=Array.from(this.countsByKindSample,([e,t])=>n(t,e)),i=this.countBeyondSampleLimit>0?[...r,n(this.countBeyondSampleLimit)]:r;return this.clear(),i}clear(){this.countsByKindSample.clear(),this.countBeyondSampleLimit=0,this.startTime=void 0}},d=class extends c{constructor(e){super(e),this.invalidContextSubmissions=new u,this.reporter=new a(e),this.evaluationEventSnapshotPreprocessor=new l(e.valueIdGenerator)}async updateContext(e){this.cancelScheduledFlush(),await this.flush(),this._context=e,await this.flushAndScheduleNext()}evaluatedConfig(e){this.collectEvents&&this.evaluationEventQueue.push(e)}recordInvalidContext(e){this.invalidContextSubmissions.record(e)}absorbInvalidContextSubmissions(e){this.invalidContextSubmissions.absorb(e)}async forceFlush(){this.cancelScheduledFlush(),await this.flushAndScheduleNext()}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=await this.evaluationEventSnapshotPreprocessor.process(this.evaluationEventQueue.takeSnapshot());return await this.reporter?.report({context:this._context,discreteEvents:{},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e),invalidContext:this.invalidContextSubmissions.takeSnapshot()},droppedEvents:{evaluatedConfig:e.droppedCount}})}};let f=(e,t)=>new URL(e,t?.toString()),p=(e,t)=>{let n=BigInt(0),r=BigInt(62),i=Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),a=BigInt(`0x`+i),o=``;for(;a>n;)o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`[Number(a%r)]+o,a/=r;return o.padStart(t,`0`)},m=Math.ceil(128/Math.log2(62)),h=new TextEncoder,g=async e=>{if(typeof crypto>`u`||crypto.subtle==null)return;let t=await crypto.subtle.digest(`SHA-256`,h.encode(e?.toString()??``));return p(new Uint8Array(t,0,16),m)},_,v=(e,t,...n)=>({type:`Log`,payload:{level:e,message:t,args:n}}),y=e=>{try{self.postMessage(e)}catch{self.postMessage(v(e.payload.level,e.payload.message))}},b={debug:function(e,...t){y(v(`debug`,e,t))},info:function(e,...t){y(v(`info`,e,t))},warn:function(e,...t){y(v(`warn`,e,t))},error:function(e,...t){y(v(`error`,e,t))}};addEventListener(`message`,e=>{let t=e.data;if(t.type!=`Initialize`&&!_){b.info(`[TelemetryWorker] Ignoring ${t.type} because no collector is configured`);return}switch(b.debug(`[TelemetryWorker] Received '${t.type}' event:`,t),t.type){case`Initialize`:if(_){b.warn(`[TelemetryWorker] Received a duplicate Initialize event, ignoring it`);break}_=new d({...t.payload,baseUrl:f(t.payload.baseUrl),urlFactory:f,valueIdGenerator:g,logger:b}),_.start();break;case`EvaluatedConfigEvent`:_?.evaluatedConfig(t.payload);break;case`InvalidContextSubmissions`:_?.absorbInvalidContextSubmissions(t.payload.entries);break;case`UpdateContext`:_?.updateContext(t.payload.context);break;case`Flush`:_?.forceFlush();break;case`Close`:(_?.close()??Promise.resolve()).then(()=>{self.postMessage({type:`Closed`}),self.close()});break}})})();"],{type:`application/javascript`})))}catch(e){this.turnOff(`[WebWorkerTelemetryClient] Telemetry is off because the telemetry Web Worker could not be created: `,e);return}}turnOff(e,...t){this.turnedOff=!0,this.pendingEvaluations.length=0,this.logger.warn(e,...t)}startWorker(){if(this.worker||this.turnedOff)return this.worker;let e=this.createWorkerOrTurnOff();if(e){e.onmessage=e=>{this.handleWorkerEvent(e.data)},e.postMessage(this.initializeMessage),this.worker=e;try{document.addEventListener(`visibilitychange`,this.visibilityHandler)}catch(e){this.logger.warn(`[TelemetryEventCollector] Could not configure 'visibilitychange' listener: `,e)}for(let t of this.pendingEvaluations)e.postMessage(t);return this.pendingEvaluations.length=0,this.forwardInvalidContextSubmissions(e),e}}forwardInvalidContextSubmissions(e){if(!e)return;let t=this.pendingInvalidContextSubmissions.takeSnapshot();if(t.length===0)return;let n={type:`InvalidContextSubmissions`,payload:{entries:t}};e.postMessage(n)}async updateContext(e){var t=this,n;if(t.closePromise)return;let r={type:`UpdateContext`,payload:{context:e}};(n=t.startWorker())==null||n.postMessage(r)}evaluatedConfig(e){if(this.turnedOff)return;let t={type:`EvaluatedConfigEvent`,payload:this.sanitizeEvaluatedConfigEvent(e)};if(this.worker){this.worker.postMessage(t);return}this.pendingEvaluations.length>=this.pendingEvaluationLimit&&this.pendingEvaluations.shift(),this.pendingEvaluations.push(t)}recordInvalidContext(e){this.pendingInvalidContextSubmissions.record(e),this.forwardInvalidContextSubmissions(this.worker)}sanitizeEvaluatedConfigEvent(e){return r(r({},e),{},{defaultValue:o({value:e.defaultValue,type:e.type}),evaluatedValue:o({value:e.evaluatedValue,valueId:e.evaluatedValueId,type:e.type})})}handleWorkerEvent(e){var t,n;if(e.type===`Closed`){this.finishClose();return}if(!((t=e.payload)!=null&&t.level)||!((n=e.payload)!=null&&n.message))return;let{level:r,message:i,args:a}=e.payload;switch(r){case`debug`:this.logger.debug(i,...a);break;case`info`:this.logger.info(i,...a);break;case`warn`:this.logger.warn(i,...a);break;case`error`:this.logger.error(i,...a);break}}flush(){var e;(e=this.worker)==null||e.postMessage({type:`Flush`})}close(){return this.closePromise?this.closePromise:this.performClose()}performClose(){let e=this.worker;if(!e)return this.pendingEvaluations.length=0,this.closePromise=Promise.resolve(),this.closePromise;try{document.removeEventListener(`visibilitychange`,this.visibilityHandler)}catch(e){this.logger.warn(`[WebWorkerTelemetryClient] Could not remove 'visibilitychange' listener: `,e)}return this.closePromise=new Promise(t=>{this.closeResolve=t,this.closeTimer=setTimeout(()=>{this.logger.warn(`[WebWorkerTelemetryClient] Timed out waiting for the telemetry worker to close. Terminating the worker.`),this.finishClose()},this.workerCloseTimeout),e.postMessage({type:`Close`})}),this.closePromise}finishClose(){var e,t;clearTimeout(this.closeTimer),this.closeTimer=void 0,(e=this.worker)==null||e.terminate(),(t=this.closeResolve)==null||t.call(this)}};const l=(e,t)=>{let n=BigInt(0),r=BigInt(62),i=Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),a=BigInt(`0x`+i),o=``;for(;a>n;)o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`[Number(a%r)]+o,a/=r;return o.padStart(t,`0`)},u=Math.ceil(128/Math.log2(62)),d=new TextEncoder,f=async e=>{var t;if(typeof crypto>`u`||crypto.subtle==null)return;let n=await crypto.subtle.digest(`SHA-256`,d.encode((t=e==null?void 0:e.toString())==null?``:t));return l(new Uint8Array(n,0,16),u)},p=(o,s,l)=>{var u,d,p,m,h;let g=(u=l==null?void 0:l.logger)==null?e():u,_=(d=t(l==null||(p=l.connection)==null?void 0:p.url))==null?n:d;return new a(new c({sdkKey:o,sdkIdentity:{sdkName:s.sdkName,sdkVersion:s.sdkVersion},metaContext:{appName:l==null||(m=l.metadata)==null?void 0:m.appName,appVersion:l==null||(h=l.metadata)==null?void 0:h.appVersion},logger:g,baseUrl:_,urlFactory:i,valueIdGenerator:f}),o,s,r(r({},l),{},{logger:g}))},m=(e,t)=>p(e,{sdkName:`js-client-sdk`,sdkVersion:`1.7.0`},t),h=(t,n)=>e(t,n);export{m as createClient,h as createConsoleLogger};
2
2
  //# sourceMappingURL=configdirector-client.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"configdirector-client.mjs","names":[],"sources":["../../shared/src/telemetry/utils.ts","../../js-browser-client/src/telemetry/WebWorkerTelemetryClient.ts","../../shared/src/base62-encoder.ts","../../shared/src/value-id.ts","../../js-browser-client/src/telemetry/value-id-generator.ts","../../js-browser-client/src/BrowserConfigDirectorClient.ts","../src/api.ts"],"sourcesContent":["import type { ConfigType, ConfigValueType } from \"../types\";\nimport type { ValueIdGenerator, DroppedEvents } from \"./types\";\n\nconst CONFIG_VALUE_MAX_LENGTH = 500;\n\nexport type TelemetryValue = {\n value?: string;\n valueId?: string;\n type?: ConfigType;\n};\n\nexport type TelemetryEvaluationValue<TV extends ConfigValueType> = {\n value: TV;\n valueId?: string | null | undefined;\n type?: ConfigType;\n};\n\nexport const compactTelemetryValue = async (\n tv: TelemetryValue,\n valueIdGenerator: ValueIdGenerator,\n): Promise<TelemetryValue> => {\n if (tv.valueId) {\n return { valueId: tv.valueId };\n }\n if (tv.value) {\n if (tv.value.length <= CONFIG_VALUE_MAX_LENGTH && tv.type != \"json\") {\n return { value: tv.value };\n }\n\n return { valueId: await valueIdGenerator(tv.value) };\n }\n\n return tv;\n};\n\nexport const mapToTelemetryValue = <TV extends ConfigValueType>(\n ev: TelemetryEvaluationValue<TV>,\n): TelemetryValue => {\n if (ev.type === \"json\" || (ev.type == null && typeof ev.value === \"object\")) {\n if (ev.valueId) {\n return { valueId: ev.valueId, type: \"json\" };\n }\n try {\n const json = JSON.stringify(ev.value);\n return { value: json, type: \"json\" };\n } catch {\n return { value: ev.value.toString(), type: \"json\" };\n }\n }\n\n const stringValue = ev.value?.toString();\n if (stringValue && stringValue.length <= CONFIG_VALUE_MAX_LENGTH) {\n return { value: stringValue };\n }\n\n return ev.valueId ? { valueId: ev.valueId } : { value: stringValue };\n};\n\nexport const djb2Hash = (data: string): string => {\n const hash = djb2(new TextEncoder().encode(data));\n return hash.toString(16).padStart(8, \"0\");\n};\n\nconst djb2 = (bytes: Uint8Array): number => {\n let hash = 5381;\n for (let i = 0; i < bytes.length; i++) {\n hash = (hash << 5) + hash + (bytes[i] ?? 0);\n hash = hash >>> 0;\n }\n return hash;\n};\n\nexport const isEventListEmpty = <T extends Record<string | symbol, any[]>>(eventList: T): boolean => {\n const keys = Object.keys(eventList);\n if (keys.length == 0) {\n return true;\n }\n\n for (const key of keys) {\n if ((eventList[key]?.length ?? 0) > 0) {\n return false;\n }\n }\n\n return true;\n};\n\nexport const isDroppedEventsEmpty = (droppedEvents?: DroppedEvents): boolean => {\n if (!droppedEvents) {\n return true;\n }\n const keys = Object.keys(droppedEvents);\n if (keys.length == 0) {\n return true;\n }\n\n for (const key of keys) {\n if ((droppedEvents[key] ?? 0) > 0) {\n return false;\n }\n }\n\n return true;\n};\n","import { type TelemetryEventCollectorOptions } from \"@shared/telemetry/TelemetryEventCollector\";\nimport type { ConfigDirectorContext, ConfigDirectorLogger, ConfigValueType } from \"@js-client-core/types\";\nimport type { TelemetryClient, EvaluatedConfigEvent } from \"@js-client-core/telemetry\";\nimport type {\n TelemetryCloseEvent,\n TelemetryEvaluatedConfigEvent,\n TelemetryFlushEvent,\n TelemetryInitializeEvent,\n TelemetryUpdateContextEvent,\n TelemetryWorkerResponseEvent,\n} from \"./types\";\nimport type { TelemetryValue } from \"@shared/telemetry/utils\";\nimport { mapToTelemetryValue } from \"@shared/telemetry/utils\";\n\nconst DEFAULT_WORKER_CLOSE_TIMEOUT = 6_000;\n\nexport type WebWorkerTelemetryClientOptions = TelemetryEventCollectorOptions & {\n workerCloseTimeout?: number;\n};\n\nexport class WebWorkerTelemetryClient implements TelemetryClient {\n private readonly logger: ConfigDirectorLogger;\n private readonly initializeMessage: TelemetryInitializeEvent;\n private worker: Worker | undefined;\n private readonly pendingEvaluations: TelemetryEvaluatedConfigEvent[] = [];\n private readonly pendingEvaluationLimit: number;\n private closePromise: Promise<void> | undefined;\n private closeResolve: (() => void) | undefined;\n private closeTimer: ReturnType<typeof setTimeout> | undefined;\n private readonly workerCloseTimeout: number;\n private readonly visibilityHandler: () => void;\n\n constructor(options: WebWorkerTelemetryClientOptions) {\n this.logger = options.logger;\n this.workerCloseTimeout = options.workerCloseTimeout ?? DEFAULT_WORKER_CLOSE_TIMEOUT;\n this.pendingEvaluationLimit = options.evaluationQueueLimit ?? 1_000;\n\n this.initializeMessage = {\n type: \"Initialize\",\n payload: {\n sdkKey: options.sdkKey,\n sdkIdentity: options.sdkIdentity,\n metaContext: options.metaContext,\n baseUrl: options.baseUrl.toString(),\n evaluationQueueLimit: this.pendingEvaluationLimit,\n initialFlushIntervalDelay: options.initialFlushIntervalDelay ?? 5_000,\n flushIntervalDelay: options.flushIntervalDelay ?? 30_000,\n },\n };\n\n this.visibilityHandler = () => {\n if (document.visibilityState === \"hidden\") {\n this.flush();\n }\n };\n }\n\n private startWorker(): Worker {\n if (this.worker) {\n return this.worker;\n }\n const worker = new Worker(new URL(\"./worker.ts\", import.meta.url), { type: \"module\" });\n worker.onmessage = (message: MessageEvent<TelemetryWorkerResponseEvent>) => {\n this.handleWorkerEvent(message.data);\n };\n worker.postMessage(this.initializeMessage);\n this.worker = worker;\n\n try {\n document.addEventListener(\"visibilitychange\", this.visibilityHandler);\n } catch (error) {\n this.logger.warn(\"[TelemetryEventCollector] Could not configure 'visibilitychange' listener: \", error);\n }\n\n for (const evaluation of this.pendingEvaluations) {\n worker.postMessage(evaluation);\n }\n this.pendingEvaluations.length = 0;\n return worker;\n }\n\n public async updateContext(value: ConfigDirectorContext | undefined) {\n if (this.closePromise) {\n return;\n }\n const updateContextMessage: TelemetryUpdateContextEvent = {\n type: \"UpdateContext\",\n payload: {\n context: value,\n },\n };\n this.startWorker().postMessage(updateContextMessage);\n }\n\n public evaluatedConfig<T extends ConfigValueType>(event: EvaluatedConfigEvent<T>): void {\n const evaluatedConfigMessage: TelemetryEvaluatedConfigEvent = {\n type: \"EvaluatedConfigEvent\",\n payload: this.sanitizeEvaluatedConfigEvent(event),\n };\n if (this.worker) {\n this.worker.postMessage(evaluatedConfigMessage);\n return;\n }\n if (this.pendingEvaluations.length >= this.pendingEvaluationLimit) {\n this.pendingEvaluations.shift();\n }\n this.pendingEvaluations.push(evaluatedConfigMessage);\n }\n\n private sanitizeEvaluatedConfigEvent<T extends ConfigValueType>(\n event: EvaluatedConfigEvent<T>,\n ): EvaluatedConfigEvent<TelemetryValue> {\n return {\n ...event,\n defaultValue: mapToTelemetryValue({ value: event.defaultValue, type: event.type }),\n evaluatedValue: mapToTelemetryValue({\n value: event.evaluatedValue,\n valueId: event.evaluatedValueId,\n type: event.type,\n }),\n };\n }\n\n private handleWorkerEvent(event: TelemetryWorkerResponseEvent) {\n if (event.type === \"Closed\") {\n this.finishClose();\n return;\n }\n if (!event.payload?.level || !event.payload?.message) {\n return;\n }\n const { level, message, args } = event.payload;\n\n switch (level) {\n case \"debug\":\n this.logger.debug(message, ...args);\n break;\n case \"info\":\n this.logger.info(message, ...args);\n break;\n case \"warn\":\n this.logger.warn(message, ...args);\n break;\n case \"error\":\n this.logger.error(message, ...args);\n break;\n }\n }\n\n private flush() {\n const flushMessage: TelemetryFlushEvent = {\n type: \"Flush\",\n };\n this.worker?.postMessage(flushMessage);\n }\n\n public close(): Promise<void> {\n if (this.closePromise) {\n return this.closePromise;\n }\n\n return this.performClose();\n }\n\n private performClose(): Promise<void> {\n const worker = this.worker;\n if (!worker) {\n this.pendingEvaluations.length = 0;\n this.closePromise = Promise.resolve();\n return this.closePromise;\n }\n try {\n document.removeEventListener(\"visibilitychange\", this.visibilityHandler);\n } catch (error) {\n this.logger.warn(\"[WebWorkerTelemetryClient] Could not remove 'visibilitychange' listener: \", error);\n }\n this.closePromise = new Promise<void>((resolve) => {\n this.closeResolve = resolve;\n this.closeTimer = setTimeout(() => {\n this.logger.warn(\n \"[WebWorkerTelemetryClient] Timed out waiting for the telemetry worker to close. Terminating the worker.\",\n );\n this.finishClose();\n }, this.workerCloseTimeout);\n const closeMessage: TelemetryCloseEvent = { type: \"Close\" };\n worker.postMessage(closeMessage);\n });\n return this.closePromise;\n }\n\n private finishClose() {\n clearTimeout(this.closeTimer);\n this.closeTimer = undefined;\n this.worker?.terminate();\n this.closeResolve?.();\n }\n}\n","const BASE62 = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n\nexport const toBase62 = (buf: Uint8Array, length: number): string => {\n const ZERO = BigInt(0);\n const BASE = BigInt(62);\n const hex = Array.from(buf)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n let n = BigInt(\"0x\" + hex);\n let result = \"\";\n while (n > ZERO) {\n result = BASE62[Number(n % BASE)] + result;\n n /= BASE;\n }\n return result.padStart(length, \"0\");\n};\n","export const DIGEST_BYTES = 16;\nexport const BASE62_LENGTH = Math.ceil((DIGEST_BYTES * 8) / Math.log2(62));\n","import { toBase62 } from \"@shared/base62-encoder\";\nimport { DIGEST_BYTES, BASE62_LENGTH } from \"@shared/value-id\";\n\nconst encoder = new TextEncoder();\n\nexport const generateValueId = async (\n v: string | number | boolean | null | undefined,\n): Promise<string | undefined> => {\n if (typeof crypto === \"undefined\" || crypto.subtle == null) {\n return undefined;\n }\n const hashBuffer = await crypto.subtle.digest(\"SHA-256\", encoder.encode(v?.toString() ?? \"\"));\n return toBase62(new Uint8Array(hashBuffer, 0, DIGEST_BYTES), BASE62_LENGTH);\n};\n","import {\n type ConfigDirectorClientOptions,\n type ConfigDirectorClient,\n type IdentifyingSdkOptions,\n createDefaultLogger,\n} from \"@js-client-core/index\";\nimport { WebWorkerTelemetryClient } from \"./telemetry/WebWorkerTelemetryClient\";\nimport { DefaultConfigDirectorClient } from \"@js-client-core/DefaultConfigDirectorClient\";\nimport { defaultUrlFactory, parseUrl } from \"@shared/url\";\nimport { CLIENT_BASE_URL } from \"@shared/constants\";\nimport { generateValueId } from \"./telemetry/value-id-generator\";\n\nexport const createBrowserClient = (\n clientSdkKey: string,\n sdkOptions: IdentifyingSdkOptions,\n clientOptions?: ConfigDirectorClientOptions,\n): ConfigDirectorClient => {\n const logger = clientOptions?.logger ?? createDefaultLogger();\n const baseUrl = parseUrl(clientOptions?.connection?.url) ?? CLIENT_BASE_URL;\n const telemetryClient = new WebWorkerTelemetryClient({\n sdkKey: clientSdkKey,\n sdkIdentity: {\n sdkName: sdkOptions.sdkName,\n sdkVersion: sdkOptions.sdkVersion,\n },\n metaContext: {\n appName: clientOptions?.metadata?.appName,\n appVersion: clientOptions?.metadata?.appVersion,\n },\n logger: logger,\n baseUrl,\n urlFactory: defaultUrlFactory,\n valueIdGenerator: generateValueId,\n });\n\n return new DefaultConfigDirectorClient(telemetryClient, clientSdkKey, sdkOptions, {\n ...clientOptions,\n logger,\n });\n};\n","import type {\n ConfigDirectorClient,\n ConfigDirectorClientOptions,\n ConfigDirectorLoggingLevel,\n ConfigDirectorLogMessageDecorator,\n} from \"@js-browser-client/index\";\nimport { createBrowserClient, createDefaultLogger } from \"@js-browser-client/index\";\n/**\n * Creates a `ConfigDirectorClient` object with the given `clientSdkKey` and optional\n * `clientOptions`. The returned client needs to be initialized before it is ready to serve\n * config values.\n *\n * @param clientSdkKey The client SDK key obtained from the ConfigDirector dashboard\n * @param clientOptions {@link ConfigDirectorClientOptions} options for the client (optional)\n * @returns A {@link ConfigDirectorClient} object\n *\n * @example\n * import { createClient } from \"@configdirector/client-sdk\";\n * const client = createClient(\"YOUR-SDK-KEY\");\n * await client.initialize();\n */\nexport const createClient = (\n clientSdkKey: string,\n clientOptions?: ConfigDirectorClientOptions,\n): ConfigDirectorClient => {\n return createBrowserClient(\n clientSdkKey,\n { sdkName: \"js-client-sdk\", sdkVersion: \"__VERSION__\" },\n clientOptions,\n );\n};\n\nexport const createConsoleLogger = (\n level: ConfigDirectorLoggingLevel,\n messageDecorator?: ConfigDirectorLogMessageDecorator,\n) => {\n return createDefaultLogger(level, messageDecorator);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"configdirector-client.mjs","names":[],"sources":["../../shared/src/telemetry/utils.ts","../../shared/src/telemetry/InvalidContextSubmissions.ts","../../js-browser-client/src/telemetry/WebWorkerTelemetryClient.ts","../../shared/src/base62-encoder.ts","../../shared/src/value-id.ts","../../js-browser-client/src/telemetry/value-id-generator.ts","../../js-browser-client/src/BrowserConfigDirectorClient.ts","../src/api.ts"],"sourcesContent":["import type { ConfigType, ConfigValueType } from \"../types\";\nimport type { ValueIdGenerator, DroppedEvents } from \"./types\";\n\nconst CONFIG_VALUE_MAX_LENGTH = 500;\n\nexport type TelemetryValue = {\n value?: string;\n valueId?: string;\n type?: ConfigType;\n};\n\nexport type TelemetryEvaluationValue<TV extends ConfigValueType> = {\n value: TV;\n valueId?: string | null | undefined;\n type?: ConfigType;\n};\n\nexport const compactTelemetryValue = async (\n tv: TelemetryValue,\n valueIdGenerator: ValueIdGenerator,\n): Promise<TelemetryValue> => {\n if (tv.valueId) {\n return { valueId: tv.valueId };\n }\n if (tv.value) {\n if (tv.value.length <= CONFIG_VALUE_MAX_LENGTH && tv.type != \"json\") {\n return { value: tv.value };\n }\n\n return { valueId: await valueIdGenerator(tv.value) };\n }\n\n return tv;\n};\n\nexport const mapToTelemetryValue = <TV extends ConfigValueType>(\n ev: TelemetryEvaluationValue<TV>,\n): TelemetryValue => {\n if (ev.type === \"json\" || (ev.type == null && typeof ev.value === \"object\")) {\n if (ev.valueId) {\n return { valueId: ev.valueId, type: \"json\" };\n }\n try {\n const json = JSON.stringify(ev.value);\n return { value: json, type: \"json\" };\n } catch {\n return { value: ev.value.toString(), type: \"json\" };\n }\n }\n\n const stringValue = ev.value?.toString();\n if (stringValue && stringValue.length <= CONFIG_VALUE_MAX_LENGTH) {\n return { value: stringValue };\n }\n\n return ev.valueId ? { valueId: ev.valueId } : { value: stringValue };\n};\n\nexport const djb2Hash = (data: string): string => {\n const hash = djb2(new TextEncoder().encode(data));\n return hash.toString(16).padStart(8, \"0\");\n};\n\nconst djb2 = (bytes: Uint8Array): number => {\n let hash = 5381;\n for (let i = 0; i < bytes.length; i++) {\n hash = (hash << 5) + hash + (bytes[i] ?? 0);\n hash = hash >>> 0;\n }\n return hash;\n};\n\nexport const isEventListEmpty = <T extends Record<string | symbol, any[]>>(eventList: T): boolean => {\n const keys = Object.keys(eventList);\n if (keys.length == 0) {\n return true;\n }\n\n for (const key of keys) {\n if ((eventList[key]?.length ?? 0) > 0) {\n return false;\n }\n }\n\n return true;\n};\n\nexport const isDroppedEventsEmpty = (droppedEvents?: DroppedEvents): boolean => {\n if (!droppedEvents) {\n return true;\n }\n const keys = Object.keys(droppedEvents);\n if (keys.length == 0) {\n return true;\n }\n\n for (const key of keys) {\n if ((droppedEvents[key] ?? 0) > 0) {\n return false;\n }\n }\n\n return true;\n};\n","import type { InvalidContextEvent } from \"./telemetry-events\";\nimport type { AggregatedEvent } from \"./types\";\n\nexport const DISTINCT_KIND_SAMPLE_LIMIT = 20;\n\nexport type InvalidContextRecording =\n | \"new-kind-sample\"\n | \"known-kind-sample\"\n | \"first-beyond-sample-limit\"\n | \"beyond-sample-limit\";\n\nexport class InvalidContextSubmissions {\n private readonly countsByKindSample = new Map<string, number>();\n private countBeyondSampleLimit = 0;\n private startTime: Date | undefined;\n\n public record(kindSample: string): InvalidContextRecording {\n this.startTime ??= new Date();\n return this.add(kindSample, 1);\n }\n\n public absorb(entries: AggregatedEvent<InvalidContextEvent>[]): void {\n for (const entry of entries) {\n if (this.startTime === undefined || entry.startTime < this.startTime) {\n this.startTime = entry.startTime;\n }\n this.add(entry.event.kindSample, entry.count);\n }\n }\n\n private add(kindSample: string | undefined, count: number): InvalidContextRecording {\n if (kindSample !== undefined) {\n const knownCount = this.countsByKindSample.get(kindSample);\n if (knownCount !== undefined) {\n this.countsByKindSample.set(kindSample, knownCount + count);\n return \"known-kind-sample\";\n }\n if (this.countsByKindSample.size < DISTINCT_KIND_SAMPLE_LIMIT) {\n this.countsByKindSample.set(kindSample, count);\n return \"new-kind-sample\";\n }\n }\n const firstBeyondSampleLimit = this.countBeyondSampleLimit === 0;\n this.countBeyondSampleLimit += count;\n return firstBeyondSampleLimit ? \"first-beyond-sample-limit\" : \"beyond-sample-limit\";\n }\n\n public takeSnapshot(): AggregatedEvent<InvalidContextEvent>[] {\n const endTime = new Date();\n const startTime = this.startTime ?? endTime;\n const entryOf = (count: number, kindSample?: string): AggregatedEvent<InvalidContextEvent> => ({\n startTime,\n endTime,\n count,\n event: { reason: \"invalid-context-kind\", kindSample },\n });\n const sampledEntries = Array.from(this.countsByKindSample, ([kindSample, count]) =>\n entryOf(count, kindSample),\n );\n const entries =\n this.countBeyondSampleLimit > 0\n ? [...sampledEntries, entryOf(this.countBeyondSampleLimit)]\n : sampledEntries;\n this.clear();\n return entries;\n }\n\n private clear() {\n this.countsByKindSample.clear();\n this.countBeyondSampleLimit = 0;\n this.startTime = undefined;\n }\n}\n","import { type TelemetryEventCollectorOptions } from \"@shared/telemetry/TelemetryEventCollector\";\nimport type { ConfigDirectorContext, ConfigDirectorLogger, ConfigValueType } from \"@js-client-core/types\";\nimport type { TelemetryClient, EvaluatedConfigEvent } from \"@js-client-core/telemetry\";\nimport type {\n TelemetryCloseEvent,\n TelemetryEvaluatedConfigEvent,\n TelemetryFlushEvent,\n TelemetryInitializeEvent,\n TelemetryInvalidContextSubmissionsEvent,\n TelemetryUpdateContextEvent,\n TelemetryWorkerResponseEvent,\n} from \"./types\";\nimport type { TelemetryValue } from \"@shared/telemetry/utils\";\nimport { mapToTelemetryValue } from \"@shared/telemetry/utils\";\nimport { InvalidContextSubmissions } from \"@shared/telemetry/InvalidContextSubmissions\";\n\nconst DEFAULT_WORKER_CLOSE_TIMEOUT = 6_000;\n\nexport type WebWorkerTelemetryClientOptions = TelemetryEventCollectorOptions & {\n workerCloseTimeout?: number;\n};\n\nexport class WebWorkerTelemetryClient implements TelemetryClient {\n private readonly logger: ConfigDirectorLogger;\n private readonly initializeMessage: TelemetryInitializeEvent;\n private worker: Worker | undefined;\n private turnedOff = false;\n private readonly pendingEvaluations: TelemetryEvaluatedConfigEvent[] = [];\n private readonly pendingEvaluationLimit: number;\n private readonly pendingInvalidContextSubmissions = new InvalidContextSubmissions();\n private closePromise: Promise<void> | undefined;\n private closeResolve: (() => void) | undefined;\n private closeTimer: ReturnType<typeof setTimeout> | undefined;\n private readonly workerCloseTimeout: number;\n private readonly visibilityHandler: () => void;\n\n constructor(options: WebWorkerTelemetryClientOptions) {\n this.logger = options.logger;\n this.workerCloseTimeout = options.workerCloseTimeout ?? DEFAULT_WORKER_CLOSE_TIMEOUT;\n this.pendingEvaluationLimit = options.evaluationQueueLimit ?? 1_000;\n\n this.initializeMessage = {\n type: \"Initialize\",\n payload: {\n sdkKey: options.sdkKey,\n sdkIdentity: options.sdkIdentity,\n metaContext: options.metaContext,\n baseUrl: options.baseUrl.toString(),\n evaluationQueueLimit: this.pendingEvaluationLimit,\n initialFlushIntervalDelay: options.initialFlushIntervalDelay ?? 5_000,\n flushIntervalDelay: options.flushIntervalDelay ?? 30_000,\n },\n };\n\n this.visibilityHandler = () => {\n if (document.visibilityState === \"hidden\") {\n this.flush();\n }\n };\n }\n\n private createWorkerOrTurnOff(): Worker | undefined {\n if (typeof Worker === \"undefined\") {\n this.turnOff(\n \"[WebWorkerTelemetryClient] Telemetry is off because Web Workers are not available in this environment.\",\n );\n return undefined;\n }\n try {\n return new Worker(new URL(\"./worker.ts\", import.meta.url), { type: \"module\" });\n } catch (error) {\n this.turnOff(\n \"[WebWorkerTelemetryClient] Telemetry is off because the telemetry Web Worker could not be created: \",\n error,\n );\n return undefined;\n }\n }\n\n private turnOff(warning: string, ...details: unknown[]) {\n this.turnedOff = true;\n this.pendingEvaluations.length = 0;\n this.logger.warn(warning, ...details);\n }\n\n private startWorker(): Worker | undefined {\n if (this.worker || this.turnedOff) {\n return this.worker;\n }\n const worker = this.createWorkerOrTurnOff();\n if (!worker) {\n return undefined;\n }\n worker.onmessage = (message: MessageEvent<TelemetryWorkerResponseEvent>) => {\n this.handleWorkerEvent(message.data);\n };\n worker.postMessage(this.initializeMessage);\n this.worker = worker;\n\n try {\n document.addEventListener(\"visibilitychange\", this.visibilityHandler);\n } catch (error) {\n this.logger.warn(\"[TelemetryEventCollector] Could not configure 'visibilitychange' listener: \", error);\n }\n\n for (const evaluation of this.pendingEvaluations) {\n worker.postMessage(evaluation);\n }\n this.pendingEvaluations.length = 0;\n this.forwardInvalidContextSubmissions(worker);\n return worker;\n }\n\n private forwardInvalidContextSubmissions(worker: Worker | undefined) {\n if (!worker) {\n return;\n }\n const entries = this.pendingInvalidContextSubmissions.takeSnapshot();\n if (entries.length === 0) {\n return;\n }\n const submissionsMessage: TelemetryInvalidContextSubmissionsEvent = {\n type: \"InvalidContextSubmissions\",\n payload: { entries },\n };\n worker.postMessage(submissionsMessage);\n }\n\n public async updateContext(value: ConfigDirectorContext | undefined) {\n if (this.closePromise) {\n return;\n }\n const updateContextMessage: TelemetryUpdateContextEvent = {\n type: \"UpdateContext\",\n payload: {\n context: value,\n },\n };\n this.startWorker()?.postMessage(updateContextMessage);\n }\n\n public evaluatedConfig<T extends ConfigValueType>(event: EvaluatedConfigEvent<T>): void {\n if (this.turnedOff) {\n return;\n }\n const evaluatedConfigMessage: TelemetryEvaluatedConfigEvent = {\n type: \"EvaluatedConfigEvent\",\n payload: this.sanitizeEvaluatedConfigEvent(event),\n };\n if (this.worker) {\n this.worker.postMessage(evaluatedConfigMessage);\n return;\n }\n if (this.pendingEvaluations.length >= this.pendingEvaluationLimit) {\n this.pendingEvaluations.shift();\n }\n this.pendingEvaluations.push(evaluatedConfigMessage);\n }\n\n public recordInvalidContext(kindSample: string): void {\n this.pendingInvalidContextSubmissions.record(kindSample);\n this.forwardInvalidContextSubmissions(this.worker);\n }\n\n private sanitizeEvaluatedConfigEvent<T extends ConfigValueType>(\n event: EvaluatedConfigEvent<T>,\n ): EvaluatedConfigEvent<TelemetryValue> {\n return {\n ...event,\n defaultValue: mapToTelemetryValue({ value: event.defaultValue, type: event.type }),\n evaluatedValue: mapToTelemetryValue({\n value: event.evaluatedValue,\n valueId: event.evaluatedValueId,\n type: event.type,\n }),\n };\n }\n\n private handleWorkerEvent(event: TelemetryWorkerResponseEvent) {\n if (event.type === \"Closed\") {\n this.finishClose();\n return;\n }\n if (!event.payload?.level || !event.payload?.message) {\n return;\n }\n const { level, message, args } = event.payload;\n\n switch (level) {\n case \"debug\":\n this.logger.debug(message, ...args);\n break;\n case \"info\":\n this.logger.info(message, ...args);\n break;\n case \"warn\":\n this.logger.warn(message, ...args);\n break;\n case \"error\":\n this.logger.error(message, ...args);\n break;\n }\n }\n\n private flush() {\n const flushMessage: TelemetryFlushEvent = {\n type: \"Flush\",\n };\n this.worker?.postMessage(flushMessage);\n }\n\n public close(): Promise<void> {\n if (this.closePromise) {\n return this.closePromise;\n }\n\n return this.performClose();\n }\n\n private performClose(): Promise<void> {\n const worker = this.worker;\n if (!worker) {\n this.pendingEvaluations.length = 0;\n this.closePromise = Promise.resolve();\n return this.closePromise;\n }\n try {\n document.removeEventListener(\"visibilitychange\", this.visibilityHandler);\n } catch (error) {\n this.logger.warn(\"[WebWorkerTelemetryClient] Could not remove 'visibilitychange' listener: \", error);\n }\n this.closePromise = new Promise<void>((resolve) => {\n this.closeResolve = resolve;\n this.closeTimer = setTimeout(() => {\n this.logger.warn(\n \"[WebWorkerTelemetryClient] Timed out waiting for the telemetry worker to close. Terminating the worker.\",\n );\n this.finishClose();\n }, this.workerCloseTimeout);\n const closeMessage: TelemetryCloseEvent = { type: \"Close\" };\n worker.postMessage(closeMessage);\n });\n return this.closePromise;\n }\n\n private finishClose() {\n clearTimeout(this.closeTimer);\n this.closeTimer = undefined;\n this.worker?.terminate();\n this.closeResolve?.();\n }\n}\n","const BASE62 = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n\nexport const toBase62 = (buf: Uint8Array, length: number): string => {\n const ZERO = BigInt(0);\n const BASE = BigInt(62);\n const hex = Array.from(buf)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n let n = BigInt(\"0x\" + hex);\n let result = \"\";\n while (n > ZERO) {\n result = BASE62[Number(n % BASE)] + result;\n n /= BASE;\n }\n return result.padStart(length, \"0\");\n};\n","export const DIGEST_BYTES = 16;\nexport const BASE62_LENGTH = Math.ceil((DIGEST_BYTES * 8) / Math.log2(62));\n","import { toBase62 } from \"@shared/base62-encoder\";\nimport { DIGEST_BYTES, BASE62_LENGTH } from \"@shared/value-id\";\n\nconst encoder = new TextEncoder();\n\nexport const generateValueId = async (\n v: string | number | boolean | null | undefined,\n): Promise<string | undefined> => {\n if (typeof crypto === \"undefined\" || crypto.subtle == null) {\n return undefined;\n }\n const hashBuffer = await crypto.subtle.digest(\"SHA-256\", encoder.encode(v?.toString() ?? \"\"));\n return toBase62(new Uint8Array(hashBuffer, 0, DIGEST_BYTES), BASE62_LENGTH);\n};\n","import {\n type ConfigDirectorClientOptions,\n type ConfigDirectorClient,\n type IdentifyingSdkOptions,\n createDefaultLogger,\n} from \"@js-client-core/index\";\nimport { WebWorkerTelemetryClient } from \"./telemetry/WebWorkerTelemetryClient\";\nimport { DefaultConfigDirectorClient } from \"@js-client-core/DefaultConfigDirectorClient\";\nimport { defaultUrlFactory, parseUrl } from \"@shared/url\";\nimport { CLIENT_BASE_URL } from \"@shared/constants\";\nimport { generateValueId } from \"./telemetry/value-id-generator\";\n\nexport const createBrowserClient = (\n clientSdkKey: string,\n sdkOptions: IdentifyingSdkOptions,\n clientOptions?: ConfigDirectorClientOptions,\n): ConfigDirectorClient => {\n const logger = clientOptions?.logger ?? createDefaultLogger();\n const baseUrl = parseUrl(clientOptions?.connection?.url) ?? CLIENT_BASE_URL;\n const telemetryClient = new WebWorkerTelemetryClient({\n sdkKey: clientSdkKey,\n sdkIdentity: {\n sdkName: sdkOptions.sdkName,\n sdkVersion: sdkOptions.sdkVersion,\n },\n metaContext: {\n appName: clientOptions?.metadata?.appName,\n appVersion: clientOptions?.metadata?.appVersion,\n },\n logger: logger,\n baseUrl,\n urlFactory: defaultUrlFactory,\n valueIdGenerator: generateValueId,\n });\n\n return new DefaultConfigDirectorClient(telemetryClient, clientSdkKey, sdkOptions, {\n ...clientOptions,\n logger,\n });\n};\n","import type {\n ConfigDirectorClient,\n ConfigDirectorClientOptions,\n ConfigDirectorLoggingLevel,\n ConfigDirectorLogMessageDecorator,\n} from \"@js-browser-client/index\";\nimport { createBrowserClient, createDefaultLogger } from \"@js-browser-client/index\";\n/**\n * Creates a `ConfigDirectorClient` object with the given `clientSdkKey` and optional\n * `clientOptions`. The returned client needs to be initialized before it is ready to serve\n * config values.\n *\n * @param clientSdkKey The client SDK key obtained from the ConfigDirector dashboard\n * @param clientOptions {@link ConfigDirectorClientOptions} options for the client (optional)\n * @returns A {@link ConfigDirectorClient} object\n *\n * @example\n * import { createClient } from \"@configdirector/client-sdk\";\n * const client = createClient(\"YOUR-SDK-KEY\");\n * await client.initialize();\n */\nexport const createClient = (\n clientSdkKey: string,\n clientOptions?: ConfigDirectorClientOptions,\n): ConfigDirectorClient => {\n return createBrowserClient(\n clientSdkKey,\n { sdkName: \"js-client-sdk\", sdkVersion: \"__VERSION__\" },\n clientOptions,\n );\n};\n\nexport const createConsoleLogger = (\n level: ConfigDirectorLoggingLevel,\n messageDecorator?: ConfigDirectorLogMessageDecorator,\n) => {\n return createDefaultLogger(level, messageDecorator);\n};\n"],"mappings":"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"}
@@ -33,27 +33,40 @@ type ConfigEvaluation = {
33
33
  context?: ConfigDirectorContext;
34
34
  };
35
35
  /**
36
- * The user's context to be sent to ConfigDirector. This context will be used for targeting
37
- * rules evaluation.
36
+ * The context to be sent to ConfigDirector: the user or other entity, such as a device or an
37
+ * organization, that configs are evaluated for. This context will be used for targeting rules
38
+ * evaluation.
38
39
  */
39
40
  type ConfigDirectorContext = {
40
41
  /**
41
- * The user's identifier. This should be a value that uniquely identifies an application
42
- * user.
42
+ * The context's identifier. This should be a value that uniquely identifies the user or other
43
+ * entity among contexts of the same {@link kind}.
43
44
  * In the case of anonymous users, you could generate a UUID or alternatively not provide
44
45
  * the {@link id} and the SDK will generate a random UUID. However, keep in mind that this
45
- * value is used for segmenting users in percentage rollouts, and changes to the {@link id}
46
- * could result in the user being assigned to a different percentile.
46
+ * value is used for segmenting contexts in percentage rollouts, and changes to the {@link id}
47
+ * could result in the context being assigned to a different percentile.
47
48
  */
48
49
  id?: string;
49
50
  /**
50
- * The user's display name. This will be shown in the ConfigDirector dashboard and may be
51
+ * The kind of entity this context represents, such as `user`, `device`, `organization`,
52
+ * `tenant` or `listing`. You can use any other kind that fits your application. A kind has 1
53
+ * to 64 characters, using only lowercase letters (`a`-`z`), digits, `-` and `_`. It is used
54
+ * exactly as given, without trimming or changing its case.
55
+ *
56
+ * A context with an invalid kind is ignored as a whole: the SDK logs an error and evaluates
57
+ * as if no context had been provided.
58
+ *
59
+ * @default "user"
60
+ */
61
+ kind?: string;
62
+ /**
63
+ * The context's display name. This will be shown in the ConfigDirector dashboard and may be
51
64
  * used for targeting rules.
52
65
  */
53
66
  name?: string;
54
67
  /**
55
- * Any arbitrary traits for the current user. They will be shown in the ConfigDirector
56
- * dashboard and may be used for targeting rules.
68
+ * Any arbitrary traits for the context. They will be shown in the ConfigDirector dashboard
69
+ * and may be used for targeting rules.
57
70
  */
58
71
  traits?: {
59
72
  [key: string]: unknown;
@@ -202,17 +215,25 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
202
215
  * not attempt to re-connect and an error will be logged to the console or the provided
203
216
  * logger.
204
217
  *
205
- * @param context The current user's context to be used for evaluating targeting rules (optional).
218
+ * A context whose {@link ConfigDirectorContext.kind} is invalid is ignored as a whole: an error is
219
+ * logged, and the client connects as if no context had been provided.
220
+ *
221
+ * @param context The current context to be used for evaluating targeting rules (optional).
206
222
  */
207
223
  initialize(context?: ConfigDirectorContext): Promise<void>;
208
224
  /**
209
- * Updates the user's context and re-evaluates all config and flag values based on the new context.
225
+ * Updates the context and re-evaluates all config and flag values based on the new context.
226
+ *
227
+ * A context whose {@link ConfigDirectorContext.kind} is invalid is ignored as a whole: an error is
228
+ * logged, the previous context is no longer used, and the client connects as if no context had been
229
+ * provided.
210
230
  *
211
- * @param context The current user's context to be used for evaluating targeting rules (required).
231
+ * @param context The current context to be used for evaluating targeting rules (required).
212
232
  */
213
233
  updateContext(context: ConfigDirectorContext): Promise<void>;
214
234
  /**
215
- * Returns the current context the client is configured with, or undefined if there is no context.
235
+ * Returns the current context the client is configured with, or undefined if there is no context,
236
+ * including after a context with an invalid kind was ignored.
216
237
  *
217
238
  * Note that the value returned does not updated immediately after calling {@link updateContext}. Only
218
239
  * once the underlying connection was successful or timed out, the current context is updated. This is
@@ -304,4 +325,4 @@ declare class ConfigDirectorValidationError extends Error {
304
325
  }
305
326
  //#endregion
306
327
  export { ConfigDirectorContext as a, ConfigDirectorLoggingLevel as c, ConfigDirectorClientOptions as i, ConfigValueType as l, ConfigDirectorValidationError as n, ConfigDirectorLogMessageDecorator as o, ConfigDirectorClient as r, ConfigDirectorLogger as s, ConfigDirectorConnectionError as t };
307
- //# sourceMappingURL=errors-zagImFi0.d.mts.map
328
+ //# sourceMappingURL=errors-DTlTJsJh.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-DTlTJsJh.d.cts","names":[],"sources":["../../js-client-core/src/Emitter.ts","../../shared/src/types.ts","../../js-client-core/src/types.ts","../../shared/src/errors.ts"],"mappings":"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"}
@@ -33,27 +33,40 @@ type ConfigEvaluation = {
33
33
  context?: ConfigDirectorContext;
34
34
  };
35
35
  /**
36
- * The user's context to be sent to ConfigDirector. This context will be used for targeting
37
- * rules evaluation.
36
+ * The context to be sent to ConfigDirector: the user or other entity, such as a device or an
37
+ * organization, that configs are evaluated for. This context will be used for targeting rules
38
+ * evaluation.
38
39
  */
39
40
  type ConfigDirectorContext = {
40
41
  /**
41
- * The user's identifier. This should be a value that uniquely identifies an application
42
- * user.
42
+ * The context's identifier. This should be a value that uniquely identifies the user or other
43
+ * entity among contexts of the same {@link kind}.
43
44
  * In the case of anonymous users, you could generate a UUID or alternatively not provide
44
45
  * the {@link id} and the SDK will generate a random UUID. However, keep in mind that this
45
- * value is used for segmenting users in percentage rollouts, and changes to the {@link id}
46
- * could result in the user being assigned to a different percentile.
46
+ * value is used for segmenting contexts in percentage rollouts, and changes to the {@link id}
47
+ * could result in the context being assigned to a different percentile.
47
48
  */
48
49
  id?: string;
49
50
  /**
50
- * The user's display name. This will be shown in the ConfigDirector dashboard and may be
51
+ * The kind of entity this context represents, such as `user`, `device`, `organization`,
52
+ * `tenant` or `listing`. You can use any other kind that fits your application. A kind has 1
53
+ * to 64 characters, using only lowercase letters (`a`-`z`), digits, `-` and `_`. It is used
54
+ * exactly as given, without trimming or changing its case.
55
+ *
56
+ * A context with an invalid kind is ignored as a whole: the SDK logs an error and evaluates
57
+ * as if no context had been provided.
58
+ *
59
+ * @default "user"
60
+ */
61
+ kind?: string;
62
+ /**
63
+ * The context's display name. This will be shown in the ConfigDirector dashboard and may be
51
64
  * used for targeting rules.
52
65
  */
53
66
  name?: string;
54
67
  /**
55
- * Any arbitrary traits for the current user. They will be shown in the ConfigDirector
56
- * dashboard and may be used for targeting rules.
68
+ * Any arbitrary traits for the context. They will be shown in the ConfigDirector dashboard
69
+ * and may be used for targeting rules.
57
70
  */
58
71
  traits?: {
59
72
  [key: string]: unknown;
@@ -202,17 +215,25 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
202
215
  * not attempt to re-connect and an error will be logged to the console or the provided
203
216
  * logger.
204
217
  *
205
- * @param context The current user's context to be used for evaluating targeting rules (optional).
218
+ * A context whose {@link ConfigDirectorContext.kind} is invalid is ignored as a whole: an error is
219
+ * logged, and the client connects as if no context had been provided.
220
+ *
221
+ * @param context The current context to be used for evaluating targeting rules (optional).
206
222
  */
207
223
  initialize(context?: ConfigDirectorContext): Promise<void>;
208
224
  /**
209
- * Updates the user's context and re-evaluates all config and flag values based on the new context.
225
+ * Updates the context and re-evaluates all config and flag values based on the new context.
226
+ *
227
+ * A context whose {@link ConfigDirectorContext.kind} is invalid is ignored as a whole: an error is
228
+ * logged, the previous context is no longer used, and the client connects as if no context had been
229
+ * provided.
210
230
  *
211
- * @param context The current user's context to be used for evaluating targeting rules (required).
231
+ * @param context The current context to be used for evaluating targeting rules (required).
212
232
  */
213
233
  updateContext(context: ConfigDirectorContext): Promise<void>;
214
234
  /**
215
- * Returns the current context the client is configured with, or undefined if there is no context.
235
+ * Returns the current context the client is configured with, or undefined if there is no context,
236
+ * including after a context with an invalid kind was ignored.
216
237
  *
217
238
  * Note that the value returned does not updated immediately after calling {@link updateContext}. Only
218
239
  * once the underlying connection was successful or timed out, the current context is updated. This is
@@ -304,4 +325,4 @@ declare class ConfigDirectorValidationError extends Error {
304
325
  }
305
326
  //#endregion
306
327
  export { ConfigDirectorContext as a, ConfigDirectorLoggingLevel as c, ConfigDirectorClientOptions as i, ConfigValueType as l, ConfigDirectorValidationError as n, ConfigDirectorLogMessageDecorator as o, ConfigDirectorClient as r, ConfigDirectorLogger as s, ConfigDirectorConnectionError as t };
307
- //# sourceMappingURL=errors-BOgYEeJ6.d.cts.map
328
+ //# sourceMappingURL=errors-T_hDavJF.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-T_hDavJF.d.mts","names":[],"sources":["../../js-client-core/src/Emitter.ts","../../shared/src/types.ts","../../js-client-core/src/types.ts","../../shared/src/errors.ts"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@configdirector/client-sdk",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Javascript client SDK for ConfigDirector. ConfigDirector is a remote configuration and feature flag service.",
5
5
  "license": "MIT",
6
6
  "author": "ConfigDirector",