@configdirector/nuxt-sdk 1.0.0 → 1.3.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/dist/module.d.mts +37 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +13 -7
- package/dist/runtime/app/composables/useConfigDirectorClient.d.mts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorClient.d.ts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.mts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.ts +1 -1
- package/dist/runtime/nitro/middleware.d.mts +1 -1
- package/dist/runtime/nitro/middleware.d.ts +1 -1
- package/dist/runtime/nitro/middleware.mjs +5 -3
- package/dist/runtime/nitro/plugin.d.mts +2 -1
- package/dist/runtime/nitro/plugin.d.ts +2 -1
- package/dist/runtime/nitro/plugin.mjs +153 -65
- package/dist/runtime/plugin.client.mjs +200 -54
- package/dist/runtime/plugin.server.mjs +4 -8
- package/dist/runtime/server/composables/useConfigDirectorClient.d.mts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorClient.d.ts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.mts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.ts +1 -1
- package/dist/runtime/{types-B4EK1B6g.d.mts → types-BPvGEzJY.d.mts} +4 -0
- package/dist/runtime/{types-CzmBIfqo.d.mts → types-C54FjC1D.d.mts} +4 -0
- package/package.json +3 -3
package/dist/module.d.mts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import * as _$_nuxt_schema0 from "@nuxt/schema";
|
|
2
2
|
import { LogLevel } from "consola";
|
|
3
3
|
|
|
4
|
+
//#region ../shared/src/types.d.ts
|
|
5
|
+
type ConnectionMode = "streaming" | "polling";
|
|
6
|
+
//#endregion
|
|
4
7
|
//#region src/module.d.ts
|
|
5
8
|
interface ConfigDirectorRuntimeConfig {
|
|
6
9
|
/**
|
|
@@ -23,6 +26,40 @@ interface ConfigDirectorRuntimeConfig {
|
|
|
23
26
|
* Can be set via the NUXT_CONFIGDIRECTOR_BASE_URL environment variable.
|
|
24
27
|
*/
|
|
25
28
|
baseUrl?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Connection options for the server SDK client used during SSR and in Nitro handlers.
|
|
31
|
+
*/
|
|
32
|
+
connection?: {
|
|
33
|
+
/**
|
|
34
|
+
* The connection mode, one of `streaming` (default) or `polling`. In `streaming` mode the
|
|
35
|
+
* connection stays open and receives config updates as they happen. In `polling` mode configs
|
|
36
|
+
* are fetched once during initialization and then again on every `pollingInterval`.
|
|
37
|
+
* Can be set via the NUXT_CONFIGDIRECTOR_CONNECTION_MODE environment variable.
|
|
38
|
+
*/
|
|
39
|
+
mode?: ConnectionMode;
|
|
40
|
+
/**
|
|
41
|
+
* The polling interval in seconds when `mode` is `polling`. Has no effect in `streaming` mode.
|
|
42
|
+
* When omitted (or `0`), the server SDK default is used.
|
|
43
|
+
* Can be set via the NUXT_CONFIGDIRECTOR_CONNECTION_POLLING_INTERVAL environment variable.
|
|
44
|
+
*/
|
|
45
|
+
pollingInterval?: number;
|
|
46
|
+
/**
|
|
47
|
+
* How long, in milliseconds, the server SDK client waits for its initial config payload. Requests
|
|
48
|
+
* that arrive before the payload is received are held until it arrives or this timeout elapses,
|
|
49
|
+
* after which they render with default values. When omitted (or `0`), the server SDK default is used.
|
|
50
|
+
* Can be set via the NUXT_CONFIGDIRECTOR_CONNECTION_TIMEOUT environment variable.
|
|
51
|
+
*/
|
|
52
|
+
timeout?: number;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Whether requests that arrive before the server SDK client received its initial config payload
|
|
56
|
+
* (for example right after the server starts) wait for it, for up to the connection `timeout`, before
|
|
57
|
+
* being handled. When disabled, such requests are handled immediately and evaluate configs to their
|
|
58
|
+
* default values until the payload arrives.
|
|
59
|
+
*
|
|
60
|
+
* Defaults to `true`. Can be set via the NUXT_CONFIGDIRECTOR_WAIT_FOR_INITIALIZATION environment variable.
|
|
61
|
+
*/
|
|
62
|
+
waitForInitialization?: boolean;
|
|
26
63
|
}
|
|
27
64
|
interface ConfigDirectorPublicRuntimeConfig {
|
|
28
65
|
/**
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -11,14 +11,20 @@ var module_default = defineNuxtModule({
|
|
|
11
11
|
setup(options, nuxt) {
|
|
12
12
|
const logger = useLogger("configdirector");
|
|
13
13
|
const resolver = createResolver(import.meta.url);
|
|
14
|
-
|
|
15
|
-
serverSdkKey: "",
|
|
16
|
-
logLevel: options.logLevel,
|
|
17
|
-
baseUrl: ""
|
|
18
|
-
} : {
|
|
14
|
+
const serverDefaults = {
|
|
19
15
|
serverSdkKey: "",
|
|
20
|
-
baseUrl: ""
|
|
21
|
-
|
|
16
|
+
baseUrl: "",
|
|
17
|
+
connection: {
|
|
18
|
+
mode: "streaming",
|
|
19
|
+
pollingInterval: 0,
|
|
20
|
+
timeout: 0
|
|
21
|
+
},
|
|
22
|
+
waitForInitialization: true
|
|
23
|
+
};
|
|
24
|
+
nuxt.options.runtimeConfig.configdirector = defu(nuxt.options.runtimeConfig.configdirector, options.logLevel !== void 0 ? {
|
|
25
|
+
...serverDefaults,
|
|
26
|
+
logLevel: options.logLevel
|
|
27
|
+
} : serverDefaults);
|
|
22
28
|
nuxt.options.runtimeConfig.public.configdirector = defu(nuxt.options.runtimeConfig.public.configdirector, options.logLevel !== void 0 ? {
|
|
23
29
|
clientSdkKey: "",
|
|
24
30
|
logLevel: options.logLevel,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ClientHooks } from "../../types-
|
|
1
|
+
import { t as ClientHooks } from "../../types-BPvGEzJY.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/app/composables/useConfigDirectorClientHooks.d.ts
|
|
4
4
|
declare const useConfigDirectorClientHooks: (hooks: ClientHooks) => void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ClientHooks } from "../../types-
|
|
1
|
+
import { t as ClientHooks } from "../../types-BPvGEzJY.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/app/composables/useConfigDirectorClientHooks.d.ts
|
|
4
4
|
declare const useConfigDirectorClientHooks: (hooks: ClientHooks) => void;
|
|
@@ -2,7 +2,7 @@ import * as _$h3 from "h3";
|
|
|
2
2
|
import { ConfigDirectorClient } from "@configdirector/server-sdk";
|
|
3
3
|
|
|
4
4
|
//#region src/runtime/nitro/middleware.d.ts
|
|
5
|
-
declare const _default: _$h3.EventHandler<_$h3.EventHandlerRequest, void
|
|
5
|
+
declare const _default: _$h3.EventHandler<_$h3.EventHandlerRequest, Promise<void>>;
|
|
6
6
|
declare module "h3" {
|
|
7
7
|
interface H3EventContext {
|
|
8
8
|
configDirectorClient: ConfigDirectorClient;
|
|
@@ -2,7 +2,7 @@ import * as _$h3 from "h3";
|
|
|
2
2
|
import { ConfigDirectorClient } from "@configdirector/server-sdk";
|
|
3
3
|
|
|
4
4
|
//#region src/runtime/nitro/middleware.d.ts
|
|
5
|
-
declare const _default: _$h3.EventHandler<_$h3.EventHandlerRequest, void
|
|
5
|
+
declare const _default: _$h3.EventHandler<_$h3.EventHandlerRequest, Promise<void>>;
|
|
6
6
|
declare module "h3" {
|
|
7
7
|
interface H3EventContext {
|
|
8
8
|
configDirectorClient: ConfigDirectorClient;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { useNitroApp } from "nitropack/runtime";
|
|
1
|
+
import { useNitroApp, useRuntimeConfig } from "nitropack/runtime";
|
|
2
2
|
import { defineEventHandler } from "h3";
|
|
3
3
|
//#region src/runtime/nitro/middleware.ts
|
|
4
|
-
var middleware_default = defineEventHandler((event) => {
|
|
5
|
-
|
|
4
|
+
var middleware_default = defineEventHandler(async (event) => {
|
|
5
|
+
const nitroApp = useNitroApp();
|
|
6
|
+
if (useRuntimeConfig(event).configdirector.waitForInitialization) await nitroApp.configDirectorInitialization;
|
|
7
|
+
event.context.configDirectorClient = nitroApp.configDirectorClient;
|
|
6
8
|
});
|
|
7
9
|
//#endregion
|
|
8
10
|
export { middleware_default as default };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as ConfigDirectorClient } from "../types-
|
|
1
|
+
import { n as ConfigDirectorClient } from "../types-C54FjC1D.mjs";
|
|
2
2
|
import * as _$nitropack from "nitropack";
|
|
3
3
|
|
|
4
4
|
//#region src/runtime/nitro/plugin.d.ts
|
|
@@ -6,6 +6,7 @@ declare const _default: _$nitropack.NitroAppPlugin;
|
|
|
6
6
|
declare module "nitropack" {
|
|
7
7
|
interface NitroApp {
|
|
8
8
|
configDirectorClient: ConfigDirectorClient;
|
|
9
|
+
configDirectorInitialization: Promise<void>;
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
//#endregion
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as ConfigDirectorClient } from "../types-
|
|
1
|
+
import { n as ConfigDirectorClient } from "../types-C54FjC1D.mjs";
|
|
2
2
|
import * as _$nitropack from "nitropack";
|
|
3
3
|
|
|
4
4
|
//#region src/runtime/nitro/plugin.d.ts
|
|
@@ -6,6 +6,7 @@ declare const _default: _$nitropack.NitroAppPlugin;
|
|
|
6
6
|
declare module "nitropack" {
|
|
7
7
|
interface NitroApp {
|
|
8
8
|
configDirectorClient: ConfigDirectorClient;
|
|
9
|
+
configDirectorInitialization: Promise<void>;
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
//#endregion
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import EventEmitter, { EventEmitter as EventEmitter$1 } from "node:events";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
3
|
import { findByPointer } from "@jsonjoy.com/json-pointer";
|
|
3
4
|
import * as semver from "semver";
|
|
4
5
|
import { rapidhash } from "rapidhash-js";
|
|
5
|
-
import { createHash } from "node:crypto";
|
|
6
6
|
import { defineNitroPlugin, useRuntimeConfig } from "nitropack/runtime";
|
|
7
7
|
import { consola } from "consola";
|
|
8
8
|
//#region ../shared/src/errors.ts
|
|
@@ -177,6 +177,7 @@ var EventSourceClient = class {
|
|
|
177
177
|
DEFAULT_CALCULATE_RECONNECT_DELAY = () => this.serverReconnectionTime;
|
|
178
178
|
url;
|
|
179
179
|
headers;
|
|
180
|
+
body;
|
|
180
181
|
requestOptions;
|
|
181
182
|
lastEventId;
|
|
182
183
|
reconnectAttempt = 0;
|
|
@@ -199,8 +200,9 @@ var EventSourceClient = class {
|
|
|
199
200
|
Accept: "text/event-stream",
|
|
200
201
|
...options.headers
|
|
201
202
|
};
|
|
203
|
+
const body = options.body;
|
|
204
|
+
this.body = typeof body === "function" ? body : () => body;
|
|
202
205
|
this.requestOptions = {
|
|
203
|
-
body: options.body,
|
|
204
206
|
method: options.method ?? "GET",
|
|
205
207
|
mode: options.mode,
|
|
206
208
|
credentials: options.credentials,
|
|
@@ -256,6 +258,7 @@ var EventSourceClient = class {
|
|
|
256
258
|
this._readyState = ReadyState.CONNECTING;
|
|
257
259
|
this._fetch(this.url, {
|
|
258
260
|
...this.requestOptions,
|
|
261
|
+
body: this.body(),
|
|
259
262
|
headers: this.buildRequestHeaders(),
|
|
260
263
|
signal: this.abortController.signal,
|
|
261
264
|
cache: "no-store"
|
|
@@ -353,18 +356,44 @@ const createEventSourceClient = (options) => {
|
|
|
353
356
|
return new EventSourceClient(options);
|
|
354
357
|
};
|
|
355
358
|
//#endregion
|
|
359
|
+
//#region ../shared/src/fetchWithTimeout.ts
|
|
360
|
+
const fetchWithTimeout = async (timeout, resource, options, logger) => {
|
|
361
|
+
const abortController = new AbortController();
|
|
362
|
+
const abortTimeoutId = setTimeout(() => {
|
|
363
|
+
logger.debug("[fetchWithTimeout] Reached timeout, aborting request");
|
|
364
|
+
abortController.abort();
|
|
365
|
+
}, timeout);
|
|
366
|
+
try {
|
|
367
|
+
const response = await fetch(typeof resource === "string" || resource instanceof Request ? resource : resource.toString(), {
|
|
368
|
+
...options,
|
|
369
|
+
signal: abortController.signal
|
|
370
|
+
});
|
|
371
|
+
clearTimeout(abortTimeoutId);
|
|
372
|
+
return response;
|
|
373
|
+
} catch (error) {
|
|
374
|
+
logger.warn("[fetchWithTimeout] Fetch error: ", error);
|
|
375
|
+
clearTimeout(abortTimeoutId);
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
//#endregion
|
|
356
380
|
//#region ../js-server-sdk/src/StreamingTransport.ts
|
|
357
381
|
const MAX_EXPONENTIAL_DELAY = 9;
|
|
382
|
+
const HEARTBEAT_INTERVAL_MS = 9e4;
|
|
358
383
|
var StreamingTransport = class {
|
|
359
384
|
logger;
|
|
360
385
|
eventSource;
|
|
361
386
|
eventEmitter = new EventEmitter$1();
|
|
362
387
|
url;
|
|
388
|
+
heartbeatUrl;
|
|
389
|
+
heartbeatTimer;
|
|
390
|
+
_sessionId;
|
|
363
391
|
constructor(options) {
|
|
364
392
|
this.options = options;
|
|
365
393
|
this.options = options;
|
|
366
394
|
this.logger = options.logger;
|
|
367
395
|
this.url = new URL("server/sse/v1", options.baseUrl);
|
|
396
|
+
this.heartbeatUrl = new URL("server/heartbeat/v1", options.baseUrl);
|
|
368
397
|
}
|
|
369
398
|
async connect(timeout) {
|
|
370
399
|
if (this.eventSource) this.close();
|
|
@@ -373,10 +402,7 @@ var StreamingTransport = class {
|
|
|
373
402
|
url: this.url,
|
|
374
403
|
method: "POST",
|
|
375
404
|
headers: { "Content-Type": "application/json" },
|
|
376
|
-
body:
|
|
377
|
-
metaContext: this.options.metaContext,
|
|
378
|
-
serverSdkKey: this.options.serverSdkKey
|
|
379
|
-
}),
|
|
405
|
+
body: () => this.buildRequestBody(),
|
|
380
406
|
onMessage: ({ data }) => {
|
|
381
407
|
this.dispatchMessage(data);
|
|
382
408
|
},
|
|
@@ -386,7 +412,11 @@ var StreamingTransport = class {
|
|
|
386
412
|
},
|
|
387
413
|
shouldReconnect: (state) => {
|
|
388
414
|
const reconnect = !this.isStatusFatal(state.status);
|
|
389
|
-
if (!reconnect)
|
|
415
|
+
if (!reconnect) {
|
|
416
|
+
const error = this.prepareFatalError(state.status, state.error);
|
|
417
|
+
this.eventEmitter.emit("connectionError", error);
|
|
418
|
+
reject(error);
|
|
419
|
+
}
|
|
390
420
|
return reconnect;
|
|
391
421
|
},
|
|
392
422
|
calculateReconnectDelay: (state) => {
|
|
@@ -402,10 +432,37 @@ var StreamingTransport = class {
|
|
|
402
432
|
});
|
|
403
433
|
this.eventSource.connect();
|
|
404
434
|
});
|
|
435
|
+
this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), HEARTBEAT_INTERVAL_MS);
|
|
405
436
|
return Promise.race([eventSourcePromise, new Promise((resolve) => {
|
|
406
437
|
setTimeout(() => resolve(this), timeout);
|
|
407
438
|
})]);
|
|
408
439
|
}
|
|
440
|
+
get sessionId() {
|
|
441
|
+
return this._sessionId;
|
|
442
|
+
}
|
|
443
|
+
sendHeartbeat() {
|
|
444
|
+
if (!this.isConnected || !this._sessionId) return;
|
|
445
|
+
fetchWithTimeout(HEARTBEAT_INTERVAL_MS, this.heartbeatUrl, {
|
|
446
|
+
method: "POST",
|
|
447
|
+
headers: { "Content-Type": "application/json" },
|
|
448
|
+
body: JSON.stringify({
|
|
449
|
+
serverSdkKey: this.options.serverSdkKey,
|
|
450
|
+
sessionId: this._sessionId
|
|
451
|
+
})
|
|
452
|
+
}, this.logger).then((response) => {
|
|
453
|
+
if (!response.ok) this.logger.debug(`[StreamingTransport] Heartbeat failed with status: ${response.status}`);
|
|
454
|
+
}).catch((error) => {
|
|
455
|
+
this.logger.debug("[StreamingTransport] Heartbeat failed: ", error);
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
buildRequestBody() {
|
|
459
|
+
this._sessionId = randomUUID();
|
|
460
|
+
return JSON.stringify({
|
|
461
|
+
metaContext: this.options.metaContext,
|
|
462
|
+
serverSdkKey: this.options.serverSdkKey,
|
|
463
|
+
sessionId: this._sessionId
|
|
464
|
+
});
|
|
465
|
+
}
|
|
409
466
|
dispatchMessage(data) {
|
|
410
467
|
try {
|
|
411
468
|
const json = JSON.parse(data);
|
|
@@ -435,6 +492,8 @@ var StreamingTransport = class {
|
|
|
435
492
|
this.eventEmitter.removeAllListeners();
|
|
436
493
|
}
|
|
437
494
|
close() {
|
|
495
|
+
clearInterval(this.heartbeatTimer);
|
|
496
|
+
this.heartbeatTimer = void 0;
|
|
438
497
|
this.eventSource?.close();
|
|
439
498
|
}
|
|
440
499
|
dispose() {
|
|
@@ -459,6 +518,7 @@ var AbstractPollingTransport = class {
|
|
|
459
518
|
async handleNonOkResponse(response) {
|
|
460
519
|
if (!response.ok) if (this.isStatusFatal(response.status)) {
|
|
461
520
|
this.fatalError = true;
|
|
521
|
+
this.close();
|
|
462
522
|
throw this.prepareFatalResponseStatusError(response.status, await response.text());
|
|
463
523
|
} else throw new ConfigDirectorConnectionError(`Connection failed with status: ${response.status}`, response.status);
|
|
464
524
|
}
|
|
@@ -478,9 +538,7 @@ var AbstractPollingTransport = class {
|
|
|
478
538
|
return !!status && status >= 400 && status < 500;
|
|
479
539
|
}
|
|
480
540
|
isFetchErrorFatal(fetchError) {
|
|
481
|
-
|
|
482
|
-
else if (fetchError instanceof TypeError) return true;
|
|
483
|
-
return false;
|
|
541
|
+
return fetchError?.name === "NotAllowedError";
|
|
484
542
|
}
|
|
485
543
|
get isConnected() {
|
|
486
544
|
return this.pollingInterval !== void 0;
|
|
@@ -495,40 +553,20 @@ var AbstractPollingTransport = class {
|
|
|
495
553
|
}
|
|
496
554
|
};
|
|
497
555
|
//#endregion
|
|
498
|
-
//#region ../shared/src/fetchWithTimeout.ts
|
|
499
|
-
const fetchWithTimeout = async (timeout, resource, options, logger) => {
|
|
500
|
-
const abortController = new AbortController();
|
|
501
|
-
const abortTimeoutId = setTimeout(() => {
|
|
502
|
-
logger.debug("[fetchWithTimeout] Reached timeout, aborting request");
|
|
503
|
-
abortController.abort();
|
|
504
|
-
}, timeout);
|
|
505
|
-
try {
|
|
506
|
-
const response = await fetch(typeof resource === "string" || resource instanceof Request ? resource : resource.toString(), {
|
|
507
|
-
...options,
|
|
508
|
-
signal: abortController.signal
|
|
509
|
-
});
|
|
510
|
-
clearTimeout(abortTimeoutId);
|
|
511
|
-
return response;
|
|
512
|
-
} catch (error) {
|
|
513
|
-
logger.warn("[fetchWithTimeout] Fetch error: ", error);
|
|
514
|
-
clearTimeout(abortTimeoutId);
|
|
515
|
-
throw error;
|
|
516
|
-
}
|
|
517
|
-
};
|
|
518
|
-
//#endregion
|
|
519
556
|
//#region ../js-server-sdk/src/PollingTransport.ts
|
|
520
557
|
var PollingTransport = class extends AbstractPollingTransport {
|
|
521
558
|
logger;
|
|
522
559
|
eventEmitter = new EventEmitter$1();
|
|
523
560
|
url;
|
|
524
561
|
lastUpdateTimestamp;
|
|
562
|
+
_sessionId = randomUUID();
|
|
525
563
|
constructor(options) {
|
|
526
564
|
super();
|
|
527
565
|
this.options = options;
|
|
528
566
|
this.options = options;
|
|
529
567
|
this.logger = options.logger;
|
|
530
568
|
this.url = new URL("server/polling/v1", options.baseUrl);
|
|
531
|
-
this.pollingIntervalSeconds = options.pollingInterval ??
|
|
569
|
+
this.pollingIntervalSeconds = options.pollingInterval ?? 300;
|
|
532
570
|
}
|
|
533
571
|
async connect(timeout) {
|
|
534
572
|
this.clearPollingInterval();
|
|
@@ -543,6 +581,9 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
543
581
|
}
|
|
544
582
|
return this;
|
|
545
583
|
}
|
|
584
|
+
get sessionId() {
|
|
585
|
+
return this._sessionId;
|
|
586
|
+
}
|
|
546
587
|
async fetchConfigs(timeout) {
|
|
547
588
|
if (this.fatalError) {
|
|
548
589
|
this.logger.warn("[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.");
|
|
@@ -555,7 +596,8 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
555
596
|
body: JSON.stringify({
|
|
556
597
|
metaContext: this.options.metaContext,
|
|
557
598
|
serverSdkKey: this.options.serverSdkKey,
|
|
558
|
-
lastUpdateTimestamp: this.lastUpdateTimestamp
|
|
599
|
+
lastUpdateTimestamp: this.lastUpdateTimestamp,
|
|
600
|
+
sessionId: this._sessionId
|
|
559
601
|
})
|
|
560
602
|
}, this.logger);
|
|
561
603
|
await this.handleNonOkResponse(response);
|
|
@@ -565,7 +607,12 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
565
607
|
this.eventEmitter.emit("configBundleReceived", json);
|
|
566
608
|
}
|
|
567
609
|
} catch (fetchError) {
|
|
568
|
-
|
|
610
|
+
try {
|
|
611
|
+
this.handleFetchError(fetchError);
|
|
612
|
+
} catch (handledError) {
|
|
613
|
+
if (this.fatalError) this.eventEmitter.emit("connectionError", handledError);
|
|
614
|
+
throw handledError;
|
|
615
|
+
}
|
|
569
616
|
}
|
|
570
617
|
}
|
|
571
618
|
on(name, handler) {
|
|
@@ -580,14 +627,6 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
580
627
|
}
|
|
581
628
|
};
|
|
582
629
|
//#endregion
|
|
583
|
-
//#region ../js-server-sdk/src/OneTimeTransport.ts
|
|
584
|
-
var OneTimeTransport = class extends PollingTransport {
|
|
585
|
-
constructor(options) {
|
|
586
|
-
super(options);
|
|
587
|
-
this.pollingIntervalSeconds = 0;
|
|
588
|
-
}
|
|
589
|
-
};
|
|
590
|
-
//#endregion
|
|
591
630
|
//#region ../config-evaluator/src/text-comparison.ts
|
|
592
631
|
/**
|
|
593
632
|
* Text comparisons.
|
|
@@ -649,11 +688,11 @@ const compareNumeric = (givenValue, operator, targetValues) => {
|
|
|
649
688
|
const parseFinite = (value) => {
|
|
650
689
|
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
651
690
|
if (typeof value !== "string" || value.length === 0) return;
|
|
652
|
-
if (!isDecimalCharacters(value)) return;
|
|
691
|
+
if (!isDecimalCharacters$1(value)) return;
|
|
653
692
|
const parsed = Number(value);
|
|
654
693
|
return Number.isFinite(parsed) ? parsed : void 0;
|
|
655
694
|
};
|
|
656
|
-
const isDecimalCharacters = (value) => {
|
|
695
|
+
const isDecimalCharacters$1 = (value) => {
|
|
657
696
|
for (const character of value) if (!(character >= "0" && character <= "9") && !"+-.eE".includes(character)) return false;
|
|
658
697
|
return true;
|
|
659
698
|
};
|
|
@@ -7088,7 +7127,7 @@ const isNumericNativeType = (requestedType) => {
|
|
|
7088
7127
|
const parseConfigValue = (configState, defaultValue) => {
|
|
7089
7128
|
const value = configState.value;
|
|
7090
7129
|
const requestedType = getRequestedType(defaultValue);
|
|
7091
|
-
if (
|
|
7130
|
+
if (value == null) return {
|
|
7092
7131
|
parsedValue: defaultValue,
|
|
7093
7132
|
parsedValueId: void 0,
|
|
7094
7133
|
requestedType,
|
|
@@ -7103,7 +7142,7 @@ const parseConfigValue = (configState, defaultValue) => {
|
|
|
7103
7142
|
usedDefault: false,
|
|
7104
7143
|
reason: "found-match"
|
|
7105
7144
|
};
|
|
7106
|
-
if (typeof defaultValue === "boolean" && (configState.type === "boolean" || configState.type === "string")) {
|
|
7145
|
+
if (typeof defaultValue === "boolean" && (configState.type === "boolean" || configState.type === "string" || configState.type === "enum")) {
|
|
7107
7146
|
const boolValue = parseConfigBoolean(value);
|
|
7108
7147
|
const hasBoolean = typeof boolValue === "boolean";
|
|
7109
7148
|
return {
|
|
@@ -7137,11 +7176,11 @@ const parseConfigValue = (configState, defaultValue) => {
|
|
|
7137
7176
|
};
|
|
7138
7177
|
}
|
|
7139
7178
|
return {
|
|
7140
|
-
parsedValue:
|
|
7141
|
-
parsedValueId:
|
|
7179
|
+
parsedValue: defaultValue,
|
|
7180
|
+
parsedValueId: void 0,
|
|
7142
7181
|
requestedType,
|
|
7143
|
-
usedDefault:
|
|
7144
|
-
reason: "
|
|
7182
|
+
usedDefault: true,
|
|
7183
|
+
reason: "type-mismatch"
|
|
7145
7184
|
};
|
|
7146
7185
|
};
|
|
7147
7186
|
const parseConfigBoolean = (value) => {
|
|
@@ -7151,17 +7190,22 @@ const parseConfigBoolean = (value) => {
|
|
|
7151
7190
|
return lowerValue === "true";
|
|
7152
7191
|
};
|
|
7153
7192
|
const parseConfigInteger = (value) => {
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
if (isNaN(num) || !isFinite(num)) return;
|
|
7157
|
-
return num;
|
|
7193
|
+
const num = parseStrictNumber(value);
|
|
7194
|
+
return num == null ? void 0 : Math.trunc(num);
|
|
7158
7195
|
};
|
|
7159
7196
|
const parseConfigFloat = (value) => {
|
|
7160
|
-
|
|
7161
|
-
|
|
7197
|
+
return parseStrictNumber(value);
|
|
7198
|
+
};
|
|
7199
|
+
const parseStrictNumber = (value) => {
|
|
7200
|
+
if (!value || !isDecimalCharacters(value)) return;
|
|
7201
|
+
const num = Number(value);
|
|
7162
7202
|
if (isNaN(num) || !isFinite(num)) return;
|
|
7163
7203
|
return num;
|
|
7164
7204
|
};
|
|
7205
|
+
const isDecimalCharacters = (value) => {
|
|
7206
|
+
for (const character of value) if (!(character >= "0" && character <= "9") && !"+-.eE".includes(character)) return false;
|
|
7207
|
+
return true;
|
|
7208
|
+
};
|
|
7165
7209
|
const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
|
|
7166
7210
|
if (typeof defaultValue === "string") return {
|
|
7167
7211
|
parsedValue: jsonString,
|
|
@@ -7171,8 +7215,16 @@ const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
|
|
|
7171
7215
|
reason: "found-match"
|
|
7172
7216
|
};
|
|
7173
7217
|
try {
|
|
7218
|
+
const parsed = JSON.parse(jsonString);
|
|
7219
|
+
if (!isJsonTypeCompatible(parsed, defaultValue)) return {
|
|
7220
|
+
parsedValue: defaultValue,
|
|
7221
|
+
parsedValueId: void 0,
|
|
7222
|
+
requestedType,
|
|
7223
|
+
usedDefault: true,
|
|
7224
|
+
reason: "type-mismatch"
|
|
7225
|
+
};
|
|
7174
7226
|
return {
|
|
7175
|
-
parsedValue:
|
|
7227
|
+
parsedValue: parsed,
|
|
7176
7228
|
parsedValueId: valueId,
|
|
7177
7229
|
requestedType,
|
|
7178
7230
|
usedDefault: false,
|
|
@@ -7188,6 +7240,12 @@ const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
|
|
|
7188
7240
|
};
|
|
7189
7241
|
}
|
|
7190
7242
|
};
|
|
7243
|
+
const isJsonTypeCompatible = (parsed, defaultValue) => {
|
|
7244
|
+
if (parsed === null) return false;
|
|
7245
|
+
const defaultType = typeof defaultValue;
|
|
7246
|
+
if (defaultType === "bigint") return typeof parsed === "number";
|
|
7247
|
+
return typeof parsed === defaultType;
|
|
7248
|
+
};
|
|
7191
7249
|
//#endregion
|
|
7192
7250
|
//#region ../shared/src/logger.ts
|
|
7193
7251
|
const LEVELS = {
|
|
@@ -7456,11 +7514,25 @@ var TelemetryEventCollector = class {
|
|
|
7456
7514
|
this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), initialDelay);
|
|
7457
7515
|
}
|
|
7458
7516
|
async flushAndScheduleNext() {
|
|
7459
|
-
if (
|
|
7517
|
+
if (!this.collectEvents) return;
|
|
7518
|
+
let response;
|
|
7519
|
+
try {
|
|
7520
|
+
response = await this.flush();
|
|
7521
|
+
} catch (error) {
|
|
7522
|
+
this.logger.warn("[TelemetryEventCollector] Error flushing telemetry events: ", error);
|
|
7523
|
+
response = {
|
|
7524
|
+
success: false,
|
|
7525
|
+
fatalError: false
|
|
7526
|
+
};
|
|
7527
|
+
}
|
|
7528
|
+
if (response.fatalError) {
|
|
7460
7529
|
this.collectEvents = false;
|
|
7461
7530
|
this.close();
|
|
7462
7531
|
this.logger.warn("[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.");
|
|
7463
|
-
} else
|
|
7532
|
+
} else if (this.collectEvents) {
|
|
7533
|
+
this.cancelScheduledFlush();
|
|
7534
|
+
this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), this.flushIntervalDelay);
|
|
7535
|
+
}
|
|
7464
7536
|
}
|
|
7465
7537
|
cancelScheduledFlush() {
|
|
7466
7538
|
clearTimeout(this.flushTimeout);
|
|
@@ -7468,7 +7540,11 @@ var TelemetryEventCollector = class {
|
|
|
7468
7540
|
async close() {
|
|
7469
7541
|
this.collectEvents = false;
|
|
7470
7542
|
clearTimeout(this.flushTimeout);
|
|
7471
|
-
|
|
7543
|
+
try {
|
|
7544
|
+
await this.flush();
|
|
7545
|
+
} catch (error) {
|
|
7546
|
+
this.logger.warn("[TelemetryEventCollector] Error flushing telemetry events during close: ", error);
|
|
7547
|
+
}
|
|
7472
7548
|
this.evaluationEventQueue.clear();
|
|
7473
7549
|
}
|
|
7474
7550
|
dispose() {
|
|
@@ -7681,9 +7757,14 @@ var DefaultConfigDirectorClient = class {
|
|
|
7681
7757
|
}
|
|
7682
7758
|
this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server with ${configKeys.length} key(s)`, { keys: configKeys });
|
|
7683
7759
|
});
|
|
7760
|
+
this.transport.on("connectionError", (error) => {
|
|
7761
|
+
this.logger.error("[ConfigDirectorClient] The connection encountered an unrecoverable error and will not be retried. Configs will no longer receive updates.", error);
|
|
7762
|
+
this.eventEmitter.emit("connectionError", { error });
|
|
7763
|
+
});
|
|
7684
7764
|
this.registerHandler("clientReady", clientOptions?.hooks?.clientReady);
|
|
7685
7765
|
this.registerHandler("configEvaluated", clientOptions?.hooks?.configEvaluated);
|
|
7686
7766
|
this.registerHandler("configsUpdated", clientOptions?.hooks?.configsUpdated);
|
|
7767
|
+
this.registerHandler("connectionError", clientOptions?.hooks?.connectionError);
|
|
7687
7768
|
}
|
|
7688
7769
|
registerHandler(event, handler) {
|
|
7689
7770
|
if (!handler) return;
|
|
@@ -7717,7 +7798,6 @@ var DefaultConfigDirectorClient = class {
|
|
|
7717
7798
|
}
|
|
7718
7799
|
getTransportConstructor(mode) {
|
|
7719
7800
|
switch (mode) {
|
|
7720
|
-
case "one-time": return OneTimeTransport;
|
|
7721
7801
|
case "polling": return PollingTransport;
|
|
7722
7802
|
default: return StreamingTransport;
|
|
7723
7803
|
}
|
|
@@ -7864,6 +7944,9 @@ var DefaultConfigDirectorClient = class {
|
|
|
7864
7944
|
dispose() {
|
|
7865
7945
|
this.removeAllObservers();
|
|
7866
7946
|
this.closeConnection();
|
|
7947
|
+
this.usageEventCollector.close().catch((error) => {
|
|
7948
|
+
this.logger.warn("[ConfigDirectorClient] Error flushing telemetry while disposing the client: ", error);
|
|
7949
|
+
});
|
|
7867
7950
|
}
|
|
7868
7951
|
};
|
|
7869
7952
|
//#endregion
|
|
@@ -7884,16 +7967,21 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
|
|
|
7884
7967
|
throw new ConfigDirectorInitializationError(message);
|
|
7885
7968
|
}
|
|
7886
7969
|
logger.debug("Initializing ConfigDirector Nitro plugin");
|
|
7887
|
-
const { serverSdkKey, baseUrl } = runtimeConfig.configdirector;
|
|
7970
|
+
const { serverSdkKey, baseUrl, connection } = runtimeConfig.configdirector;
|
|
7888
7971
|
const client = new DefaultConfigDirectorClient(serverSdkKey, {
|
|
7889
7972
|
sdkName: "nuxt-sdk",
|
|
7890
|
-
sdkVersion: "1.
|
|
7973
|
+
sdkVersion: "1.3.0"
|
|
7891
7974
|
}, {
|
|
7892
7975
|
logger,
|
|
7893
|
-
|
|
7976
|
+
connection: {
|
|
7977
|
+
url: baseUrl || void 0,
|
|
7978
|
+
mode: connection?.mode || void 0,
|
|
7979
|
+
pollingInterval: connection?.pollingInterval || void 0,
|
|
7980
|
+
timeout: connection?.timeout || void 0
|
|
7981
|
+
}
|
|
7894
7982
|
});
|
|
7895
7983
|
nitroApp.configDirectorClient = client;
|
|
7896
|
-
client.initialize();
|
|
7984
|
+
nitroApp.configDirectorInitialization = client.initialize();
|
|
7897
7985
|
});
|
|
7898
7986
|
//#endregion
|
|
7899
7987
|
export { plugin_default as default };
|
|
@@ -207,6 +207,7 @@ var EventSourceClient = class {
|
|
|
207
207
|
DEFAULT_CALCULATE_RECONNECT_DELAY = () => this.serverReconnectionTime;
|
|
208
208
|
url;
|
|
209
209
|
headers;
|
|
210
|
+
body;
|
|
210
211
|
requestOptions;
|
|
211
212
|
lastEventId;
|
|
212
213
|
reconnectAttempt = 0;
|
|
@@ -229,8 +230,9 @@ var EventSourceClient = class {
|
|
|
229
230
|
Accept: "text/event-stream",
|
|
230
231
|
...options.headers
|
|
231
232
|
};
|
|
233
|
+
const body = options.body;
|
|
234
|
+
this.body = typeof body === "function" ? body : () => body;
|
|
232
235
|
this.requestOptions = {
|
|
233
|
-
body: options.body,
|
|
234
236
|
method: options.method ?? "GET",
|
|
235
237
|
mode: options.mode,
|
|
236
238
|
credentials: options.credentials,
|
|
@@ -286,6 +288,7 @@ var EventSourceClient = class {
|
|
|
286
288
|
this._readyState = ReadyState.CONNECTING;
|
|
287
289
|
this._fetch(this.url, {
|
|
288
290
|
...this.requestOptions,
|
|
291
|
+
body: this.body(),
|
|
289
292
|
headers: this.buildRequestHeaders(),
|
|
290
293
|
signal: this.abortController.signal,
|
|
291
294
|
cache: "no-store"
|
|
@@ -419,7 +422,11 @@ var StreamingTransport = class {
|
|
|
419
422
|
},
|
|
420
423
|
shouldReconnect: (state) => {
|
|
421
424
|
const reconnect = !this.isStatusFatal(state.status);
|
|
422
|
-
if (!reconnect)
|
|
425
|
+
if (!reconnect) {
|
|
426
|
+
const error = this.prepareFatalError(state.status, state.error);
|
|
427
|
+
this.eventEmitter.emit("connectionError", error);
|
|
428
|
+
reject(error);
|
|
429
|
+
}
|
|
423
430
|
return reconnect;
|
|
424
431
|
},
|
|
425
432
|
calculateReconnectDelay: (state) => {
|
|
@@ -500,7 +507,7 @@ const isNumericNativeType = (requestedType) => {
|
|
|
500
507
|
const parseConfigValue = (configState, defaultValue) => {
|
|
501
508
|
const value = configState.value;
|
|
502
509
|
const requestedType = getRequestedType(defaultValue);
|
|
503
|
-
if (
|
|
510
|
+
if (value == null) return {
|
|
504
511
|
parsedValue: defaultValue,
|
|
505
512
|
parsedValueId: void 0,
|
|
506
513
|
requestedType,
|
|
@@ -515,7 +522,7 @@ const parseConfigValue = (configState, defaultValue) => {
|
|
|
515
522
|
usedDefault: false,
|
|
516
523
|
reason: "found-match"
|
|
517
524
|
};
|
|
518
|
-
if (typeof defaultValue === "boolean" && (configState.type === "boolean" || configState.type === "string")) {
|
|
525
|
+
if (typeof defaultValue === "boolean" && (configState.type === "boolean" || configState.type === "string" || configState.type === "enum")) {
|
|
519
526
|
const boolValue = parseConfigBoolean(value);
|
|
520
527
|
const hasBoolean = typeof boolValue === "boolean";
|
|
521
528
|
return {
|
|
@@ -549,11 +556,11 @@ const parseConfigValue = (configState, defaultValue) => {
|
|
|
549
556
|
};
|
|
550
557
|
}
|
|
551
558
|
return {
|
|
552
|
-
parsedValue:
|
|
553
|
-
parsedValueId:
|
|
559
|
+
parsedValue: defaultValue,
|
|
560
|
+
parsedValueId: void 0,
|
|
554
561
|
requestedType,
|
|
555
|
-
usedDefault:
|
|
556
|
-
reason: "
|
|
562
|
+
usedDefault: true,
|
|
563
|
+
reason: "type-mismatch"
|
|
557
564
|
};
|
|
558
565
|
};
|
|
559
566
|
const parseConfigBoolean = (value) => {
|
|
@@ -563,17 +570,22 @@ const parseConfigBoolean = (value) => {
|
|
|
563
570
|
return lowerValue === "true";
|
|
564
571
|
};
|
|
565
572
|
const parseConfigInteger = (value) => {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
if (isNaN(num) || !isFinite(num)) return;
|
|
569
|
-
return num;
|
|
573
|
+
const num = parseStrictNumber(value);
|
|
574
|
+
return num == null ? void 0 : Math.trunc(num);
|
|
570
575
|
};
|
|
571
576
|
const parseConfigFloat = (value) => {
|
|
572
|
-
|
|
573
|
-
|
|
577
|
+
return parseStrictNumber(value);
|
|
578
|
+
};
|
|
579
|
+
const parseStrictNumber = (value) => {
|
|
580
|
+
if (!value || !isDecimalCharacters(value)) return;
|
|
581
|
+
const num = Number(value);
|
|
574
582
|
if (isNaN(num) || !isFinite(num)) return;
|
|
575
583
|
return num;
|
|
576
584
|
};
|
|
585
|
+
const isDecimalCharacters = (value) => {
|
|
586
|
+
for (const character of value) if (!(character >= "0" && character <= "9") && !"+-.eE".includes(character)) return false;
|
|
587
|
+
return true;
|
|
588
|
+
};
|
|
577
589
|
const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
|
|
578
590
|
if (typeof defaultValue === "string") return {
|
|
579
591
|
parsedValue: jsonString,
|
|
@@ -583,8 +595,16 @@ const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
|
|
|
583
595
|
reason: "found-match"
|
|
584
596
|
};
|
|
585
597
|
try {
|
|
598
|
+
const parsed = JSON.parse(jsonString);
|
|
599
|
+
if (!isJsonTypeCompatible(parsed, defaultValue)) return {
|
|
600
|
+
parsedValue: defaultValue,
|
|
601
|
+
parsedValueId: void 0,
|
|
602
|
+
requestedType,
|
|
603
|
+
usedDefault: true,
|
|
604
|
+
reason: "type-mismatch"
|
|
605
|
+
};
|
|
586
606
|
return {
|
|
587
|
-
parsedValue:
|
|
607
|
+
parsedValue: parsed,
|
|
588
608
|
parsedValueId: valueId,
|
|
589
609
|
requestedType,
|
|
590
610
|
usedDefault: false,
|
|
@@ -600,6 +620,12 @@ const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
|
|
|
600
620
|
};
|
|
601
621
|
}
|
|
602
622
|
};
|
|
623
|
+
const isJsonTypeCompatible = (parsed, defaultValue) => {
|
|
624
|
+
if (parsed === null) return false;
|
|
625
|
+
const defaultType = typeof defaultValue;
|
|
626
|
+
if (defaultType === "bigint") return typeof parsed === "number";
|
|
627
|
+
return typeof parsed === defaultType;
|
|
628
|
+
};
|
|
603
629
|
//#endregion
|
|
604
630
|
//#region ../shared/src/logger.ts
|
|
605
631
|
const LEVELS = {
|
|
@@ -664,6 +690,111 @@ const createDefaultLogger$1 = (level, messageDecorator) => {
|
|
|
664
690
|
return new DefaultConsoleLogger(level ?? "warn", messageDecorator ?? new LogMessageDecorator());
|
|
665
691
|
};
|
|
666
692
|
//#endregion
|
|
693
|
+
//#region ../shared/src/url.ts
|
|
694
|
+
const defaultUrlFactory = (input, base) => new URL(input, base?.toString());
|
|
695
|
+
const parseUrl = (url) => {
|
|
696
|
+
if (!url) return;
|
|
697
|
+
try {
|
|
698
|
+
return new URL(url);
|
|
699
|
+
} catch (error) {
|
|
700
|
+
throw new ConfigDirectorValidationError(`Invalid URL '${url}'. Parsing failed: ${error}`);
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
//#endregion
|
|
704
|
+
//#region ../shared/src/instance-id.ts
|
|
705
|
+
const generateInstanceId = () => {
|
|
706
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
707
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (placeholder) => {
|
|
708
|
+
const random = Math.random() * 16 | 0;
|
|
709
|
+
return (placeholder === "x" ? random : random & 3 | 8).toString(16);
|
|
710
|
+
});
|
|
711
|
+
};
|
|
712
|
+
//#endregion
|
|
713
|
+
//#region ../js-client-core/src/instance-id.ts
|
|
714
|
+
const STORAGE_KEY = "configdirector-sdk:instance-id";
|
|
715
|
+
const RECORD_VERSION = 1;
|
|
716
|
+
const MAX_AGE_MS = 360 * 60 * 1e3;
|
|
717
|
+
const CHECKSUM_SALT = "configdirector.instanceId.v1";
|
|
718
|
+
const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
719
|
+
const fnv1a = (input, offsetBasis) => {
|
|
720
|
+
let hash = offsetBasis;
|
|
721
|
+
for (let i = 0; i < input.length; i++) {
|
|
722
|
+
hash ^= input.charCodeAt(i);
|
|
723
|
+
hash = Math.imul(hash, 16777619);
|
|
724
|
+
}
|
|
725
|
+
return hash >>> 0;
|
|
726
|
+
};
|
|
727
|
+
const checksumFor = (id, createdAt) => {
|
|
728
|
+
const payload = `${CHECKSUM_SALT}|${id}|${createdAt}`;
|
|
729
|
+
const low = fnv1a(payload, 2166136261);
|
|
730
|
+
return fnv1a(`${low}|${payload}`, 16777619).toString(16).padStart(8, "0") + low.toString(16).padStart(8, "0");
|
|
731
|
+
};
|
|
732
|
+
const getLocalStorage = () => {
|
|
733
|
+
try {
|
|
734
|
+
return globalThis.localStorage ?? void 0;
|
|
735
|
+
} catch {
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
const isUsableRecord = (record) => {
|
|
740
|
+
if (!record || typeof record !== "object") return false;
|
|
741
|
+
const { v, id, createdAt, checksum } = record;
|
|
742
|
+
if (v !== RECORD_VERSION || typeof id !== "string" || typeof checksum !== "string") return false;
|
|
743
|
+
if (typeof createdAt !== "number" || !Number.isInteger(createdAt) || createdAt <= 0) return false;
|
|
744
|
+
if (!UUID_V4_PATTERN.test(id)) return false;
|
|
745
|
+
return checksum === checksumFor(id, createdAt);
|
|
746
|
+
};
|
|
747
|
+
const readStoredInstanceId = (storage, logger) => {
|
|
748
|
+
let raw;
|
|
749
|
+
try {
|
|
750
|
+
raw = storage.getItem(STORAGE_KEY);
|
|
751
|
+
} catch (error) {
|
|
752
|
+
logger?.debug("[ConfigDirectorClient] Could not read the stored instance id, generating a new one", error);
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
if (!raw) return;
|
|
756
|
+
let record;
|
|
757
|
+
try {
|
|
758
|
+
record = JSON.parse(raw);
|
|
759
|
+
} catch {
|
|
760
|
+
logger?.debug("[ConfigDirectorClient] The stored instance id is not valid JSON, generating a new one");
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
if (!isUsableRecord(record)) {
|
|
764
|
+
logger?.debug("[ConfigDirectorClient] The stored instance id failed validation, generating a new one");
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
const age = Date.now() - record.createdAt;
|
|
768
|
+
if (age < 0 || age >= MAX_AGE_MS) {
|
|
769
|
+
logger?.debug("[ConfigDirectorClient] The stored instance id has expired, generating a new one");
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
return record.id;
|
|
773
|
+
};
|
|
774
|
+
const storeInstanceId = (storage, id, logger) => {
|
|
775
|
+
const createdAt = Date.now();
|
|
776
|
+
const record = {
|
|
777
|
+
v: RECORD_VERSION,
|
|
778
|
+
id,
|
|
779
|
+
createdAt,
|
|
780
|
+
checksum: checksumFor(id, createdAt)
|
|
781
|
+
};
|
|
782
|
+
try {
|
|
783
|
+
storage.setItem(STORAGE_KEY, JSON.stringify(record));
|
|
784
|
+
} catch (error) {
|
|
785
|
+
logger?.debug("[ConfigDirectorClient] Could not persist the instance id", error);
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
const resolveInstanceId = (logger) => {
|
|
789
|
+
const storage = getLocalStorage();
|
|
790
|
+
if (!storage) return generateInstanceId();
|
|
791
|
+
const storedInstanceId = readStoredInstanceId(storage, logger);
|
|
792
|
+
if (storedInstanceId) return storedInstanceId;
|
|
793
|
+
const instanceId = generateInstanceId();
|
|
794
|
+
storeInstanceId(storage, instanceId, logger);
|
|
795
|
+
return instanceId;
|
|
796
|
+
};
|
|
797
|
+
//#endregion
|
|
667
798
|
//#region ../shared/src/transport/AbstractPollingTransport.ts
|
|
668
799
|
var AbstractPollingTransport = class {
|
|
669
800
|
fatalError = false;
|
|
@@ -680,6 +811,7 @@ var AbstractPollingTransport = class {
|
|
|
680
811
|
async handleNonOkResponse(response) {
|
|
681
812
|
if (!response.ok) if (this.isStatusFatal(response.status)) {
|
|
682
813
|
this.fatalError = true;
|
|
814
|
+
this.close();
|
|
683
815
|
throw this.prepareFatalResponseStatusError(response.status, await response.text());
|
|
684
816
|
} else throw new ConfigDirectorConnectionError(`Connection failed with status: ${response.status}`, response.status);
|
|
685
817
|
}
|
|
@@ -699,9 +831,7 @@ var AbstractPollingTransport = class {
|
|
|
699
831
|
return !!status && status >= 400 && status < 500;
|
|
700
832
|
}
|
|
701
833
|
isFetchErrorFatal(fetchError) {
|
|
702
|
-
|
|
703
|
-
else if (fetchError instanceof TypeError) return true;
|
|
704
|
-
return false;
|
|
834
|
+
return fetchError?.name === "NotAllowedError";
|
|
705
835
|
}
|
|
706
836
|
get isConnected() {
|
|
707
837
|
return this.pollingInterval !== void 0;
|
|
@@ -749,7 +879,9 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
749
879
|
this.options = options;
|
|
750
880
|
this.logger = options.logger;
|
|
751
881
|
this.url = options.resolveUrl("client/polling/v1", options.baseUrl);
|
|
752
|
-
|
|
882
|
+
const requestedInterval = options.pollingInterval ?? 300;
|
|
883
|
+
if (requestedInterval < 60) this.logger.warn(`[PollingTransport] pollingInterval of ${requestedInterval} seconds is below the minimum of 60 seconds. Using 60 seconds.`);
|
|
884
|
+
this.pollingIntervalSeconds = Math.max(requestedInterval, 60);
|
|
753
885
|
}
|
|
754
886
|
async connect(context, timeout) {
|
|
755
887
|
this.clearPollingInterval();
|
|
@@ -788,7 +920,12 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
788
920
|
this.eventEmitter.emit("configSetReceived", json);
|
|
789
921
|
}
|
|
790
922
|
} catch (fetchError) {
|
|
791
|
-
|
|
923
|
+
try {
|
|
924
|
+
this.handleFetchError(fetchError);
|
|
925
|
+
} catch (handledError) {
|
|
926
|
+
if (this.fatalError) this.eventEmitter.emit("connectionError", handledError);
|
|
927
|
+
throw handledError;
|
|
928
|
+
}
|
|
792
929
|
}
|
|
793
930
|
}
|
|
794
931
|
on(name, handler) {
|
|
@@ -802,33 +939,16 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
802
939
|
}
|
|
803
940
|
};
|
|
804
941
|
//#endregion
|
|
805
|
-
//#region ../js-client-core/src/
|
|
806
|
-
|
|
807
|
-
constructor(options) {
|
|
808
|
-
super(options);
|
|
809
|
-
this.pollingIntervalSeconds = 0;
|
|
810
|
-
}
|
|
811
|
-
};
|
|
812
|
-
//#endregion
|
|
813
|
-
//#region ../shared/src/url.ts
|
|
814
|
-
const defaultUrlFactory = (input, base) => new URL(input, base?.toString());
|
|
815
|
-
const parseUrl = (url) => {
|
|
816
|
-
if (!url) return;
|
|
942
|
+
//#region ../js-client-core/src/browser-globals.ts
|
|
943
|
+
const read = (readValue) => {
|
|
817
944
|
try {
|
|
818
|
-
return
|
|
819
|
-
} catch
|
|
820
|
-
|
|
945
|
+
return readValue(globalThis);
|
|
946
|
+
} catch {
|
|
947
|
+
return;
|
|
821
948
|
}
|
|
822
949
|
};
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
const generateInstanceId = () => {
|
|
826
|
-
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
827
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (placeholder) => {
|
|
828
|
-
const random = Math.random() * 16 | 0;
|
|
829
|
-
return (placeholder === "x" ? random : random & 3 | 8).toString(16);
|
|
830
|
-
});
|
|
831
|
-
};
|
|
950
|
+
const readUserAgent = () => read((globals) => globals.navigator?.userAgent);
|
|
951
|
+
const readHost = () => read((globals) => globals.location?.host);
|
|
832
952
|
//#endregion
|
|
833
953
|
//#region ../js-client-core/src/DefaultConfigDirectorClient.ts
|
|
834
954
|
const MAX_EXPONENTIAL_DELAY = 9;
|
|
@@ -849,8 +969,8 @@ var DefaultConfigDirectorClient = class {
|
|
|
849
969
|
connectionMode;
|
|
850
970
|
instanceId;
|
|
851
971
|
constructor(telemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
|
|
852
|
-
this.instanceId = generateInstanceId();
|
|
853
972
|
this.logger = clientOptions?.logger ?? createDefaultLogger$1();
|
|
973
|
+
this.instanceId = resolveInstanceId(this.logger);
|
|
854
974
|
this.timeout = clientOptions?.connection?.timeout ?? 3e3;
|
|
855
975
|
this.validateSdkKeyPresence(clientSdkKey);
|
|
856
976
|
const urlFactory = internalClientOptions?.urlFactory ?? defaultUrlFactory;
|
|
@@ -867,7 +987,8 @@ var DefaultConfigDirectorClient = class {
|
|
|
867
987
|
...clientOptions?.metadata,
|
|
868
988
|
sdkName: sdkOptions.sdkName,
|
|
869
989
|
sdkVersion: sdkOptions.sdkVersion,
|
|
870
|
-
userAgent:
|
|
990
|
+
userAgent: readUserAgent(),
|
|
991
|
+
host: readHost()
|
|
871
992
|
},
|
|
872
993
|
logger: this.logger,
|
|
873
994
|
fetch: internalClientOptions?.fetch,
|
|
@@ -893,10 +1014,15 @@ var DefaultConfigDirectorClient = class {
|
|
|
893
1014
|
}
|
|
894
1015
|
this.logger.debug("[ConfigDirectorClient] ConfigSet updated from server:", { keys: configKeys });
|
|
895
1016
|
});
|
|
1017
|
+
this.transport.on("connectionError", (error) => {
|
|
1018
|
+
this.logger.error("[ConfigDirectorClient] The connection encountered an unrecoverable error and will not be retried. Configs will no longer receive updates.", error);
|
|
1019
|
+
this.emit("connectionError", { error });
|
|
1020
|
+
});
|
|
896
1021
|
this.registerHandler("clientReady", clientOptions?.hooks?.clientReady);
|
|
897
1022
|
this.registerHandler("configEvaluated", clientOptions?.hooks?.configEvaluated);
|
|
898
1023
|
this.registerHandler("configsUpdated", clientOptions?.hooks?.configsUpdated);
|
|
899
1024
|
this.registerHandler("contextUpdated", clientOptions?.hooks?.contextUpdated);
|
|
1025
|
+
this.registerHandler("connectionError", clientOptions?.hooks?.connectionError);
|
|
900
1026
|
}
|
|
901
1027
|
registerHandler(event, handler) {
|
|
902
1028
|
if (!handler) return;
|
|
@@ -905,7 +1031,6 @@ var DefaultConfigDirectorClient = class {
|
|
|
905
1031
|
}
|
|
906
1032
|
getTransportConstructor(connectionMode) {
|
|
907
1033
|
switch (connectionMode) {
|
|
908
|
-
case "one-time": return OneTimeTransport;
|
|
909
1034
|
case "polling": return PollingTransport;
|
|
910
1035
|
default: return StreamingTransport;
|
|
911
1036
|
}
|
|
@@ -925,7 +1050,8 @@ var DefaultConfigDirectorClient = class {
|
|
|
925
1050
|
this.ready = false;
|
|
926
1051
|
this.readyPromise = new Promise((resolve) => {
|
|
927
1052
|
this.readyResolve = resolve;
|
|
928
|
-
}).then(() => {
|
|
1053
|
+
}).then((cancelled) => {
|
|
1054
|
+
if (cancelled) return;
|
|
929
1055
|
this.ready = true;
|
|
930
1056
|
this.initializing = false;
|
|
931
1057
|
this.emit("clientReady", { action: caller });
|
|
@@ -1085,10 +1211,11 @@ var DefaultConfigDirectorClient = class {
|
|
|
1085
1211
|
async close() {
|
|
1086
1212
|
this.logger.debug("[ConfigDirectorClient] close() has been called, closing connection to server");
|
|
1087
1213
|
clearTimeout(this.timeoutTimer);
|
|
1088
|
-
this.readyResolve?.();
|
|
1214
|
+
this.readyResolve?.(true);
|
|
1089
1215
|
this.telemetryClient.close();
|
|
1090
1216
|
this.transport.close();
|
|
1091
1217
|
this.ready = false;
|
|
1218
|
+
this.initializing = false;
|
|
1092
1219
|
}
|
|
1093
1220
|
dispose() {
|
|
1094
1221
|
this.clear();
|
|
@@ -1122,14 +1249,18 @@ const mapToTelemetryValue = (ev) => {
|
|
|
1122
1249
|
};
|
|
1123
1250
|
//#endregion
|
|
1124
1251
|
//#region ../js-browser-client/src/telemetry/WebWorkerTelemetryClient.ts
|
|
1252
|
+
const DEFAULT_WORKER_CLOSE_TIMEOUT = 6e3;
|
|
1125
1253
|
var WebWorkerTelemetryClient = class {
|
|
1126
1254
|
logger;
|
|
1127
1255
|
worker;
|
|
1128
1256
|
closePromise;
|
|
1129
1257
|
closeResolve;
|
|
1258
|
+
closeTimer;
|
|
1259
|
+
workerCloseTimeout;
|
|
1130
1260
|
visibilityHandler;
|
|
1131
1261
|
constructor(options) {
|
|
1132
1262
|
this.logger = options.logger;
|
|
1263
|
+
this.workerCloseTimeout = options.workerCloseTimeout ?? DEFAULT_WORKER_CLOSE_TIMEOUT;
|
|
1133
1264
|
const initializeMessage = {
|
|
1134
1265
|
type: "Initialize",
|
|
1135
1266
|
payload: {
|
|
@@ -1141,7 +1272,7 @@ var WebWorkerTelemetryClient = class {
|
|
|
1141
1272
|
flushIntervalDelay: options.flushIntervalDelay ?? 3e4
|
|
1142
1273
|
}
|
|
1143
1274
|
};
|
|
1144
|
-
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}},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(){(await this.flush()).fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay)}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout)
|
|
1275
|
+
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}},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" })));
|
|
1145
1276
|
this.worker.onmessage = (message) => {
|
|
1146
1277
|
this.handleWorkerEvent(message.data);
|
|
1147
1278
|
};
|
|
@@ -1185,7 +1316,7 @@ var WebWorkerTelemetryClient = class {
|
|
|
1185
1316
|
}
|
|
1186
1317
|
handleWorkerEvent(event) {
|
|
1187
1318
|
if (event.type === "Closed") {
|
|
1188
|
-
this.
|
|
1319
|
+
this.finishClose();
|
|
1189
1320
|
return;
|
|
1190
1321
|
}
|
|
1191
1322
|
if (!event.payload?.level || !event.payload?.message) return;
|
|
@@ -1201,7 +1332,7 @@ var WebWorkerTelemetryClient = class {
|
|
|
1201
1332
|
this.logger.warn(message, ...args);
|
|
1202
1333
|
break;
|
|
1203
1334
|
case "error":
|
|
1204
|
-
this.logger.
|
|
1335
|
+
this.logger.error(message, ...args);
|
|
1205
1336
|
break;
|
|
1206
1337
|
}
|
|
1207
1338
|
}
|
|
@@ -1213,13 +1344,27 @@ var WebWorkerTelemetryClient = class {
|
|
|
1213
1344
|
return this.performClose();
|
|
1214
1345
|
}
|
|
1215
1346
|
performClose() {
|
|
1216
|
-
|
|
1347
|
+
try {
|
|
1348
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
this.logger.warn("[WebWorkerTelemetryClient] Could not remove 'visibilitychange' listener: ", error);
|
|
1351
|
+
}
|
|
1217
1352
|
this.closePromise = new Promise((resolve) => {
|
|
1218
1353
|
this.closeResolve = resolve;
|
|
1354
|
+
this.closeTimer = setTimeout(() => {
|
|
1355
|
+
this.logger.warn("[WebWorkerTelemetryClient] Timed out waiting for the telemetry worker to close. Terminating the worker.");
|
|
1356
|
+
this.finishClose();
|
|
1357
|
+
}, this.workerCloseTimeout);
|
|
1219
1358
|
this.worker.postMessage({ type: "Close" });
|
|
1220
1359
|
});
|
|
1221
1360
|
return this.closePromise;
|
|
1222
1361
|
}
|
|
1362
|
+
finishClose() {
|
|
1363
|
+
clearTimeout(this.closeTimer);
|
|
1364
|
+
this.closeTimer = void 0;
|
|
1365
|
+
this.worker.terminate();
|
|
1366
|
+
this.closeResolve?.();
|
|
1367
|
+
}
|
|
1223
1368
|
};
|
|
1224
1369
|
//#endregion
|
|
1225
1370
|
//#region ../shared/src/base62-encoder.ts
|
|
@@ -1241,6 +1386,7 @@ const BASE62_LENGTH = Math.ceil(128 / Math.log2(62));
|
|
|
1241
1386
|
//#region ../js-browser-client/src/telemetry/value-id-generator.ts
|
|
1242
1387
|
const encoder = new TextEncoder();
|
|
1243
1388
|
const generateValueId = async (v) => {
|
|
1389
|
+
if (typeof crypto === "undefined" || crypto.subtle == null) return;
|
|
1244
1390
|
const hashBuffer = await crypto.subtle.digest("SHA-256", encoder.encode(v?.toString() ?? ""));
|
|
1245
1391
|
return toBase62(new Uint8Array(hashBuffer, 0, 16), BASE62_LENGTH);
|
|
1246
1392
|
};
|
|
@@ -1317,7 +1463,7 @@ var plugin_client_default = defineNuxtPlugin((nuxtApp) => {
|
|
|
1317
1463
|
const { clientSdkKey, appName, appVersion, baseUrl } = runtimeConfig.public.configdirector;
|
|
1318
1464
|
const client = createBrowserClient(clientSdkKey, {
|
|
1319
1465
|
sdkName: "nuxt-sdk",
|
|
1320
|
-
sdkVersion: "1.
|
|
1466
|
+
sdkVersion: "1.3.0"
|
|
1321
1467
|
}, {
|
|
1322
1468
|
metadata: {
|
|
1323
1469
|
appName,
|
|
@@ -83,15 +83,11 @@ var SsrClient = class {
|
|
|
83
83
|
getValue(configKey, defaultValue) {
|
|
84
84
|
return this.serverClient.getValue(configKey, defaultValue, this.currentContext);
|
|
85
85
|
}
|
|
86
|
-
watch(
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
unwatch(configKey, callback) {
|
|
90
|
-
return this.serverClient.unwatch(configKey, callback);
|
|
91
|
-
}
|
|
92
|
-
unwatchAll() {
|
|
93
|
-
return this.serverClient.unwatchAll();
|
|
86
|
+
watch() {
|
|
87
|
+
return () => {};
|
|
94
88
|
}
|
|
89
|
+
unwatch() {}
|
|
90
|
+
unwatchAll() {}
|
|
95
91
|
pauseNetwork() {}
|
|
96
92
|
async resumeNetwork() {}
|
|
97
93
|
dispose() {}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as ConfigDirectorClient } from "../../types-
|
|
1
|
+
import { n as ConfigDirectorClient } from "../../types-C54FjC1D.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/server/composables/useConfigDirectorClient.d.ts
|
|
4
4
|
declare const useConfigDirectorClient: () => ConfigDirectorClient;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as ConfigDirectorClient } from "../../types-
|
|
1
|
+
import { n as ConfigDirectorClient } from "../../types-C54FjC1D.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/server/composables/useConfigDirectorClient.d.ts
|
|
4
4
|
declare const useConfigDirectorClient: () => ConfigDirectorClient;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ClientHooks } from "../../types-
|
|
1
|
+
import { t as ClientHooks } from "../../types-C54FjC1D.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/server/composables/useConfigDirectorServerHooks.d.ts
|
|
4
4
|
declare const useConfigDirectorServerHooks: (hooks: ClientHooks) => void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ClientHooks } from "../../types-
|
|
1
|
+
import { t as ClientHooks } from "../../types-C54FjC1D.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/server/composables/useConfigDirectorServerHooks.d.ts
|
|
4
4
|
declare const useConfigDirectorServerHooks: (hooks: ClientHooks) => void;
|
|
@@ -16,6 +16,7 @@ type ClientHooks = {
|
|
|
16
16
|
configsUpdated?: HookHandler<"configsUpdated"> | HookHandler<"configsUpdated">[];
|
|
17
17
|
contextUpdated?: HookHandler<"contextUpdated"> | HookHandler<"contextUpdated">[];
|
|
18
18
|
configEvaluated?: HookHandler<"configEvaluated"> | HookHandler<"configEvaluated">[];
|
|
19
|
+
connectionError?: HookHandler<"connectionError"> | HookHandler<"connectionError">[];
|
|
19
20
|
};
|
|
20
21
|
type ClientConnectAction = "initialization" | "context update" | "network resume";
|
|
21
22
|
type HookHandler<TEvent extends keyof ClientEvents> = (payload: ClientEvents[TEvent]) => void;
|
|
@@ -32,6 +33,9 @@ type ClientEvents = {
|
|
|
32
33
|
configEvaluated: {
|
|
33
34
|
evaluation: ConfigEvaluation;
|
|
34
35
|
};
|
|
36
|
+
connectionError: {
|
|
37
|
+
error: Error;
|
|
38
|
+
};
|
|
35
39
|
};
|
|
36
40
|
type WatchHandler<T extends ConfigValueType> = (message: T) => void;
|
|
37
41
|
/**
|
|
@@ -9,11 +9,15 @@ type ClientEvents = {
|
|
|
9
9
|
configEvaluated: {
|
|
10
10
|
evaluation: ConfigEvaluation;
|
|
11
11
|
};
|
|
12
|
+
connectionError: {
|
|
13
|
+
error: Error;
|
|
14
|
+
};
|
|
12
15
|
};
|
|
13
16
|
type ClientHooks = {
|
|
14
17
|
clientReady?: HookHandler<"clientReady"> | HookHandler<"clientReady">[];
|
|
15
18
|
configsUpdated?: HookHandler<"configsUpdated"> | HookHandler<"configsUpdated">[];
|
|
16
19
|
configEvaluated?: HookHandler<"configEvaluated"> | HookHandler<"configEvaluated">[];
|
|
20
|
+
connectionError?: HookHandler<"connectionError"> | HookHandler<"connectionError">[];
|
|
17
21
|
};
|
|
18
22
|
type HookHandler<TEvent extends keyof ClientEvents> = (payload: ClientEvents[TEvent]) => void;
|
|
19
23
|
type WatchHandler<T extends ConfigValueType> = (message: T) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@configdirector/nuxt-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Nuxt SDK for ConfigDirector. ConfigDirector is a remote configuration and feature flag service.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ConfigDirector",
|
|
@@ -58,14 +58,14 @@
|
|
|
58
58
|
"semver": "^7.7.4"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@nuxt/devtools": "^3.2
|
|
61
|
+
"@nuxt/devtools": "^3.4.2",
|
|
62
62
|
"@nuxt/eslint-config": "^1.15.2",
|
|
63
63
|
"@nuxt/schema": "4.4.8",
|
|
64
64
|
"@nuxt/test-utils": "^4.0.2",
|
|
65
65
|
"@types/node": "^24.13.3",
|
|
66
66
|
"changelogen": "^0.6.2",
|
|
67
67
|
"eslint": "^10.7.0",
|
|
68
|
-
"nuxt": "^4.
|
|
68
|
+
"nuxt": "^4.5.2",
|
|
69
69
|
"playwright": "^1.60.0",
|
|
70
70
|
"rimraf": "^6.1.3",
|
|
71
71
|
"tsdown": "^0.21.7",
|