@configdirector/react-web-sdk 1.5.0 → 1.6.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.
- package/README.md +34 -0
- package/dist/configdirector-react-client-testing.cjs +1 -0
- package/dist/configdirector-react-client-testing.d.cts +158 -0
- package/dist/configdirector-react-client-testing.d.cts.map +1 -0
- package/dist/configdirector-react-client-testing.d.mts +158 -0
- package/dist/configdirector-react-client-testing.d.mts.map +1 -0
- package/dist/configdirector-react-client-testing.mjs +2 -0
- package/dist/configdirector-react-client-testing.mjs.map +1 -0
- package/dist/configdirector-react-client.cjs +1 -5
- package/dist/configdirector-react-client.d.cts +47 -207
- package/dist/configdirector-react-client.d.cts.map +1 -1
- package/dist/configdirector-react-client.d.mts +47 -207
- package/dist/configdirector-react-client.d.mts.map +1 -1
- package/dist/configdirector-react-client.mjs +1 -5
- package/dist/configdirector-react-client.mjs.map +1 -1
- package/dist/installedTestClient-BqZAvzY_.mjs +6 -0
- package/dist/installedTestClient-BqZAvzY_.mjs.map +1 -0
- package/dist/installedTestClient-DozdiR3i.cjs +5 -0
- package/dist/types-3Pw7bIUn.d.cts +210 -0
- package/dist/types-3Pw7bIUn.d.cts.map +1 -0
- package/dist/types-CU9VmelO.d.mts +210 -0
- package/dist/types-CU9VmelO.d.mts.map +1 -0
- package/package.json +19 -2
package/README.md
CHANGED
|
@@ -30,6 +30,40 @@ function App() {
|
|
|
30
30
|
|
|
31
31
|
Full details are in the [official documentation](https://docs.configdirector.com/sdks/browser/react).
|
|
32
32
|
|
|
33
|
+
## Test your code
|
|
34
|
+
|
|
35
|
+
`@configdirector/react-web-sdk/testing` creates a real client connected to an in-memory server that
|
|
36
|
+
your test controls. Render the production `ConfigDirectorProvider` with its `client` prop, or install
|
|
37
|
+
the test client for components that render their own provider.
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { act, render, screen } from "@testing-library/react";
|
|
41
|
+
import { ConfigDirectorProvider } from "@configdirector/react-web-sdk";
|
|
42
|
+
import { createTestClient, installTestClient } from "@configdirector/react-web-sdk/testing";
|
|
43
|
+
|
|
44
|
+
const testClient = createTestClient({ values: { "dark-mode": true } });
|
|
45
|
+
|
|
46
|
+
render(
|
|
47
|
+
<ConfigDirectorProvider client={testClient.client}>
|
|
48
|
+
<App />
|
|
49
|
+
</ConfigDirectorProvider>,
|
|
50
|
+
);
|
|
51
|
+
expect(await screen.findByText("dark-mode: true")).toBeInTheDocument();
|
|
52
|
+
|
|
53
|
+
act(() => testClient.setValue("dark-mode", false));
|
|
54
|
+
expect(screen.getByText("dark-mode: false")).toBeInTheDocument();
|
|
55
|
+
|
|
56
|
+
const uninstall = installTestClient(testClient);
|
|
57
|
+
render(<AppWithItsOwnProvider />);
|
|
58
|
+
uninstall();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The provider renders its children before initialization completes, so the first assertion uses
|
|
62
|
+
`findBy…`. Wrap `setValue`, `removeValue`, and `replaceValues` in `act()`. The test client also holds
|
|
63
|
+
or fails `initialize` and `updateContext`, and records every context in `contextUpdates`. A provider
|
|
64
|
+
never disposes a client it was given, removes its handlers when it unmounts, and does not initialize
|
|
65
|
+
a client that is ready or initializing.
|
|
66
|
+
|
|
33
67
|
## Documentation
|
|
34
68
|
|
|
35
69
|
Refer to the [official documentation for the React SDK](https://docs.configdirector.com/sdks/browser/react).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./installedTestClient-DozdiR3i.cjs`),t=(t,r)=>{if(t.trim().length==0)throw new e.l(`Invalid config key. The key of a test value cannot be empty.`);return n(t,r)},n=(t,n)=>{switch(typeof n){case`boolean`:return{type:`boolean`,value:n?`true`:`false`};case`number`:return r(t,n);case`string`:return{type:`string`,value:n};case`object`:if(n!==null)return a(t,n,``),{type:`json`,value:JSON.stringify(n)}}throw new e.l(`Invalid test value for '${t}': ${c(n)}. Use a boolean, a number, a string, a plain object, or an array.`)},r=(t,n)=>{if(!Number.isFinite(n))throw new e.l(`Invalid test value for '${t}': ${c(n)} is not a finite number.`);if(Number.isInteger(n)){if(!Number.isSafeInteger(n))throw new e.l(`Invalid test value for '${t}': ${n} is not a safe integer, so the SDK could not read it back exactly.`);return{type:`integer`,value:String(n)}}return{type:`float`,value:i(n)}},i=e=>{let t=String(e),n=t.indexOf(`e-`);if(n<0)return t;let r=t.startsWith(`-`)?`-`:``,[i=``,a=``]=t.slice(r.length,n).split(`.`),o=Number(t.slice(n+2))-i.length;return`${r}0.${`0`.repeat(o)}${i}${a}`},a=(e,t,n)=>{if(!(t===null||typeof t==`boolean`||typeof t==`string`)){if(typeof t==`number`){if(!Number.isFinite(t))throw o(e,n,`${c(t)} is not a finite number`);return}if(Array.isArray(t)){t.forEach((t,r)=>a(e,t,`${n}[${r}]`));return}if(s(t)){for(let[r,i]of Object.entries(t))a(e,i,n?`${n}.${r}`:r);return}throw o(e,n,`${c(t)} cannot be encoded as JSON`)}},o=(t,n,r)=>new e.l(`Invalid test value for '${t}'${n?` at '${n}'`:``}: ${r}. JSON contents can hold booleans, finite numbers, strings, null, arrays, and plain objects.`),s=e=>{if(typeof e!=`object`||!e)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null},c=e=>e===null?`null`:typeof e==`function`?`a function`:typeof e==`object`?`a ${e.constructor?.name??`object`} instance`:typeof e==`symbol`?`a symbol`:String(e),l=(e,n,r)=>{let i=t(e,n);return{id:`test-config:${e}`,key:e,type:i.type,value:i.value,valueId:`test-value:${e}:${r}`}},u={environmentId:`test-environment`,projectId:`test-project`},d=e=>{switch(e){case`initialization`:return`initialize`;case`context update`:return`updateContext`;default:return}};var f=class{constructor(t,n){this.logger=t,this.emitter=new e.u,this.configs=new Map,this.recordedContexts=[],this.pendingDeliveries=[],this.armed={initialize:`nothing`,updateContext:`nothing`},this.connected=!1,this.delivering=!1,this.revision=0,this.attemptGeneration=0,this.on=(e,t)=>this.emitter.on(e,t),this.off=(e,t)=>this.emitter.off(e,t),this.store(n)}get contextUpdates(){return[...this.recordedContexts]}setValue(e,t){let n=l(e,t,++this.revision);this.configs.set(e,n),this.deliverIfConnected({...u,kind:`delta`,configs:{[e]:n}})}removeValue(e){this.configs.delete(e),this.deliverIfConnected(this.fullUpdate())}replaceValues(e){let t=this.encodeAll(e);this.configs.clear(),t.forEach((e,t)=>this.configs.set(t,e)),this.armed.initialize=`nothing`,this.armed.updateContext=`nothing`,this.deliverIfConnected(this.fullUpdate())}hold(e){this.armed[e]=`hold`}complete(e){this.pendingHold?.attempt===e?this.pendingHold.complete():this.armed[e]===`hold`&&(this.armed[e]=`nothing`)}fail(e){this.pendingHold?.attempt===e?this.pendingHold.fail():this.armed[e]=`failure`}connect(e,t,n){this.endAttempt();let r=d(n);if(!r)return this.connectNow();this.recordedContexts.push(e);let i=this.armed[r];switch(this.armed[r]=`nothing`,i){case`failure`:return this.failNow(n);case`hold`:return this.holdNow(r,n,t);default:return this.connectNow()}}clear(){this.emitter.clear()}close(){this.endAttempt()}dispose(){this.close(),this.clear()}endAttempt(){this.attemptGeneration++,this.connected=!1,this.pendingHold?.end()}connectNow(){let e=Promise.resolve(this);return this.scheduleFirstDelivery(),e}holdNow(e,t,n){return new Promise((r,i)=>{let a=setTimeout(()=>this.pendingHold?.end(),n),o=()=>{clearTimeout(a),this.pendingHold=void 0};this.pendingHold={attempt:e,end:()=>{o(),r(this)},complete:()=>{o(),r(this),this.scheduleFirstDelivery()},fail:()=>{o();let e=this.fatalError(t);this.emitter.emit(`connectionError`,e),i(e)}}})}failNow(e){let t=this.fatalError(e);return this.emitter.emit(`connectionError`,t),Promise.reject(t)}fatalError(t){return new e.c(`Connection failed with status: 401. Error: the test client failed this ${t}. This is an unrecoverable error, will not attempt to reconnect.`,401)}scheduleFirstDelivery(){let e=this.attemptGeneration;Promise.resolve().then(()=>{}).then(()=>{e===this.attemptGeneration&&(this.connected=!0,this.deliver(this.fullUpdate()))})}deliverIfConnected(e){this.connected&&this.deliver(e)}deliver(e){if(this.pendingDeliveries.push(e),!this.delivering){this.delivering=!0;try{let e=this.pendingDeliveries.shift();for(;e;)this.emitCaught(e),e=this.pendingDeliveries.shift()}finally{this.delivering=!1}}}emitCaught(e){try{this.emitter.emit(`configSetReceived`,e)}catch(e){this.logger.error(`[InMemoryConnection] Error dispatching a config update from the test client: `,e)}}fullUpdate(){return{...u,kind:`full`,configs:Object.fromEntries(this.configs)}}store(e){this.encodeAll(e).forEach((e,t)=>this.configs.set(t,e))}encodeAll(e){let t=new Map;for(let[n,r]of Object.entries(e))t.set(n,l(n,r,++this.revision));return t}};const p={updateContext:async()=>{},evaluatedConfig:()=>{},close:async()=>{}};var m=class{constructor(e,t){this.client=e,this.connection=t}setValue(e,t){this.connection.setValue(e,t)}removeValue(e){this.connection.removeValue(e)}replaceValues(e){this.connection.replaceValues(e)}holdInitialization(){this.connection.hold(`initialize`)}completeInitialization(){this.connection.complete(`initialize`)}failInitialization(){this.connection.fail(`initialize`)}holdContextUpdate(){this.connection.hold(`updateContext`)}completeContextUpdate(){this.connection.complete(`updateContext`)}failContextUpdate(){this.connection.fail(`updateContext`)}get contextUpdates(){return this.connection.contextUpdates}};const h=(t,n)=>{let r=n?.logger??e.s(),i=new f(r,n?.values??{});return new m(new e.r(p,`test-client`,t,{logger:r,connection:{timeout:n?.timeout}},{transport:i}),i)},g=t=>{let n=globalThis,r=n[e.t];return n[e.t]=t.client,()=>{n[e.t]=r}},_=e=>h({sdkName:`react-web-sdk`,sdkVersion:`1.6.0`},e);exports.ConfigDirectorConnectionError=e.c,exports.ConfigDirectorValidationError=e.l,exports.createTestClient=_,exports.installTestClient=g;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { i as ConfigDirectorLogger, n as ConfigDirectorClient, r as ConfigDirectorContext } from "./types-3Pw7bIUn.cjs";
|
|
2
|
+
|
|
3
|
+
//#region ../shared/src/errors.d.ts
|
|
4
|
+
declare class ConfigDirectorConnectionError extends Error {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly status?: number;
|
|
7
|
+
constructor(message: string, status?: number);
|
|
8
|
+
}
|
|
9
|
+
declare class ConfigDirectorValidationError extends Error {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region ../shared/src/testing/values.d.ts
|
|
15
|
+
type TestJsonValue = boolean | number | string | null | TestJsonValue[] | {
|
|
16
|
+
[key: string]: TestJsonValue;
|
|
17
|
+
};
|
|
18
|
+
type TestValue = boolean | number | string | TestJsonValue[] | {
|
|
19
|
+
[key: string]: TestJsonValue;
|
|
20
|
+
};
|
|
21
|
+
type TestValues = Record<string, TestValue>;
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region ../js-client-core/src/testing/TestClient.d.ts
|
|
24
|
+
/**
|
|
25
|
+
* Options for {@link createTestClient}. Every option is optional.
|
|
26
|
+
*/
|
|
27
|
+
type TestClientOptions = {
|
|
28
|
+
/**
|
|
29
|
+
* The config values the test client serves once its client connects, keyed by config key. The
|
|
30
|
+
* config type follows from each value: a boolean, an integral number (`integer`), any other
|
|
31
|
+
* number (`float`), a string, or a plain object or array (`json`).
|
|
32
|
+
*/
|
|
33
|
+
values?: TestValues;
|
|
34
|
+
/**
|
|
35
|
+
* The connection timeout of the SDK client, in milliseconds. It bounds how long a held
|
|
36
|
+
* `initialize` or `updateContext` waits. Defaults to the SDK's production timeout.
|
|
37
|
+
*/
|
|
38
|
+
timeout?: number;
|
|
39
|
+
/**
|
|
40
|
+
* The logger the SDK client uses. Defaults to the SDK's console logger at `warn` level.
|
|
41
|
+
*/
|
|
42
|
+
logger?: ConfigDirectorLogger;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* A real ConfigDirector client connected to an in-memory server that the test controls.
|
|
46
|
+
*
|
|
47
|
+
* The client under {@link TestClient.client} is the SDK's production client: value parsing, watchers,
|
|
48
|
+
* events, and readiness behave as they do against ConfigDirector. Only the network connection and
|
|
49
|
+
* telemetry are replaced, so no request is ever made and nothing is left running after the client is
|
|
50
|
+
* disposed.
|
|
51
|
+
*/
|
|
52
|
+
interface TestClient {
|
|
53
|
+
/**
|
|
54
|
+
* The SDK client to hand to the code under test. It starts uninitialized, like a production
|
|
55
|
+
* client; `initialize` completes at once with the seeded values unless initialization is held.
|
|
56
|
+
*/
|
|
57
|
+
readonly client: ConfigDirectorClient;
|
|
58
|
+
/**
|
|
59
|
+
* Stores `value` under `key` and, when the client is connected, delivers it as an update, so
|
|
60
|
+
* reads, watchers, and `configsUpdated` handlers see it.
|
|
61
|
+
*/
|
|
62
|
+
setValue(key: string, value: TestValue): void;
|
|
63
|
+
/**
|
|
64
|
+
* Removes `key` and, when the client is connected, delivers a full update without it, so reads
|
|
65
|
+
* fall back to the in-code default value and watchers of `key` receive that default.
|
|
66
|
+
*/
|
|
67
|
+
removeValue(key: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Replaces every stored value with `values`, disarms any armed hold or failure, and, when the
|
|
70
|
+
* client is connected, delivers the new values as a full update.
|
|
71
|
+
*/
|
|
72
|
+
replaceValues(values: TestValues): void;
|
|
73
|
+
/**
|
|
74
|
+
* Makes the next `initialize` wait until {@link completeInitialization} or
|
|
75
|
+
* {@link failInitialization} is called, or until the client's timeout elapses.
|
|
76
|
+
*/
|
|
77
|
+
holdInitialization(): void;
|
|
78
|
+
/**
|
|
79
|
+
* Delivers the stored values to a held `initialize`, so it completes with the client ready. When
|
|
80
|
+
* a hold is armed but no `initialize` has started, it disarms the hold instead.
|
|
81
|
+
*/
|
|
82
|
+
completeInitialization(): void;
|
|
83
|
+
/**
|
|
84
|
+
* Fails a held `initialize` with an unrecoverable connection error, or arms the next `initialize`
|
|
85
|
+
* to fail. `initialize` completes promptly with the client not ready and `connectionError` fires.
|
|
86
|
+
*/
|
|
87
|
+
failInitialization(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Makes the next `updateContext` wait until {@link completeContextUpdate} or
|
|
90
|
+
* {@link failContextUpdate} is called, or until the client's timeout elapses.
|
|
91
|
+
*/
|
|
92
|
+
holdContextUpdate(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Delivers the stored values to a held `updateContext`, or disarms an armed hold that no
|
|
95
|
+
* `updateContext` has picked up.
|
|
96
|
+
*/
|
|
97
|
+
completeContextUpdate(): void;
|
|
98
|
+
/**
|
|
99
|
+
* Fails a held `updateContext` with an unrecoverable connection error, or arms the next
|
|
100
|
+
* `updateContext` to fail.
|
|
101
|
+
*/
|
|
102
|
+
failContextUpdate(): void;
|
|
103
|
+
/**
|
|
104
|
+
* The context of every `initialize` and `updateContext` call, in call order. `initialize` without
|
|
105
|
+
* a context records an empty context. Resumes are not recorded.
|
|
106
|
+
*/
|
|
107
|
+
readonly contextUpdates: ConfigDirectorContext[];
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region ../js-client-core/src/testing/installTestClient.d.ts
|
|
111
|
+
/**
|
|
112
|
+
* Makes every `ConfigDirectorProvider` mounted without a `client` prop, and every client
|
|
113
|
+
* `withProvider` would build, use `testClient.client` instead of building a client, until the
|
|
114
|
+
* returned function uninstalls it. Construction props such as `sdkKey` are ignored while installed;
|
|
115
|
+
* `context` and `hooks` apply to the installed client as they do for a `client` prop. A provider
|
|
116
|
+
* reads the installed client when it is constructed, so install before rendering.
|
|
117
|
+
*
|
|
118
|
+
* Installing again replaces the installed test client. Each uninstall function restores the test
|
|
119
|
+
* client that was installed when it was created, so nested installs are uninstalled in reverse order.
|
|
120
|
+
*
|
|
121
|
+
* @param testClient The test client whose `client` providers should use
|
|
122
|
+
* @returns A function that uninstalls it
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* let uninstallTestClient: () => void;
|
|
126
|
+
* beforeEach(() => {
|
|
127
|
+
* testClient = createTestClient({ values: { "new-checkout": true } });
|
|
128
|
+
* uninstallTestClient = installTestClient(testClient);
|
|
129
|
+
* });
|
|
130
|
+
* afterEach(() => uninstallTestClient());
|
|
131
|
+
*/
|
|
132
|
+
declare const installTestClient: (testClient: TestClient) => (() => void);
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/testing.d.ts
|
|
135
|
+
/**
|
|
136
|
+
* Creates a {@link TestClient}: a real `ConfigDirectorClient` connected to an in-memory server
|
|
137
|
+
* that the test controls. No network connection is opened and no telemetry is sent. Render with
|
|
138
|
+
* `<ConfigDirectorProvider client={testClient.client}>`, or install it with `installTestClient` for
|
|
139
|
+
* components that render their own provider.
|
|
140
|
+
*
|
|
141
|
+
* @param options {@link TestClientOptions}: the initial values, the client's timeout, and its logger
|
|
142
|
+
* @returns A {@link TestClient} whose `client` is passed to the code under test
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* import { createTestClient } from "@configdirector/react-web-sdk/testing";
|
|
146
|
+
*
|
|
147
|
+
* const testClient = createTestClient({ values: { "new-checkout": true } });
|
|
148
|
+
* render(
|
|
149
|
+
* <ConfigDirectorProvider client={testClient.client}>
|
|
150
|
+
* <Checkout />
|
|
151
|
+
* </ConfigDirectorProvider>,
|
|
152
|
+
* );
|
|
153
|
+
* act(() => testClient.setValue("new-checkout", false));
|
|
154
|
+
*/
|
|
155
|
+
declare const createTestClient: (options?: TestClientOptions) => TestClient;
|
|
156
|
+
//#endregion
|
|
157
|
+
export { ConfigDirectorConnectionError, ConfigDirectorValidationError, type TestClient, type TestClientOptions, type TestJsonValue, type TestValue, type TestValues, createTestClient, installTestClient };
|
|
158
|
+
//# sourceMappingURL=configdirector-react-client-testing.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configdirector-react-client-testing.d.cts","names":[],"sources":["../../shared/src/errors.ts","../../shared/src/testing/values.ts","../../js-client-core/src/testing/TestClient.ts","../../js-client-core/src/testing/installTestClient.ts","../src/testing.ts"],"mappings":";;;cAAa,6BAAA,SAAsC,KAAA;EAAA,SACxB,IAAA;EAAA,SACT,MAAA;cAEJ,OAAA,UAAiB,MAAA;AAAA;AAAA,cAQlB,6BAAA,SAAsC,KAAA;EAAA,SACxB,IAAA;cAEb,OAAA;AAAA;;;KCZF,aAAA,sCAAmD,aAAA;EAAA,CAAqB,GAAA,WAAc,aAAA;AAAA;AAAA,KAEtF,SAAA,+BAAwC,aAAA;EAAA,CAAqB,GAAA,WAAc,aAAA;AAAA;AAAA,KAE3E,UAAA,GAAa,MAAA,SAAe,SAAA;;;;ADPxC;;KEYY,iBAAA;EFZ4C;;;;;EEkBtD,MAAA,GAAS,UAAA;EFdoB;;;AAQ/B;EEWE,OAAA;;;;EAIA,MAAA,GAAS,oBAAA;AAAA;;;;;;;ADxBX;;UCmCiB,UAAA;EDnC8F;;;;EAAA,SCwCpG,MAAA,EAAQ,oBAAA;EDxC4F;AAE/G;;;EC4CE,QAAA,CAAS,GAAA,UAAa,KAAA,EAAO,SAAA;ED5CqB;;;;ECkDlD,WAAA,CAAY,GAAA;EDhDF;;;;ECsDV,aAAA,CAAc,MAAA,EAAQ,UAAA;;;;AAjDxB;EAuDE,kBAAA;;;;;EAMA,sBAAA;EA9CA;;;;EAoDA,kBAAA;EAzCyB;;;;EA+CzB,iBAAA;EAkByB;;;;EAZzB,qBAAA;EA1CA;;;;EAgDA,iBAAA;EA1CY;;;;EAAA,SAgDH,cAAA,EAAgB,qBAAA;AAAA;;;;;AFvG3B;;;;;;;;;;;;AAYA;;;;;;;cGYa,iBAAA,GAAqB,UAAA,EAAY,UAAA;;;;;;;;;;;;;;AHZ9C;;;;;;;;;cIea,gBAAA,GAAoB,OAAA,GAAU,iBAAA,KAAoB,UAAA"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { i as ConfigDirectorLogger, n as ConfigDirectorClient, r as ConfigDirectorContext } from "./types-CU9VmelO.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../shared/src/errors.d.ts
|
|
4
|
+
declare class ConfigDirectorConnectionError extends Error {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly status?: number;
|
|
7
|
+
constructor(message: string, status?: number);
|
|
8
|
+
}
|
|
9
|
+
declare class ConfigDirectorValidationError extends Error {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region ../shared/src/testing/values.d.ts
|
|
15
|
+
type TestJsonValue = boolean | number | string | null | TestJsonValue[] | {
|
|
16
|
+
[key: string]: TestJsonValue;
|
|
17
|
+
};
|
|
18
|
+
type TestValue = boolean | number | string | TestJsonValue[] | {
|
|
19
|
+
[key: string]: TestJsonValue;
|
|
20
|
+
};
|
|
21
|
+
type TestValues = Record<string, TestValue>;
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region ../js-client-core/src/testing/TestClient.d.ts
|
|
24
|
+
/**
|
|
25
|
+
* Options for {@link createTestClient}. Every option is optional.
|
|
26
|
+
*/
|
|
27
|
+
type TestClientOptions = {
|
|
28
|
+
/**
|
|
29
|
+
* The config values the test client serves once its client connects, keyed by config key. The
|
|
30
|
+
* config type follows from each value: a boolean, an integral number (`integer`), any other
|
|
31
|
+
* number (`float`), a string, or a plain object or array (`json`).
|
|
32
|
+
*/
|
|
33
|
+
values?: TestValues;
|
|
34
|
+
/**
|
|
35
|
+
* The connection timeout of the SDK client, in milliseconds. It bounds how long a held
|
|
36
|
+
* `initialize` or `updateContext` waits. Defaults to the SDK's production timeout.
|
|
37
|
+
*/
|
|
38
|
+
timeout?: number;
|
|
39
|
+
/**
|
|
40
|
+
* The logger the SDK client uses. Defaults to the SDK's console logger at `warn` level.
|
|
41
|
+
*/
|
|
42
|
+
logger?: ConfigDirectorLogger;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* A real ConfigDirector client connected to an in-memory server that the test controls.
|
|
46
|
+
*
|
|
47
|
+
* The client under {@link TestClient.client} is the SDK's production client: value parsing, watchers,
|
|
48
|
+
* events, and readiness behave as they do against ConfigDirector. Only the network connection and
|
|
49
|
+
* telemetry are replaced, so no request is ever made and nothing is left running after the client is
|
|
50
|
+
* disposed.
|
|
51
|
+
*/
|
|
52
|
+
interface TestClient {
|
|
53
|
+
/**
|
|
54
|
+
* The SDK client to hand to the code under test. It starts uninitialized, like a production
|
|
55
|
+
* client; `initialize` completes at once with the seeded values unless initialization is held.
|
|
56
|
+
*/
|
|
57
|
+
readonly client: ConfigDirectorClient;
|
|
58
|
+
/**
|
|
59
|
+
* Stores `value` under `key` and, when the client is connected, delivers it as an update, so
|
|
60
|
+
* reads, watchers, and `configsUpdated` handlers see it.
|
|
61
|
+
*/
|
|
62
|
+
setValue(key: string, value: TestValue): void;
|
|
63
|
+
/**
|
|
64
|
+
* Removes `key` and, when the client is connected, delivers a full update without it, so reads
|
|
65
|
+
* fall back to the in-code default value and watchers of `key` receive that default.
|
|
66
|
+
*/
|
|
67
|
+
removeValue(key: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Replaces every stored value with `values`, disarms any armed hold or failure, and, when the
|
|
70
|
+
* client is connected, delivers the new values as a full update.
|
|
71
|
+
*/
|
|
72
|
+
replaceValues(values: TestValues): void;
|
|
73
|
+
/**
|
|
74
|
+
* Makes the next `initialize` wait until {@link completeInitialization} or
|
|
75
|
+
* {@link failInitialization} is called, or until the client's timeout elapses.
|
|
76
|
+
*/
|
|
77
|
+
holdInitialization(): void;
|
|
78
|
+
/**
|
|
79
|
+
* Delivers the stored values to a held `initialize`, so it completes with the client ready. When
|
|
80
|
+
* a hold is armed but no `initialize` has started, it disarms the hold instead.
|
|
81
|
+
*/
|
|
82
|
+
completeInitialization(): void;
|
|
83
|
+
/**
|
|
84
|
+
* Fails a held `initialize` with an unrecoverable connection error, or arms the next `initialize`
|
|
85
|
+
* to fail. `initialize` completes promptly with the client not ready and `connectionError` fires.
|
|
86
|
+
*/
|
|
87
|
+
failInitialization(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Makes the next `updateContext` wait until {@link completeContextUpdate} or
|
|
90
|
+
* {@link failContextUpdate} is called, or until the client's timeout elapses.
|
|
91
|
+
*/
|
|
92
|
+
holdContextUpdate(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Delivers the stored values to a held `updateContext`, or disarms an armed hold that no
|
|
95
|
+
* `updateContext` has picked up.
|
|
96
|
+
*/
|
|
97
|
+
completeContextUpdate(): void;
|
|
98
|
+
/**
|
|
99
|
+
* Fails a held `updateContext` with an unrecoverable connection error, or arms the next
|
|
100
|
+
* `updateContext` to fail.
|
|
101
|
+
*/
|
|
102
|
+
failContextUpdate(): void;
|
|
103
|
+
/**
|
|
104
|
+
* The context of every `initialize` and `updateContext` call, in call order. `initialize` without
|
|
105
|
+
* a context records an empty context. Resumes are not recorded.
|
|
106
|
+
*/
|
|
107
|
+
readonly contextUpdates: ConfigDirectorContext[];
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region ../js-client-core/src/testing/installTestClient.d.ts
|
|
111
|
+
/**
|
|
112
|
+
* Makes every `ConfigDirectorProvider` mounted without a `client` prop, and every client
|
|
113
|
+
* `withProvider` would build, use `testClient.client` instead of building a client, until the
|
|
114
|
+
* returned function uninstalls it. Construction props such as `sdkKey` are ignored while installed;
|
|
115
|
+
* `context` and `hooks` apply to the installed client as they do for a `client` prop. A provider
|
|
116
|
+
* reads the installed client when it is constructed, so install before rendering.
|
|
117
|
+
*
|
|
118
|
+
* Installing again replaces the installed test client. Each uninstall function restores the test
|
|
119
|
+
* client that was installed when it was created, so nested installs are uninstalled in reverse order.
|
|
120
|
+
*
|
|
121
|
+
* @param testClient The test client whose `client` providers should use
|
|
122
|
+
* @returns A function that uninstalls it
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* let uninstallTestClient: () => void;
|
|
126
|
+
* beforeEach(() => {
|
|
127
|
+
* testClient = createTestClient({ values: { "new-checkout": true } });
|
|
128
|
+
* uninstallTestClient = installTestClient(testClient);
|
|
129
|
+
* });
|
|
130
|
+
* afterEach(() => uninstallTestClient());
|
|
131
|
+
*/
|
|
132
|
+
declare const installTestClient: (testClient: TestClient) => (() => void);
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/testing.d.ts
|
|
135
|
+
/**
|
|
136
|
+
* Creates a {@link TestClient}: a real `ConfigDirectorClient` connected to an in-memory server
|
|
137
|
+
* that the test controls. No network connection is opened and no telemetry is sent. Render with
|
|
138
|
+
* `<ConfigDirectorProvider client={testClient.client}>`, or install it with `installTestClient` for
|
|
139
|
+
* components that render their own provider.
|
|
140
|
+
*
|
|
141
|
+
* @param options {@link TestClientOptions}: the initial values, the client's timeout, and its logger
|
|
142
|
+
* @returns A {@link TestClient} whose `client` is passed to the code under test
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* import { createTestClient } from "@configdirector/react-web-sdk/testing";
|
|
146
|
+
*
|
|
147
|
+
* const testClient = createTestClient({ values: { "new-checkout": true } });
|
|
148
|
+
* render(
|
|
149
|
+
* <ConfigDirectorProvider client={testClient.client}>
|
|
150
|
+
* <Checkout />
|
|
151
|
+
* </ConfigDirectorProvider>,
|
|
152
|
+
* );
|
|
153
|
+
* act(() => testClient.setValue("new-checkout", false));
|
|
154
|
+
*/
|
|
155
|
+
declare const createTestClient: (options?: TestClientOptions) => TestClient;
|
|
156
|
+
//#endregion
|
|
157
|
+
export { ConfigDirectorConnectionError, ConfigDirectorValidationError, type TestClient, type TestClientOptions, type TestJsonValue, type TestValue, type TestValues, createTestClient, installTestClient };
|
|
158
|
+
//# sourceMappingURL=configdirector-react-client-testing.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configdirector-react-client-testing.d.mts","names":[],"sources":["../../shared/src/errors.ts","../../shared/src/testing/values.ts","../../js-client-core/src/testing/TestClient.ts","../../js-client-core/src/testing/installTestClient.ts","../src/testing.ts"],"mappings":";;;cAAa,6BAAA,SAAsC,KAAA;EAAA,SACxB,IAAA;EAAA,SACT,MAAA;cAEJ,OAAA,UAAiB,MAAA;AAAA;AAAA,cAQlB,6BAAA,SAAsC,KAAA;EAAA,SACxB,IAAA;cAEb,OAAA;AAAA;;;KCZF,aAAA,sCAAmD,aAAA;EAAA,CAAqB,GAAA,WAAc,aAAA;AAAA;AAAA,KAEtF,SAAA,+BAAwC,aAAA;EAAA,CAAqB,GAAA,WAAc,aAAA;AAAA;AAAA,KAE3E,UAAA,GAAa,MAAA,SAAe,SAAA;;;;ADPxC;;KEYY,iBAAA;EFZ4C;;;;;EEkBtD,MAAA,GAAS,UAAA;EFdoB;;;AAQ/B;EEWE,OAAA;;;;EAIA,MAAA,GAAS,oBAAA;AAAA;;;;;;;ADxBX;;UCmCiB,UAAA;EDnC8F;;;;EAAA,SCwCpG,MAAA,EAAQ,oBAAA;EDxC4F;AAE/G;;;EC4CE,QAAA,CAAS,GAAA,UAAa,KAAA,EAAO,SAAA;ED5CqB;;;;ECkDlD,WAAA,CAAY,GAAA;EDhDF;;;;ECsDV,aAAA,CAAc,MAAA,EAAQ,UAAA;;;;AAjDxB;EAuDE,kBAAA;;;;;EAMA,sBAAA;EA9CA;;;;EAoDA,kBAAA;EAzCyB;;;;EA+CzB,iBAAA;EAkByB;;;;EAZzB,qBAAA;EA1CA;;;;EAgDA,iBAAA;EA1CY;;;;EAAA,SAgDH,cAAA,EAAgB,qBAAA;AAAA;;;;;AFvG3B;;;;;;;;;;;;AAYA;;;;;;;cGYa,iBAAA,GAAqB,UAAA,EAAY,UAAA;;;;;;;;;;;;;;AHZ9C;;;;;;;;;cIea,gBAAA,GAAoB,OAAA,GAAU,iBAAA,KAAoB,UAAA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{c as e,d as t,l as n,r,s as i,t as a,u as o}from"./installedTestClient-BqZAvzY_.mjs";const s=(e,t)=>{if(e.trim().length==0)throw new o(`Invalid config key. The key of a test value cannot be empty.`);return c(e,t)},c=(e,t)=>{switch(typeof t){case`boolean`:return{type:`boolean`,value:t?`true`:`false`};case`number`:return l(e,t);case`string`:return{type:`string`,value:t};case`object`:if(t!==null)return d(e,t,``),{type:`json`,value:JSON.stringify(t)}}throw new o(`Invalid test value for '${e}': ${m(t)}. Use a boolean, a number, a string, a plain object, or an array.`)},l=(e,t)=>{if(!Number.isFinite(t))throw new o(`Invalid test value for '${e}': ${m(t)} is not a finite number.`);if(Number.isInteger(t)){if(!Number.isSafeInteger(t))throw new o(`Invalid test value for '${e}': ${t} is not a safe integer, so the SDK could not read it back exactly.`);return{type:`integer`,value:String(t)}}return{type:`float`,value:u(t)}},u=e=>{let t=String(e),n=t.indexOf(`e-`);if(n<0)return t;let r=t.startsWith(`-`)?`-`:``,[i=``,a=``]=t.slice(r.length,n).split(`.`),o=Number(t.slice(n+2))-i.length;return`${r}0.${`0`.repeat(o)}${i}${a}`},d=(e,t,n)=>{if(!(t===null||typeof t==`boolean`||typeof t==`string`)){if(typeof t==`number`){if(!Number.isFinite(t))throw f(e,n,`${m(t)} is not a finite number`);return}if(Array.isArray(t)){t.forEach((t,r)=>d(e,t,`${n}[${r}]`));return}if(p(t)){for(let[r,i]of Object.entries(t))d(e,i,n?`${n}.${r}`:r);return}throw f(e,n,`${m(t)} cannot be encoded as JSON`)}},f=(e,t,n)=>new o(`Invalid test value for '${e}'${t?` at '${t}'`:``}: ${n}. JSON contents can hold booleans, finite numbers, strings, null, arrays, and plain objects.`),p=e=>{if(typeof e!=`object`||!e)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null},m=e=>{if(e===null)return`null`;if(typeof e==`function`)return`a function`;if(typeof e==`object`){var t,n;return`a ${(t=(n=e.constructor)==null?void 0:n.name)==null?`object`:t} instance`}return typeof e==`symbol`?`a symbol`:String(e)},h=(e,t,n)=>{let r=s(e,t);return{id:`test-config:${e}`,key:e,type:r.type,value:r.value,valueId:`test-value:${e}:${n}`}},g={environmentId:`test-environment`,projectId:`test-project`},_=e=>{switch(e){case`initialization`:return`initialize`;case`context update`:return`updateContext`;default:return}};var v=class{constructor(e,n){this.logger=e,this.emitter=new t,this.configs=new Map,this.recordedContexts=[],this.pendingDeliveries=[],this.armed={initialize:`nothing`,updateContext:`nothing`},this.connected=!1,this.delivering=!1,this.revision=0,this.attemptGeneration=0,this.on=(e,t)=>this.emitter.on(e,t),this.off=(e,t)=>this.emitter.off(e,t),this.store(n)}get contextUpdates(){return[...this.recordedContexts]}setValue(t,n){let r=h(t,n,++this.revision);this.configs.set(t,r),this.deliverIfConnected(e(e({},g),{},{kind:`delta`,configs:{[t]:r}}))}removeValue(e){this.configs.delete(e),this.deliverIfConnected(this.fullUpdate())}replaceValues(e){let t=this.encodeAll(e);this.configs.clear(),t.forEach((e,t)=>this.configs.set(t,e)),this.armed.initialize=`nothing`,this.armed.updateContext=`nothing`,this.deliverIfConnected(this.fullUpdate())}hold(e){this.armed[e]=`hold`}complete(e){var t;((t=this.pendingHold)==null?void 0:t.attempt)===e?this.pendingHold.complete():this.armed[e]===`hold`&&(this.armed[e]=`nothing`)}fail(e){var t;((t=this.pendingHold)==null?void 0:t.attempt)===e?this.pendingHold.fail():this.armed[e]=`failure`}connect(e,t,n){this.endAttempt();let r=_(n);if(!r)return this.connectNow();this.recordedContexts.push(e);let i=this.armed[r];switch(this.armed[r]=`nothing`,i){case`failure`:return this.failNow(n);case`hold`:return this.holdNow(r,n,t);default:return this.connectNow()}}clear(){this.emitter.clear()}close(){this.endAttempt()}dispose(){this.close(),this.clear()}endAttempt(){var e;this.attemptGeneration++,this.connected=!1,(e=this.pendingHold)==null||e.end()}connectNow(){let e=Promise.resolve(this);return this.scheduleFirstDelivery(),e}holdNow(e,t,n){return new Promise((r,i)=>{let a=setTimeout(()=>{var e;return(e=this.pendingHold)==null?void 0:e.end()},n),o=()=>{clearTimeout(a),this.pendingHold=void 0};this.pendingHold={attempt:e,end:()=>{o(),r(this)},complete:()=>{o(),r(this),this.scheduleFirstDelivery()},fail:()=>{o();let e=this.fatalError(t);this.emitter.emit(`connectionError`,e),i(e)}}})}failNow(e){let t=this.fatalError(e);return this.emitter.emit(`connectionError`,t),Promise.reject(t)}fatalError(e){return new n(`Connection failed with status: 401. Error: the test client failed this ${e}. This is an unrecoverable error, will not attempt to reconnect.`,401)}scheduleFirstDelivery(){let e=this.attemptGeneration;Promise.resolve().then(()=>{}).then(()=>{e===this.attemptGeneration&&(this.connected=!0,this.deliver(this.fullUpdate()))})}deliverIfConnected(e){this.connected&&this.deliver(e)}deliver(e){if(this.pendingDeliveries.push(e),!this.delivering){this.delivering=!0;try{let e=this.pendingDeliveries.shift();for(;e;)this.emitCaught(e),e=this.pendingDeliveries.shift()}finally{this.delivering=!1}}}emitCaught(e){try{this.emitter.emit(`configSetReceived`,e)}catch(e){this.logger.error(`[InMemoryConnection] Error dispatching a config update from the test client: `,e)}}fullUpdate(){return e(e({},g),{},{kind:`full`,configs:Object.fromEntries(this.configs)})}store(e){this.encodeAll(e).forEach((e,t)=>this.configs.set(t,e))}encodeAll(e){let t=new Map;for(let[n,r]of Object.entries(e))t.set(n,h(n,r,++this.revision));return t}};const y={updateContext:async()=>{},evaluatedConfig:()=>{},close:async()=>{}};var b=class{constructor(e,t){this.client=e,this.connection=t}setValue(e,t){this.connection.setValue(e,t)}removeValue(e){this.connection.removeValue(e)}replaceValues(e){this.connection.replaceValues(e)}holdInitialization(){this.connection.hold(`initialize`)}completeInitialization(){this.connection.complete(`initialize`)}failInitialization(){this.connection.fail(`initialize`)}holdContextUpdate(){this.connection.hold(`updateContext`)}completeContextUpdate(){this.connection.complete(`updateContext`)}failContextUpdate(){this.connection.fail(`updateContext`)}get contextUpdates(){return this.connection.contextUpdates}};const x=(e,t)=>{var n,a;let o=(n=t==null?void 0:t.logger)==null?i():n,s=new v(o,(a=t==null?void 0:t.values)==null?{}:a);return new b(new r(y,`test-client`,e,{logger:o,connection:{timeout:t==null?void 0:t.timeout}},{transport:s}),s)},S=e=>{let t=globalThis,n=t[a];return t[a]=e.client,()=>{t[a]=n}},C=e=>x({sdkName:`react-web-sdk`,sdkVersion:`1.6.0`},e);export{n as ConfigDirectorConnectionError,o as ConfigDirectorValidationError,C as createTestClient,S as installTestClient};
|
|
2
|
+
//# sourceMappingURL=configdirector-react-client-testing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configdirector-react-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","../../js-client-core/src/testing/installTestClient.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 { INSTALLED_TEST_CLIENT_SLOT, type InstalledTestClientSlot } from \"../installedTestClient\";\nimport type { TestClient } from \"./TestClient\";\n\n/**\n * Makes every `ConfigDirectorProvider` mounted without a `client` prop, and every client\n * `withProvider` would build, use `testClient.client` instead of building a client, until the\n * returned function uninstalls it. Construction props such as `sdkKey` are ignored while installed;\n * `context` and `hooks` apply to the installed client as they do for a `client` prop. A provider\n * reads the installed client when it is constructed, so install before rendering.\n *\n * Installing again replaces the installed test client. Each uninstall function restores the test\n * client that was installed when it was created, so nested installs are uninstalled in reverse order.\n *\n * @param testClient The test client whose `client` providers should use\n * @returns A function that uninstalls it\n *\n * @example\n * let uninstallTestClient: () => void;\n * beforeEach(() => {\n * testClient = createTestClient({ values: { \"new-checkout\": true } });\n * uninstallTestClient = installTestClient(testClient);\n * });\n * afterEach(() => uninstallTestClient());\n */\nexport const installTestClient = (testClient: TestClient): (() => void) => {\n const slot = globalThis as InstalledTestClientSlot;\n const previous = slot[INSTALLED_TEST_CLIENT_SLOT];\n slot[INSTALLED_TEST_CLIENT_SLOT] = testClient.client;\n return () => {\n slot[INSTALLED_TEST_CLIENT_SLOT] = previous;\n };\n};\n","import { createTestClient as createCoreTestClient } from \"@js-client-core/testing/index\";\nimport type { TestClient, TestClientOptions } from \"@js-client-core/testing/index\";\n\nexport { installTestClient } from \"@js-client-core/testing/index\";\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. Render with\n * `<ConfigDirectorProvider client={testClient.client}>`, or install it with `installTestClient` for\n * components that render their own provider.\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/react-web-sdk/testing\";\n *\n * const testClient = createTestClient({ values: { \"new-checkout\": true } });\n * render(\n * <ConfigDirectorProvider client={testClient.client}>\n * <Checkout />\n * </ConfigDirectorProvider>,\n * );\n * act(() => testClient.setValue(\"new-checkout\", false));\n */\nexport const createTestClient = (options?: TestClientOptions): TestClient => {\n return createCoreTestClient({ sdkName: \"react-web-sdk\", sdkVersion: \"__VERSION__\" }, options);\n};\n"],"mappings":"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"}
|
|
@@ -1,5 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`react`);c=s(c);let l=require(`react/jsx-runtime`);var u=class{constructor(){this.handlerMap=new Map}on(e,t){let n=this.handlerMap.get(e);n?n.push(t):this.handlerMap.set(e,[t])}once(e,t){let n=this;function r(i){n.off(e,r),t.apply(n,i)}this.on(e,r)}off(e,t){let n=this.handlerMap.get(e);if(n)if(t){let e=n.indexOf(t);e>=0&&n.splice(e,1)}else this.handlerMap.set(e,[])}clear(){this.handlerMap.clear()}emit(e,t){let n=this.handlerMap.get(e);n&&n.slice().map(e=>e(t))}},d=class e extends Error{constructor(t,n){super(t),this.name=`ConfigDirectorConnectionError`,this.status=n,Object.setPrototypeOf(this,e.prototype)}},f=class e extends Error{constructor(t){super(t),this.name=`ConfigDirectorValidationError`,Object.setPrototypeOf(this,e.prototype)}},ee=class e extends Error{constructor(t){super(t),this.name=`StreamClosedError`,Object.setPrototypeOf(this,e.prototype)}},p=class e extends Error{constructor(t){super(t),this.name=`MissingResponseBodyError`,Object.setPrototypeOf(this,e.prototype)}},m=class e extends Error{constructor(t){super(t),this.name=`InvalidOptionError`,Object.setPrototypeOf(this,e.prototype)}},te=class e extends Error{constructor(t){super(t),this.name=`ValueOutOfRangeError`,Object.setPrototypeOf(this,e.prototype)}},ne=class{constructor(e){this.isFirstChunk=!0,this.bufferedInput=``,this.currentEvent={data:``},this.currentEvent={data:``},this.onEvent=e.onEvent??(()=>{}),this.onRetry=e.onRetry,this.onComment=e.onComment}parse(e){let t=e;this.isFirstChunk&&(this.isFirstChunk=!1,t.startsWith(``)?t=t.slice(1):t.startsWith(``)&&(t=t.slice(3)));let n=this.scanLines(this.bufferedInput+t);for(let e of n)this.dispatchLine(e)}finish(){this.bufferedInput=``}scanLines(e){let t=[],n=0,r=0;for(;r<e.length;){let i=e[r];i===`\r`||i===`
|
|
2
|
-
`?(t.push(e.slice(n,r)),i===`\r`&&e[r+1]===`
|
|
3
|
-
`&&r++,r++,n=r):r++}return this.bufferedInput=e.slice(n),t}dispatchLine(e){if(e.startsWith(`:`)){this.onComment?.(this.extractValue(e,1));return}if(e===``){this.emitEvent();return}this.applyField(this.parseField(e))}emitEvent(){let e=this.currentEvent.data.endsWith(`
|
|
4
|
-
`)?this.currentEvent.data.slice(0,-1):this.currentEvent.data;e!==``&&this.onEvent({...this.currentEvent,id:this.lastEventId,data:e}),this.currentEvent={data:``}}applyField(e){switch(e.field){case`event`:this.currentEvent.type=e.value;break;case`data`:this.currentEvent.data+=e.value+`
|
|
5
|
-
`;break;case`id`:e.value.includes(`\0`)||(this.lastEventId=e.value);break;case`retry`:/^\d+$/.test(e.value)&&this.onRetry?.(Number.parseInt(e.value,10));break;default:break}}parseField(e){let t=e.indexOf(`:`);return t===-1?{field:e,value:``}:{field:e.slice(0,t),value:this.extractValue(e,t+1)}}extractValue(e,t){return e[t]===` `?e.slice(t+1):e.slice(t)}};let h=function(e){return e.OPEN=`open`,e.CLOSED=`closed`,e.CONNECTING=`connecting`,e}({});var g=class{constructor(e){this.DEFAULT_SHOULD_RECONNECT=()=>!0,this.DEFAULT_CALCULATE_RECONNECT_DELAY=()=>this.serverReconnectionTime,this.reconnectAttempt=0,this.abortController=new AbortController,this._readyState=h.CLOSED,this.serverReconnectionTime=2e3,this.url=e.url.toString(),this.lastEventId=e.lastEventId,this.headers={Accept:`text/event-stream`,...e.headers};let t=e.body;this.body=typeof t==`function`?t:()=>t,this.requestOptions={method:e.method??`GET`,mode:e.mode,credentials:e.credentials,redirect:e.redirect,referrer:e.referrer,referrerPolicy:e.referrerPolicy},this._fetch=e.fetch??((e,t)=>fetch(e,t)),this._onError=this.resolveCallback(e.onError,()=>{},`onError`),this._onConnect=this.resolveCallback(e.onConnect,()=>{},`onConnect`),this._onDisconnect=this.resolveCallback(e.onDisconnect,()=>{},`onDisconnect`),this._onMessage=this.resolveCallback(e.onMessage,()=>{},`onMessage`),this._onComment=this.resolveCallback(e.onComment,()=>{},`onComment`),this._shouldReconnect=this.resolveCallback(e.shouldReconnect,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`),this._calculateReconnectDelay=this.resolveCallback(e.calculateReconnectDelay,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`calculateReconnectDelay`)}set onError(e){this._onError=this.resolveCallback(e,()=>{},`onError`)}set onMessage(e){this._onMessage=this.resolveCallback(e,()=>{},`onMessage`)}set onComment(e){this._onComment=this.resolveCallback(e,()=>{},`onComment`)}set onConnect(e){this._onConnect=this.resolveCallback(e,()=>{},`onConnect`)}set onDisconnect(e){this._onDisconnect=this.resolveCallback(e,()=>{},`onDisconnect`)}set shouldReconnect(e){this._shouldReconnect=this.resolveCallback(e,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`)}set calculateReconnectDelay(e){this._calculateReconnectDelay=this.resolveCallback(e,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`shouldReconnect`)}get readyState(){return this._readyState}connect(){this._readyState===h.CLOSED&&(this.reconnectAttempt=0,this.initiateConnection())}close(){clearTimeout(this.reconnectTimeout),this._readyState=h.CLOSED,this.abortController.abort()}initiateConnection(){this.abortController=new AbortController,this._readyState=h.CONNECTING,this._fetch(this.url,{...this.requestOptions,body:this.body(),headers:this.buildRequestHeaders(),signal:this.abortController.signal,cache:`no-store`}).then(e=>this.handleFetchResponse(e)).catch(e=>{if(e.name===`AbortError`||this.abortController.signal.aborted){this.close();return}this._onError(e),this.scheduleReconnect({error:e})})}async handleFetchResponse(e){let{body:t,status:n}=e;if(n===204){this.disconnected();return}if(n>=400){this.scheduleReconnect({status:n});return}if(!t)throw new p(`The server response did not have a body`);this._readyState=h.OPEN,this._onConnect(),this.reconnectAttempt=0;let r=new ne({onEvent:e=>{typeof e.id==`string`&&(this.lastEventId=e.id),this._onMessage(e)},onComment:e=>this._onComment(e),onRetry:e=>this.serverReconnectionTime=e});await this.readResponseStream(t,r),this.scheduleReconnect({status:n,error:new ee(`The server response stream was closed`)})}async readResponseStream(e,t){let n=e.getReader(),r=new TextDecoder,i=!1;for(;!i;){let e=await n.read();e.value&&t.parse(r.decode(e.value,{stream:!0})),i=e.done}t.finish()}disconnected(){clearTimeout(this.reconnectTimeout),this._readyState=h.CLOSED,this._onDisconnect()}buildRequestHeaders(){let e=this.lastEventId?{"Last-Event-ID":this.lastEventId}:void 0;return{...this.headers,...e}}scheduleReconnect(e){if(this.abortController.signal.aborted)return;clearTimeout(this.reconnectTimeout),this.reconnectAttempt+=1;let t={attempt:this.reconnectAttempt,status:e.status,error:e.error,serverReconnectionTime:this.serverReconnectionTime};if(!this._shouldReconnect(t)){this.disconnected();return}this._readyState=h.CONNECTING;let n=this._calculateReconnectDelay(t);(!Number.isFinite(n)||n<1||n>1e3*60*60)&&(this._onError(new te(`The calculated reconnect delay is out of range: ${n}. Defaulting to ${this.serverReconnectionTime}`)),n=this.serverReconnectionTime),this.reconnectTimeout=setTimeout(()=>this.initiateConnection(),n)}resolveCallback(e,t,n){if(e==null)return t;if(typeof e!=`function`)throw new m(`${n} must be a function`);return e}};const _=e=>new g(e);var v=class{constructor(e){this.options=e,this.eventEmitter=new u,this.options=e,this.logger=e.logger,this.url=e.resolveUrl(`client/sse/v1`,e.baseUrl)}async connect(e,t){this.eventSource&&this.close();let n=new Promise((t,n)=>{this.eventSource=_({url:this.url,method:`POST`,headers:{"Content-Type":`application/json`},fetch:this.options.fetch,body:JSON.stringify({givenContext:e,metaContext:this.options.metaContext,clientSdkKey:this.options.clientSdkKey,instanceId:this.options.instanceId}),onMessage:({data:e})=>{this.dispatchMessage(e)},onConnect:()=>{this.logger.debug(`[StreamingTransport] Connected`),t(this)},shouldReconnect:e=>{let t=!this.isStatusFatal(e.status);if(!t){let t=this.prepareFatalError(e.status,e.error);this.eventEmitter.emit(`connectionError`,t),n(t)}return t},calculateReconnectDelay:e=>{let t=this.options.connectionRetryDelay(e.attempt),n=`[StreamingTransport] Scheduling reconnect attempt #${e.attempt} in ${t}ms.`;return e.attempt<=5?this.logger.info(n):this.logger.warn(n),t},onError:e=>{this.logger.debug(`[StreamingTransport] Error: `,e)},onDisconnect:()=>{this.logger.debug(`[StreamingTransport] Disconnected`)}}),this.eventSource.connect()});return clearTimeout(this.timeoutTimer),this.timeoutTimer=void 0,Promise.race([n,new Promise(e=>{this.timeoutTimer=setTimeout(()=>e(this),t)})]).finally(()=>clearTimeout(this.timeoutTimer))}dispatchMessage(e){try{let t=JSON.parse(e);this.eventEmitter.emit(`configSetReceived`,t)}catch(e){this.logger.error(`[StreamingTransport] Error parsing and dispatching config data update: `,e)}}prepareFatalError(e,t){let n=e??0;return new d(`${`Connection failed with status: ${e??`unknown`}.`}${t?` Error: ${t}`:``}. This is an unrecoverable error, will not attempt to reconnect.`,n)}isStatusFatal(e){return!!e&&e>=400&&e<500&&e!=429}on(e,t){this.eventEmitter.on(e,t)}off(e,t){this.eventEmitter.off(e,t)}clear(){this.eventEmitter.clear()}close(){this.eventSource?.close(),clearTimeout(this.timeoutTimer)}dispose(){this.close(),this.clear()}};const y=e=>{let t=typeof e;if(t===`object`)try{return e.constructor?.name??t}catch{return t}else if(t===`function`)try{let n=e.name;return n?`function: ${n}`:t}catch{return t}return t},b=e=>e===`number`||e===`bigint`,x=new Set([`boolean`,`integer`,`float`]),S=(e,t)=>{let n=e.value,r=y(t);if(n==null)return{parsedValue:t,parsedValueId:void 0,requestedType:r,usedDefault:!0,reason:`value-missing`};if(e.type===`json`)return ie(n,t,r,e.valueId);if(typeof t==`string`)return x.has(e.type)?{parsedValue:t,parsedValueId:void 0,requestedType:r,usedDefault:!0,reason:`type-mismatch`}:{parsedValue:n,parsedValueId:e.valueId,requestedType:r,usedDefault:!1,reason:`found-match`};if(typeof t==`boolean`&&(e.type===`boolean`||e.type===`string`||e.type===`enum`)){let i=C(n),a=typeof i==`boolean`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-boolean`}}if(b(typeof t)&&e.type===`integer`){let i=w(n),a=typeof i==`number`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-number`}}if(b(typeof t)&&(e.type===`float`||e.type===`enum`||e.type===`string`)){let i=T(n),a=typeof i==`number`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-number`}}return{parsedValue:t,parsedValueId:void 0,requestedType:r,usedDefault:!0,reason:`type-mismatch`}},C=e=>{if(!e)return;let t=e.toLowerCase();if(!(t!=`true`&&t!=`false`))return t===`true`},w=e=>{let t=E(e);return t==null?void 0:Math.trunc(t)},T=e=>E(e),E=e=>{if(!e||!re(e))return;let t=Number(e);if(!(isNaN(t)||!isFinite(t)))return t},re=e=>{for(let t of e)if(!(t>=`0`&&t<=`9`)&&!`+-.eE`.includes(t))return!1;return!0},ie=(e,t,n,r)=>{if(typeof t==`string`)return{parsedValue:e,parsedValueId:r,requestedType:n,usedDefault:!1,reason:`found-match`};try{let i=JSON.parse(e);return D(i,t)?{parsedValue:i,parsedValueId:r,requestedType:n,usedDefault:!1,reason:`found-match`}:{parsedValue:t,parsedValueId:void 0,requestedType:n,usedDefault:!0,reason:`type-mismatch`}}catch{return{parsedValue:t,parsedValueId:void 0,requestedType:n,usedDefault:!0,reason:`invalid-json`}}},D=(e,t)=>{if(e===null)return!1;let n=typeof t;return n===`bigint`?typeof e==`number`:typeof e===n},O={off:-1,error:0,warn:1,info:2,debug:3},k=()=>{let e={year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,timeZoneName:`short`};try{return new Intl.DateTimeFormat(void 0,{...e,fractionalSecondDigits:3})}catch{return new Intl.DateTimeFormat(void 0,e)}};var A=class{constructor(e,t){this.level=e,this.decorator=t,this.level=e,this.decorator=t,this.dateFormatter=k()}debug(e,...t){this.log(console.debug,`debug`,e,...t)}info(e,...t){this.log(console.info,`info`,e,...t)}warn(e,...t){this.log(console.warn,`warn`,e,...t)}error(e,...t){this.log(console.error,`error`,e,...t)}log(e,t,n,...r){O[this.level]>=O[t]&&e(`[${this.dateFormatter.format(new Date)}] ${this.decorator?.decorateMessage(n)}`,...r)}},j=class{decorateMessage(e){return`[ConfigDirector:js-client-sdk] ${e}`}};const M=(e,t)=>new A(e??`warn`,t??new j),N=(e,t)=>new URL(e,t?.toString()),P=e=>{if(e)try{return new URL(e)}catch(t){throw new f(`Invalid URL '${e}'. Parsing failed: ${t}`)}},F=()=>typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e===`x`?t:t&3|8).toString(16)}),I=`configdirector-sdk:instance-id`,L=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,R=(e,t)=>{let n=t;for(let t=0;t<e.length;t++)n^=e.charCodeAt(t),n=Math.imul(n,16777619);return n>>>0},z=(e,t)=>{let n=`configdirector.instanceId.v1|${e}|${t}`,r=R(n,2166136261);return R(`${r}|${n}`,16777619).toString(16).padStart(8,`0`)+r.toString(16).padStart(8,`0`)},B=()=>{try{return globalThis.localStorage??void 0}catch{return}},V=e=>{if(!e||typeof e!=`object`)return!1;let{v:t,id:n,createdAt:r,checksum:i}=e;return t!==1||typeof n!=`string`||typeof i!=`string`||typeof r!=`number`||!Number.isInteger(r)||r<=0||!L.test(n)?!1:i===z(n,r)},H=(e,t)=>{let n;try{n=e.getItem(I)}catch(e){t?.debug(`[ConfigDirectorClient] Could not read the stored instance id, generating a new one`,e);return}if(!n)return;let r;try{r=JSON.parse(n)}catch{t?.debug(`[ConfigDirectorClient] The stored instance id is not valid JSON, generating a new one`);return}if(!V(r)){t?.debug(`[ConfigDirectorClient] The stored instance id failed validation, generating a new one`);return}let i=Date.now()-r.createdAt;if(i<0||i>=216e5){t?.debug(`[ConfigDirectorClient] The stored instance id has expired, generating a new one`);return}return r.id},U=(e,t,n)=>{let r=Date.now(),i={v:1,id:t,createdAt:r,checksum:z(t,r)};try{e.setItem(I,JSON.stringify(i))}catch(e){n?.debug(`[ConfigDirectorClient] Could not persist the instance id`,e)}},W=e=>{let t=B();if(!t)return F();let n=H(t,e);if(n)return n;let r=F();return U(t,r,e),r};var G=class{constructor(){this.fatalError=!1,this.pollingIntervalSeconds=60}clearPollingInterval(){clearInterval(this.pollingInterval)}schedulePollingInterval(e){clearInterval(this.pollingInterval),!(this.pollingIntervalSeconds<=0)&&(this.pollingInterval=setInterval(e,this.pollingIntervalSeconds*1e3))}async handleNonOkResponse(e){if(!e.ok)throw this.isStatusFatal(e.status)?(this.fatalError=!0,this.close(),this.prepareFatalResponseStatusError(e.status,await e.text())):new d(`Connection failed with status: ${e.status}`,e.status)}handleFetchError(e){throw this.isFetchErrorFatal(e)?(this.fatalError=!0,this.close(),new d(`Connection failed with fatal error: ${e}. This is an unrecoverable error, retry attempts will be ignored.`)):e instanceof SyntaxError?new d(`Failed to parse the response from the server: ${e}`):new d(`Connection failed with error: ${e}.`)}prepareFatalResponseStatusError(e,t){let n=e??0;return new d(`${`Connection failed with status: ${e??`unknown`}`}${(t?.trim()?.length??0)>0?` (${t})`:``}. This is an unrecoverable error, retry attempts will be ignored.`,n)}isStatusFatal(e){return!!e&&e>=400&&e<500&&e!=429}isFetchErrorFatal(e){return e?.name===`NotAllowedError`}get isConnected(){return this.pollingInterval!==void 0}close(){clearInterval(this.pollingInterval),this.pollingInterval=void 0}dispose(){this.close(),this.clear()}};const ae=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}};var K=class extends G{constructor(e){super(),this.options=e,this.eventEmitter=new u,this.options=e,this.logger=e.logger,this.url=e.resolveUrl(`client/polling/v1`,e.baseUrl);let t=e.pollingInterval??300;t<60&&this.logger.warn(`[PollingTransport] pollingInterval of ${t} seconds is below the minimum of 60 seconds. Using 60 seconds.`),this.pollingIntervalSeconds=Math.max(t,60)}async connect(e,t){this.clearPollingInterval();try{await this.fetchConfigs(e,t)}finally{this.fatalError||this.schedulePollingInterval(()=>{this.fetchConfigs(e,t).catch(e=>{this.logger.error(`[PollingTransport] Error during polling:`,e)})})}return this}async fetchConfigs(e,t){if(this.fatalError){this.logger.warn(`[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.`);return}try{let n=await ae(t,this.url,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({givenContext:e,metaContext:this.options.metaContext,clientSdkKey:this.options.clientSdkKey,instanceId:this.options.instanceId,lastUpdateTimestamp:this.lastUpdateTimestamp})},this.logger);if(await this.handleNonOkResponse(n),n.status==200){let e=JSON.parse(await n.text());this.lastUpdateTimestamp=e.timestamp,this.eventEmitter.emit(`configSetReceived`,e)}}catch(e){try{this.handleFetchError(e)}catch(e){throw this.fatalError&&this.eventEmitter.emit(`connectionError`,e),e}}}on(e,t){this.eventEmitter.on(e,t)}off(e,t){this.eventEmitter.off(e,t)}clear(){this.eventEmitter.clear()}};const q=e=>{try{return e(globalThis)}catch{return}},J=()=>q(e=>e.navigator?.userAgent),oe=()=>q(e=>e.location?.host);var se=class{constructor(e,t,n,r,i){this.handlersMap=new Map,this.eventEmitter=new u,this.ready=!1,this.initializing=!1,this.logger=r?.logger??M(),this.instanceId=W(this.logger),this.timeout=r?.connection?.timeout??3e3,this.validateSdkKeyPresence(t);let a=i?.urlFactory??N,o=this.parseUrl(r?.connection?.url,a)??`https://client-sdk-api.configdirector.com`;this.connectionMode=r?.connection?.mode??`streaming`;let s=this.getTransportConstructor(this.connectionMode);this.telemetryClient=e,this.transport=new s({instanceId:this.instanceId,clientSdkKey:t,baseUrl:o,resolveUrl:a,metaContext:{...r?.metadata,sdkName:n.sdkName,sdkVersion:n.sdkVersion,userAgent:J(),host:oe()},logger:this.logger,fetch:i?.fetch,connectionRetryDelay:i?.connectionRetryDelay??(e=>2**Math.min(e,9)*1e3),pollingInterval:r?.connection?.pollingInterval}),this.transport.on(`configSetReceived`,e=>{this.readyResolve?.();let t=Object.keys(e.configs);!this.configSet||e.kind==`full`?(this.configSet=e,this.emit(`configsUpdated`,{keys:t}),this.updateWatchers(e.configs)):(this.configSet.configs={...this.configSet.configs,...e.configs},this.emit(`configsUpdated`,{keys:t}),this.updateWatchers(e.configs)),this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server:`,{keys:t})}),this.transport.on(`connectionError`,e=>{this.logger.error(`[ConfigDirectorClient] The connection encountered an unrecoverable error and will not be retried. Configs will no longer receive updates.`,e),this.emit(`connectionError`,{error:e})}),this.registerHandler(`clientReady`,r?.hooks?.clientReady),this.registerHandler(`configEvaluated`,r?.hooks?.configEvaluated),this.registerHandler(`configsUpdated`,r?.hooks?.configsUpdated),this.registerHandler(`contextUpdated`,r?.hooks?.contextUpdated),this.registerHandler(`connectionError`,r?.hooks?.connectionError)}registerHandler(e,t){if(!t)return;let n=Array.isArray(t)?t:[t];for(let t of n)typeof t==`function`&&this.on(e,t)}getTransportConstructor(e){switch(e){case`polling`:return K;default:return v}}async initialize(e){this.initializing=!0,await this.connectToTransport(e,`initialization`)}async updateContext(e){await this.connectToTransport(e,`context update`)}get context(){return this.currentContext}async connectToTransport(e,t){try{this.ready=!1,this.readyPromise=new Promise(e=>{this.readyResolve=e}).then(e=>{e||(this.ready=!0,this.initializing=!1,this.emit(`clientReady`,{action:t}),this.logger.debug(`[ConfigDirectorClient] Received initial payload from the server, client is ready`))});let n=new Date().getTime();await this.transport.connect(e??{},this.timeout),this.currentContext=e,this.telemetryClient.updateContext(e),this.emit(`contextUpdated`,{context:e});let r=new Date().getTime()-n,i=this.timeout-r;clearTimeout(this.timeoutTimer),this.timeoutTimer=void 0,i>0&&await Promise.race([this.readyPromise,new Promise(e=>{this.timeoutTimer=setTimeout(()=>e(),i)})]).finally(()=>clearTimeout(this.timeoutTimer)),this.ready||this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${t} after ${this.timeout}ms. The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds.`)}catch(e){this.logger.error(`[ConfigDirectorClient] An error occurred during ${t}: `,e)}}updateWatchers(e){Object.values(e).forEach(e=>this.updateWatchersForConfig(e))}updateWatchersForConfig(e){this.handlersMap.get(e.key)?.forEach(t=>{let n=this.getValueFromConfigState(e.key,e,t.defaultValue);t.handler(n)})}watch(e,t,n){this.validateDefaultValue(t);let r=this.handlersMap.get(e),i={handler:n,defaultValue:t,requestedType:typeof t};return r?r.push(i):this.handlersMap.set(e,[i]),()=>this.unwatch(e,n)}unwatch(e,t){let n=this.handlersMap.get(e);if(n)if(t){let e=n.findIndex(e=>e.handler==t);e>=0&&n?.splice(e,1)}else n.splice(0)}getValue(e,t){this.validateDefaultValue(t);let n=this.configSet?.configs[e];return this.getValueFromConfigState(e,n,t)}getValueFromConfigState(e,t,n){if(!t){this.logger.debug(`[ConfigDirectorClient] No config state found for '${e}', returning default value '${n}'`);let t=this.isReady?`config-state-missing`:`client-not-ready`;return this.telemetryClient.evaluatedConfig({contextId:this.currentContext?.id,key:e,defaultValue:n,requestedType:y(n),evaluatedValue:n,usedDefault:!0,evaluationReason:t}),this.dispatchEvaluationEvent({key:e,value:n,isDefaultValue:!0,reason:t,context:this.context}),n}let r=S(t,n);return this.telemetryClient.evaluatedConfig({contextId:this.currentContext?.id,type:t.type,key:e,defaultValue:n,requestedType:r.requestedType,evaluatedValue:r.parsedValue,evaluatedValueId:r.parsedValueId,usedDefault:r.usedDefault,evaluationReason:r.reason}),this.dispatchEvaluationEvent({key:e,value:r.parsedValue,valueId:r.parsedValueId??void 0,isDefaultValue:r.usedDefault,reason:r.reason,context:this.context}),this.logger.debug(`[ConfigDirectorClient] Evaluated '${e}' to '${r.parsedValue}'`),r.parsedValue}dispatchEvaluationEvent(e){setTimeout(()=>this.emit(`configEvaluated`,{evaluation:e}),0)}emit(e,t){try{this.eventEmitter.emit(e,t)}catch(t){this.logger.error(`[ConfigDirectorClient] Error executing event handlers for '${e}'`,t)}}parseUrl(e,t){if(e)try{return t(e)}catch(t){throw new f(`Invalid base URL '${e}'. Parsing failed: ${t}`)}}validateSdkKeyPresence(e){if(!e||e.trim().length==0)throw new f(`No client SDK key was provided, the client cannot be instantiated without a valid client SDK key`)}validateDefaultValue(e){if(e==null)throw new f(`Invalid default value. The default value for a config must be defined and non-null.`);if(typeof e==`function`)throw new f(`Invalid default value. The default value for a config cannot be a function.`)}get isReady(){return this.ready}get isInitializing(){return this.initializing}on(e,t){this.eventEmitter.on(e,t)}off(e,t){this.eventEmitter.off(e,t)}clear(){this.logger.debug(`[ConfigDirectorClient] clear() has been called, removing all observers`),this.eventEmitter.clear(),this.handlersMap.clear()}unwatchAll(){this.handlersMap.clear()}pauseNetwork(){this.logger.debug(`[ConfigDirectorClient] pauseNetwork() called, pausing transport connection`),clearTimeout(this.timeoutTimer),this.timeoutTimer=void 0,this.transport.close(),this.ready=!1}async resumeNetwork(){await this.connectToTransport(this.currentContext,`network resume`)}async close(){this.logger.debug(`[ConfigDirectorClient] close() has been called, closing connection to server`),clearTimeout(this.timeoutTimer),this.readyResolve?.(!0),this.telemetryClient.close(),this.transport.close(),this.ready=!1,this.initializing=!1}dispose(){this.clear(),this.close()}};const Y=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 ce=class{constructor(e){this.logger=e.logger,this.workerCloseTimeout=e.workerCloseTimeout??6e3;let t={type:`Initialize`,payload:{sdkKey:e.sdkKey,sdkIdentity:e.sdkIdentity,baseUrl:e.baseUrl.toString(),evaluationQueueLimit:e.evaluationQueueLimit??1e3,initialFlushIntervalDelay:e.initialFlushIntervalDelay??5e3,flushIntervalDelay:e.flushIntervalDelay??3e4}};this.worker=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.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},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.logger=e.logger,this.evaluationEventQueue=new s(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}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});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`}))),this.worker.onmessage=e=>{this.handleWorkerEvent(e.data)},this.worker.postMessage(t),this.visibilityHandler=()=>{document.visibilityState===`hidden`&&this.flush()};try{document.addEventListener(`visibilitychange`,this.visibilityHandler)}catch(e){this.logger.warn(`[TelemetryEventCollector] Could not configure 'visibilitychange' listener: `,e)}}async updateContext(e){let t={type:`UpdateContext`,payload:{context:e}};this.worker.postMessage(t)}evaluatedConfig(e){let t={type:`EvaluatedConfigEvent`,payload:this.sanitizeEvaluatedConfigEvent(e)};this.worker.postMessage(t)}sanitizeEvaluatedConfigEvent(e){return{...e,defaultValue:Y({value:e.defaultValue,type:e.type}),evaluatedValue:Y({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(){try{document.removeEventListener(`visibilitychange`,this.visibilityHandler)}catch(e){this.logger.warn(`[WebWorkerTelemetryClient] Could not remove 'visibilitychange' listener: `,e)}return this.closePromise=new Promise(e=>{this.closeResolve=e,this.closeTimer=setTimeout(()=>{this.logger.warn(`[WebWorkerTelemetryClient] Timed out waiting for the telemetry worker to close. Terminating the worker.`),this.finishClose()},this.workerCloseTimeout),this.worker.postMessage({type:`Close`})}),this.closePromise}finishClose(){clearTimeout(this.closeTimer),this.closeTimer=void 0,this.worker.terminate(),this.closeResolve?.()}};const le=(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`)},ue=Math.ceil(128/Math.log2(62)),de=new TextEncoder,fe=async e=>{if(typeof crypto>`u`||crypto.subtle==null)return;let t=await crypto.subtle.digest(`SHA-256`,de.encode(e?.toString()??``));return le(new Uint8Array(t,0,16),ue)},pe=(e,t,n)=>{let r=n?.logger??M(),i=P(n?.connection?.url)??`https://client-sdk-api.configdirector.com`;return new se(new ce({sdkKey:e,sdkIdentity:{sdkName:t.sdkName,sdkVersion:t.sdkVersion},logger:r,baseUrl:i,urlFactory:N,valueIdGenerator:fe}),e,t,{...n,logger:r})},X=(e,t)=>pe(e,{sdkName:`react-web-sdk`,sdkVersion:`1.5.0`},t),Z=c.default.createContext({status:`loading`});var me=class{decorateMessage(e){return`[ConfigDirector:react-web-sdk] ${e}`}};const Q=e=>M(e,new me),he=async e=>{let t=e.logger??Q(`debug`),n=X(e.sdkKey,{connection:{url:e.url,timeout:e.timeout},metadata:{appName:e.appName,appVersion:e.appVersion},logger:t,hooks:e.hooks});return await n.initialize(e.context),({children:e})=>{let[t,r]=(0,c.useState)(()=>({client:n,status:n.isReady?`ready`:`default`}));return(0,c.useEffect)(()=>{let e=()=>{r(e=>({...e,updatedAt:new Date}))},t=()=>{r(e=>e.status===`ready`?e:{...e,status:`ready`})};return n.on(`configsUpdated`,e),n.on(`clientReady`,t),n.isReady&&t(),()=>{n.off(`configsUpdated`,e),n.off(`clientReady`,t)}},[]),(0,l.jsx)(Z.Provider,{value:t,children:e})}};var ge=class extends c.Component{constructor(e){super(e),this.state={client:X(e.sdkKey,{connection:{url:e.url,timeout:e.timeout},metadata:{appName:e.appName,appVersion:e.appVersion},logger:e.logger??Q(`warn`),hooks:e.hooks}),status:`loading`}}async componentDidMount(){this.state.client?.on(`configsUpdated`,()=>{this.setState({updatedAt:new Date})}),this.state.client?.on(`clientReady`,()=>{this.setState({status:`ready`})}),await this.state.client?.initialize(this.props.context),this.state.client?.isReady||this.setState({status:`default`})}async componentDidUpdate(e){e.context!==this.props.context&&(this.setState({status:`loading`}),await this.state.client?.updateContext(this.props.context??{}),this.state.client?.isReady||this.setState({status:`default`}))}componentWillUnmount(){this.state.client?.dispose()}render(){return(0,l.jsx)(Z.Provider,{value:this.state,children:this.props.children})}},_e=class e extends Error{constructor(t){super(t),this.name=`ConfigDirectorReactContextError`,Object.setPrototypeOf(this,e.prototype)}};const $=()=>new _e(`The ConfigDirector client was not found in the React context. Please make sure the component using this hook is inside the context of a ConfigDirectorProvider.`),ve=(e,t)=>{let n=(0,c.useContext)(Z);if(!n?.client)throw $();return{value:n.client.getValue(e,t),readyStatus:n.status,loading:n.status===`loading`}},ye=()=>{let e=(0,c.useContext)(Z);if(!e?.client)throw $();return{updateContext:async t=>{await e.client?.updateContext(t)}}},be=()=>{let e=(0,c.useContext)(Z);if(!e?.client)throw $();return{client:e.client}};exports.ConfigDirectorProvider=ge,exports.createConsoleLogger=Q,exports.useClient=be,exports.useConfigValue=ve,exports.useContext=ye,exports.withProvider=he;
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=require(`./installedTestClient-DozdiR3i.cjs`);let l=require(`react`);l=s(l);let u=require(`react/jsx-runtime`);const d=l.default.createContext({status:`loading`}),f=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 p=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:f({value:e.defaultValue,type:e.type}),evaluatedValue:f({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 m=(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`)},h=Math.ceil(128/Math.log2(62)),g=new TextEncoder,_=async e=>{if(typeof crypto>`u`||crypto.subtle==null)return;let t=await crypto.subtle.digest(`SHA-256`,g.encode(e?.toString()??``));return m(new Uint8Array(t,0,16),h)},v=(e,t,n)=>{let r=n?.logger??c.s(),i=c.o(n?.connection?.url)??`https://client-sdk-api.configdirector.com`;return new c.r(new p({sdkKey:e,sdkIdentity:{sdkName:t.sdkName,sdkVersion:t.sdkVersion},metaContext:{appName:n?.metadata?.appName,appVersion:n?.metadata?.appVersion},logger:r,baseUrl:i,urlFactory:c.a,valueIdGenerator:_}),e,t,{...n,logger:r})};var y=class{decorateMessage(e){return`[ConfigDirector:react-web-sdk] ${e}`}};const b=e=>c.s(e,new y),x=(e,t)=>{if(Object.is(e,t))return!0;if(typeof e!=`object`||typeof t!=`object`||e===null||t===null||Object.getPrototypeOf(e)!==Object.getPrototypeOf(t))return!1;if(e instanceof Date&&t instanceof Date)return e.getTime()===t.getTime();if(Array.isArray(e)&&Array.isArray(t))return e.length===t.length&&e.every((e,n)=>x(e,t[n]));if(!S(e)||!S(t))return!1;let n=Object.keys(e),r=Object.keys(t);return n.length===r.length&&n.every(n=>Object.prototype.hasOwnProperty.call(t,n)&&x(e[n],t[n]))},S=e=>{let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null},C=(e,t)=>e.isInitializing?`none`:e.isReady?t!==void 0&&!x(t,e.context)?`updateContext`:`none`:`initialize`,w=(e,t)=>v(e,{sdkName:`react-web-sdk`,sdkVersion:`1.6.0`},t),T=e=>w(e.sdkKey,{connection:{url:e.url,timeout:e.timeout,mode:e.mode,pollingInterval:e.pollingInterval},metadata:{appName:e.appName,appVersion:e.appVersion},logger:e.logger??b(`warn`),hooks:e.hooks}),E=e=>`client`in e&&e.client!==void 0,D=e=>{if(E(e))return{client:e.client,owned:!1};let t=c.n();return t?{client:t,owned:!1}:{client:T(e),owned:!0}},O=async e=>{let t=c.n()??T({...e,logger:e.logger??b(`debug`)});switch(C(t,e.context)){case`initialize`:await t.initialize(e.context);break;case`updateContext`:await t.updateContext(e.context);break;case`none`:break}return({children:e})=>{let[n,r]=(0,l.useState)(()=>({client:t,status:t.isReady?`ready`:`default`}));return(0,l.useEffect)(()=>{let e=()=>{r(e=>({...e,updatedAt:new Date}))},n=()=>{r(e=>e.status===`ready`?e:{...e,status:`ready`})};return t.on(`configsUpdated`,e),t.on(`clientReady`,n),t.isReady&&n(),()=>{t.off(`configsUpdated`,e),t.off(`clientReady`,n)}},[]),(0,u.jsx)(d.Provider,{value:n,children:e})}};var k=class{constructor(e){this.client=e,this.registrations=[]}on(e,t){this.client.on(e,t),this.registrations.push({event:e,handler:t})}registerHooks(e){for(let[t,n]of Object.entries(e??{})){let e=Array.isArray(n)?n:[n];for(let n of e)typeof n==`function`&&this.on(t,n)}}removeAll(){for(let{event:e,handler:t}of this.registrations)this.client.off(e,t);this.registrations=[]}},A=class extends l.Component{constructor(e){super(e),this.disposedOwnClient=!1;let t=D(e);this.ownsClient=t.owned,this.state={client:t.client,status:t.client.isReady?`ready`:`loading`}}async componentDidMount(){let e=this.state.client;this.ownsClient&&this.disposedOwnClient&&(e=T(this.props),this.disposedOwnClient=!1,this.setState({client:e,status:`loading`})),this.handlers=new k(e),this.handlers.on(`configsUpdated`,()=>{this.setState({updatedAt:new Date})}),this.handlers.on(`clientReady`,()=>{this.setState({status:`ready`})}),this.ownsClient||this.handlers.registerHooks(this.props.hooks),await this.connect(e,this.props.context)}async connect(e,t){switch(C(e,t)){case`initialize`:await e.initialize(t);break;case`updateContext`:this.setState({status:`loading`}),await e.updateContext(t);break;case`none`:return}e.isReady||this.setState({status:`default`})}async componentDidUpdate(e){E(e)&&E(this.props)&&e.client!==this.props.client&&b(`warn`).warn(`The client prop of ConfigDirectorProvider changed after it mounted and is ignored. Remount the provider, for example with a new key, to use another client.`),x(e.context,this.props.context)||(this.setState({status:`loading`}),await this.state.client?.updateContext(this.props.context??{}),this.state.client?.isReady||this.setState({status:`default`}))}componentWillUnmount(){this.handlers?.removeAll(),this.handlers=void 0,this.ownsClient&&(this.state.client?.dispose(),this.disposedOwnClient=!0)}render(){return(0,u.jsx)(d.Provider,{value:this.state,children:this.props.children})}},j=class e extends Error{constructor(t){super(t),this.name=`ConfigDirectorReactContextError`,Object.setPrototypeOf(this,e.prototype)}};const M=()=>new j(`The ConfigDirector client was not found in the React context. Please make sure the component using this hook is inside the context of a ConfigDirectorProvider.`),N=(e,t)=>{let n=(0,l.useContext)(d);if(!n?.client)throw M();return{value:n.client.getValue(e,t),readyStatus:n.status,loading:n.status===`loading`}},P=()=>{let e=(0,l.useContext)(d);if(!e?.client)throw M();return{updateContext:async t=>{await e.client?.updateContext(t)}}},F=()=>{let e=(0,l.useContext)(d);if(!e?.client)throw M();return{client:e.client}};exports.ConfigDirectorProvider=A,exports.createConsoleLogger=b,exports.useClient=F,exports.useConfigValue=N,exports.useContext=P,exports.withProvider=O;
|