@configdirector/nuxt-sdk 0.10.0 → 1.2.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.json +1 -1
- 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/plugin.d.mts +1 -1
- package/dist/runtime/nitro/plugin.d.ts +1 -1
- package/dist/runtime/nitro/plugin.mjs +146 -72
- package/dist/runtime/plugin.client.mjs +102 -55
- 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.json
CHANGED
|
@@ -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;
|
|
@@ -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.
|
|
@@ -610,18 +649,9 @@ const compareText = (value, operator, targetValues) => {
|
|
|
610
649
|
case "does not start with any of": return !targetValues.some((target) => value.startsWith(target));
|
|
611
650
|
case "ends with any of": return targetValues.some((target) => value.endsWith(target));
|
|
612
651
|
case "does not end with any of": return !targetValues.some((target) => value.endsWith(target));
|
|
613
|
-
case "matches regex": return first !== void 0 && matchesRegex(first, value);
|
|
614
|
-
case "does not match regex": return first !== void 0 && !matchesRegex(first, value);
|
|
615
652
|
default: return false;
|
|
616
653
|
}
|
|
617
654
|
};
|
|
618
|
-
const matchesRegex = (regexString, value) => {
|
|
619
|
-
try {
|
|
620
|
-
return new RegExp(regexString).test(value);
|
|
621
|
-
} catch {
|
|
622
|
-
return false;
|
|
623
|
-
}
|
|
624
|
-
};
|
|
625
655
|
//#endregion
|
|
626
656
|
//#region ../config-evaluator/src/numeric-comparison.ts
|
|
627
657
|
/**
|
|
@@ -658,11 +688,11 @@ const compareNumeric = (givenValue, operator, targetValues) => {
|
|
|
658
688
|
const parseFinite = (value) => {
|
|
659
689
|
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
660
690
|
if (typeof value !== "string" || value.length === 0) return;
|
|
661
|
-
if (!isDecimalCharacters(value)) return;
|
|
691
|
+
if (!isDecimalCharacters$1(value)) return;
|
|
662
692
|
const parsed = Number(value);
|
|
663
693
|
return Number.isFinite(parsed) ? parsed : void 0;
|
|
664
694
|
};
|
|
665
|
-
const isDecimalCharacters = (value) => {
|
|
695
|
+
const isDecimalCharacters$1 = (value) => {
|
|
666
696
|
for (const character of value) if (!(character >= "0" && character <= "9") && !"+-.eE".includes(character)) return false;
|
|
667
697
|
return true;
|
|
668
698
|
};
|
|
@@ -7052,7 +7082,7 @@ var ConfigEvaluator = class {
|
|
|
7052
7082
|
let sum = 0;
|
|
7053
7083
|
let bucket = void 0;
|
|
7054
7084
|
for (const percentage of percentages) {
|
|
7055
|
-
if (assignedPercentage
|
|
7085
|
+
if (assignedPercentage < percentage.percentage + sum) {
|
|
7056
7086
|
bucket = percentage;
|
|
7057
7087
|
break;
|
|
7058
7088
|
}
|
|
@@ -7097,7 +7127,7 @@ const isNumericNativeType = (requestedType) => {
|
|
|
7097
7127
|
const parseConfigValue = (configState, defaultValue) => {
|
|
7098
7128
|
const value = configState.value;
|
|
7099
7129
|
const requestedType = getRequestedType(defaultValue);
|
|
7100
|
-
if (
|
|
7130
|
+
if (value == null) return {
|
|
7101
7131
|
parsedValue: defaultValue,
|
|
7102
7132
|
parsedValueId: void 0,
|
|
7103
7133
|
requestedType,
|
|
@@ -7112,7 +7142,7 @@ const parseConfigValue = (configState, defaultValue) => {
|
|
|
7112
7142
|
usedDefault: false,
|
|
7113
7143
|
reason: "found-match"
|
|
7114
7144
|
};
|
|
7115
|
-
if (typeof defaultValue === "boolean" && (configState.type === "boolean" || configState.type === "string")) {
|
|
7145
|
+
if (typeof defaultValue === "boolean" && (configState.type === "boolean" || configState.type === "string" || configState.type === "enum")) {
|
|
7116
7146
|
const boolValue = parseConfigBoolean(value);
|
|
7117
7147
|
const hasBoolean = typeof boolValue === "boolean";
|
|
7118
7148
|
return {
|
|
@@ -7146,11 +7176,11 @@ const parseConfigValue = (configState, defaultValue) => {
|
|
|
7146
7176
|
};
|
|
7147
7177
|
}
|
|
7148
7178
|
return {
|
|
7149
|
-
parsedValue:
|
|
7150
|
-
parsedValueId:
|
|
7179
|
+
parsedValue: defaultValue,
|
|
7180
|
+
parsedValueId: void 0,
|
|
7151
7181
|
requestedType,
|
|
7152
|
-
usedDefault:
|
|
7153
|
-
reason: "
|
|
7182
|
+
usedDefault: true,
|
|
7183
|
+
reason: "type-mismatch"
|
|
7154
7184
|
};
|
|
7155
7185
|
};
|
|
7156
7186
|
const parseConfigBoolean = (value) => {
|
|
@@ -7160,17 +7190,22 @@ const parseConfigBoolean = (value) => {
|
|
|
7160
7190
|
return lowerValue === "true";
|
|
7161
7191
|
};
|
|
7162
7192
|
const parseConfigInteger = (value) => {
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
if (isNaN(num) || !isFinite(num)) return;
|
|
7166
|
-
return num;
|
|
7193
|
+
const num = parseStrictNumber(value);
|
|
7194
|
+
return num == null ? void 0 : Math.trunc(num);
|
|
7167
7195
|
};
|
|
7168
7196
|
const parseConfigFloat = (value) => {
|
|
7169
|
-
|
|
7170
|
-
|
|
7197
|
+
return parseStrictNumber(value);
|
|
7198
|
+
};
|
|
7199
|
+
const parseStrictNumber = (value) => {
|
|
7200
|
+
if (!value || !isDecimalCharacters(value)) return;
|
|
7201
|
+
const num = Number(value);
|
|
7171
7202
|
if (isNaN(num) || !isFinite(num)) return;
|
|
7172
7203
|
return num;
|
|
7173
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
|
+
};
|
|
7174
7209
|
const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
|
|
7175
7210
|
if (typeof defaultValue === "string") return {
|
|
7176
7211
|
parsedValue: jsonString,
|
|
@@ -7180,8 +7215,16 @@ const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
|
|
|
7180
7215
|
reason: "found-match"
|
|
7181
7216
|
};
|
|
7182
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
|
+
};
|
|
7183
7226
|
return {
|
|
7184
|
-
parsedValue:
|
|
7227
|
+
parsedValue: parsed,
|
|
7185
7228
|
parsedValueId: valueId,
|
|
7186
7229
|
requestedType,
|
|
7187
7230
|
usedDefault: false,
|
|
@@ -7197,6 +7240,12 @@ const parseJson = (jsonString, defaultValue, requestedType, valueId) => {
|
|
|
7197
7240
|
};
|
|
7198
7241
|
}
|
|
7199
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
|
+
};
|
|
7200
7249
|
//#endregion
|
|
7201
7250
|
//#region ../shared/src/logger.ts
|
|
7202
7251
|
const LEVELS = {
|
|
@@ -7465,11 +7514,25 @@ var TelemetryEventCollector = class {
|
|
|
7465
7514
|
this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), initialDelay);
|
|
7466
7515
|
}
|
|
7467
7516
|
async flushAndScheduleNext() {
|
|
7468
|
-
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) {
|
|
7469
7529
|
this.collectEvents = false;
|
|
7470
7530
|
this.close();
|
|
7471
7531
|
this.logger.warn("[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.");
|
|
7472
|
-
} else
|
|
7532
|
+
} else if (this.collectEvents) {
|
|
7533
|
+
this.cancelScheduledFlush();
|
|
7534
|
+
this.flushTimeout = setTimeout(() => this.flushAndScheduleNext(), this.flushIntervalDelay);
|
|
7535
|
+
}
|
|
7473
7536
|
}
|
|
7474
7537
|
cancelScheduledFlush() {
|
|
7475
7538
|
clearTimeout(this.flushTimeout);
|
|
@@ -7477,7 +7540,11 @@ var TelemetryEventCollector = class {
|
|
|
7477
7540
|
async close() {
|
|
7478
7541
|
this.collectEvents = false;
|
|
7479
7542
|
clearTimeout(this.flushTimeout);
|
|
7480
|
-
|
|
7543
|
+
try {
|
|
7544
|
+
await this.flush();
|
|
7545
|
+
} catch (error) {
|
|
7546
|
+
this.logger.warn("[TelemetryEventCollector] Error flushing telemetry events during close: ", error);
|
|
7547
|
+
}
|
|
7481
7548
|
this.evaluationEventQueue.clear();
|
|
7482
7549
|
}
|
|
7483
7550
|
dispose() {
|
|
@@ -7690,9 +7757,14 @@ var DefaultConfigDirectorClient = class {
|
|
|
7690
7757
|
}
|
|
7691
7758
|
this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server with ${configKeys.length} key(s)`, { keys: configKeys });
|
|
7692
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
|
+
});
|
|
7693
7764
|
this.registerHandler("clientReady", clientOptions?.hooks?.clientReady);
|
|
7694
7765
|
this.registerHandler("configEvaluated", clientOptions?.hooks?.configEvaluated);
|
|
7695
7766
|
this.registerHandler("configsUpdated", clientOptions?.hooks?.configsUpdated);
|
|
7767
|
+
this.registerHandler("connectionError", clientOptions?.hooks?.connectionError);
|
|
7696
7768
|
}
|
|
7697
7769
|
registerHandler(event, handler) {
|
|
7698
7770
|
if (!handler) return;
|
|
@@ -7726,7 +7798,6 @@ var DefaultConfigDirectorClient = class {
|
|
|
7726
7798
|
}
|
|
7727
7799
|
getTransportConstructor(mode) {
|
|
7728
7800
|
switch (mode) {
|
|
7729
|
-
case "one-time": return OneTimeTransport;
|
|
7730
7801
|
case "polling": return PollingTransport;
|
|
7731
7802
|
default: return StreamingTransport;
|
|
7732
7803
|
}
|
|
@@ -7873,6 +7944,9 @@ var DefaultConfigDirectorClient = class {
|
|
|
7873
7944
|
dispose() {
|
|
7874
7945
|
this.removeAllObservers();
|
|
7875
7946
|
this.closeConnection();
|
|
7947
|
+
this.usageEventCollector.close().catch((error) => {
|
|
7948
|
+
this.logger.warn("[ConfigDirectorClient] Error flushing telemetry while disposing the client: ", error);
|
|
7949
|
+
});
|
|
7876
7950
|
}
|
|
7877
7951
|
};
|
|
7878
7952
|
//#endregion
|
|
@@ -7896,7 +7970,7 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
|
|
|
7896
7970
|
const { serverSdkKey, baseUrl } = runtimeConfig.configdirector;
|
|
7897
7971
|
const client = new DefaultConfigDirectorClient(serverSdkKey, {
|
|
7898
7972
|
sdkName: "nuxt-sdk",
|
|
7899
|
-
sdkVersion: "
|
|
7973
|
+
sdkVersion: "1.2.0"
|
|
7900
7974
|
}, {
|
|
7901
7975
|
logger,
|
|
7902
7976
|
...baseUrl ? { connection: { url: baseUrl } } : {}
|
|
@@ -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,26 @@ 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
|
|
667
713
|
//#region ../shared/src/transport/AbstractPollingTransport.ts
|
|
668
714
|
var AbstractPollingTransport = class {
|
|
669
715
|
fatalError = false;
|
|
@@ -680,6 +726,7 @@ var AbstractPollingTransport = class {
|
|
|
680
726
|
async handleNonOkResponse(response) {
|
|
681
727
|
if (!response.ok) if (this.isStatusFatal(response.status)) {
|
|
682
728
|
this.fatalError = true;
|
|
729
|
+
this.close();
|
|
683
730
|
throw this.prepareFatalResponseStatusError(response.status, await response.text());
|
|
684
731
|
} else throw new ConfigDirectorConnectionError(`Connection failed with status: ${response.status}`, response.status);
|
|
685
732
|
}
|
|
@@ -699,9 +746,7 @@ var AbstractPollingTransport = class {
|
|
|
699
746
|
return !!status && status >= 400 && status < 500;
|
|
700
747
|
}
|
|
701
748
|
isFetchErrorFatal(fetchError) {
|
|
702
|
-
|
|
703
|
-
else if (fetchError instanceof TypeError) return true;
|
|
704
|
-
return false;
|
|
749
|
+
return fetchError?.name === "NotAllowedError";
|
|
705
750
|
}
|
|
706
751
|
get isConnected() {
|
|
707
752
|
return this.pollingInterval !== void 0;
|
|
@@ -788,7 +833,12 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
788
833
|
this.eventEmitter.emit("configSetReceived", json);
|
|
789
834
|
}
|
|
790
835
|
} catch (fetchError) {
|
|
791
|
-
|
|
836
|
+
try {
|
|
837
|
+
this.handleFetchError(fetchError);
|
|
838
|
+
} catch (handledError) {
|
|
839
|
+
if (this.fatalError) this.eventEmitter.emit("connectionError", handledError);
|
|
840
|
+
throw handledError;
|
|
841
|
+
}
|
|
792
842
|
}
|
|
793
843
|
}
|
|
794
844
|
on(name, handler) {
|
|
@@ -802,34 +852,6 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
802
852
|
}
|
|
803
853
|
};
|
|
804
854
|
//#endregion
|
|
805
|
-
//#region ../js-client-core/src/OneTimeTransport.ts
|
|
806
|
-
var OneTimeTransport = class extends PollingTransport {
|
|
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;
|
|
817
|
-
try {
|
|
818
|
-
return new URL(url);
|
|
819
|
-
} catch (error) {
|
|
820
|
-
throw new ConfigDirectorValidationError(`Invalid URL '${url}'. Parsing failed: ${error}`);
|
|
821
|
-
}
|
|
822
|
-
};
|
|
823
|
-
//#endregion
|
|
824
|
-
//#region ../shared/src/instance-id.ts
|
|
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
|
-
};
|
|
832
|
-
//#endregion
|
|
833
855
|
//#region ../js-client-core/src/DefaultConfigDirectorClient.ts
|
|
834
856
|
const MAX_EXPONENTIAL_DELAY = 9;
|
|
835
857
|
var DefaultConfigDirectorClient = class {
|
|
@@ -893,10 +915,15 @@ var DefaultConfigDirectorClient = class {
|
|
|
893
915
|
}
|
|
894
916
|
this.logger.debug("[ConfigDirectorClient] ConfigSet updated from server:", { keys: configKeys });
|
|
895
917
|
});
|
|
918
|
+
this.transport.on("connectionError", (error) => {
|
|
919
|
+
this.logger.error("[ConfigDirectorClient] The connection encountered an unrecoverable error and will not be retried. Configs will no longer receive updates.", error);
|
|
920
|
+
this.emit("connectionError", { error });
|
|
921
|
+
});
|
|
896
922
|
this.registerHandler("clientReady", clientOptions?.hooks?.clientReady);
|
|
897
923
|
this.registerHandler("configEvaluated", clientOptions?.hooks?.configEvaluated);
|
|
898
924
|
this.registerHandler("configsUpdated", clientOptions?.hooks?.configsUpdated);
|
|
899
925
|
this.registerHandler("contextUpdated", clientOptions?.hooks?.contextUpdated);
|
|
926
|
+
this.registerHandler("connectionError", clientOptions?.hooks?.connectionError);
|
|
900
927
|
}
|
|
901
928
|
registerHandler(event, handler) {
|
|
902
929
|
if (!handler) return;
|
|
@@ -905,7 +932,6 @@ var DefaultConfigDirectorClient = class {
|
|
|
905
932
|
}
|
|
906
933
|
getTransportConstructor(connectionMode) {
|
|
907
934
|
switch (connectionMode) {
|
|
908
|
-
case "one-time": return OneTimeTransport;
|
|
909
935
|
case "polling": return PollingTransport;
|
|
910
936
|
default: return StreamingTransport;
|
|
911
937
|
}
|
|
@@ -925,7 +951,8 @@ var DefaultConfigDirectorClient = class {
|
|
|
925
951
|
this.ready = false;
|
|
926
952
|
this.readyPromise = new Promise((resolve) => {
|
|
927
953
|
this.readyResolve = resolve;
|
|
928
|
-
}).then(() => {
|
|
954
|
+
}).then((cancelled) => {
|
|
955
|
+
if (cancelled) return;
|
|
929
956
|
this.ready = true;
|
|
930
957
|
this.initializing = false;
|
|
931
958
|
this.emit("clientReady", { action: caller });
|
|
@@ -1085,10 +1112,11 @@ var DefaultConfigDirectorClient = class {
|
|
|
1085
1112
|
async close() {
|
|
1086
1113
|
this.logger.debug("[ConfigDirectorClient] close() has been called, closing connection to server");
|
|
1087
1114
|
clearTimeout(this.timeoutTimer);
|
|
1088
|
-
this.readyResolve?.();
|
|
1115
|
+
this.readyResolve?.(true);
|
|
1089
1116
|
this.telemetryClient.close();
|
|
1090
1117
|
this.transport.close();
|
|
1091
1118
|
this.ready = false;
|
|
1119
|
+
this.initializing = false;
|
|
1092
1120
|
}
|
|
1093
1121
|
dispose() {
|
|
1094
1122
|
this.clear();
|
|
@@ -1122,14 +1150,18 @@ const mapToTelemetryValue = (ev) => {
|
|
|
1122
1150
|
};
|
|
1123
1151
|
//#endregion
|
|
1124
1152
|
//#region ../js-browser-client/src/telemetry/WebWorkerTelemetryClient.ts
|
|
1153
|
+
const DEFAULT_WORKER_CLOSE_TIMEOUT = 6e3;
|
|
1125
1154
|
var WebWorkerTelemetryClient = class {
|
|
1126
1155
|
logger;
|
|
1127
1156
|
worker;
|
|
1128
1157
|
closePromise;
|
|
1129
1158
|
closeResolve;
|
|
1159
|
+
closeTimer;
|
|
1160
|
+
workerCloseTimeout;
|
|
1130
1161
|
visibilityHandler;
|
|
1131
1162
|
constructor(options) {
|
|
1132
1163
|
this.logger = options.logger;
|
|
1164
|
+
this.workerCloseTimeout = options.workerCloseTimeout ?? DEFAULT_WORKER_CLOSE_TIMEOUT;
|
|
1133
1165
|
const initializeMessage = {
|
|
1134
1166
|
type: "Initialize",
|
|
1135
1167
|
payload: {
|
|
@@ -1141,7 +1173,7 @@ var WebWorkerTelemetryClient = class {
|
|
|
1141
1173
|
flushIntervalDelay: options.flushIntervalDelay ?? 3e4
|
|
1142
1174
|
}
|
|
1143
1175
|
};
|
|
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)
|
|
1176
|
+
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
1177
|
this.worker.onmessage = (message) => {
|
|
1146
1178
|
this.handleWorkerEvent(message.data);
|
|
1147
1179
|
};
|
|
@@ -1185,7 +1217,7 @@ var WebWorkerTelemetryClient = class {
|
|
|
1185
1217
|
}
|
|
1186
1218
|
handleWorkerEvent(event) {
|
|
1187
1219
|
if (event.type === "Closed") {
|
|
1188
|
-
this.
|
|
1220
|
+
this.finishClose();
|
|
1189
1221
|
return;
|
|
1190
1222
|
}
|
|
1191
1223
|
if (!event.payload?.level || !event.payload?.message) return;
|
|
@@ -1201,7 +1233,7 @@ var WebWorkerTelemetryClient = class {
|
|
|
1201
1233
|
this.logger.warn(message, ...args);
|
|
1202
1234
|
break;
|
|
1203
1235
|
case "error":
|
|
1204
|
-
this.logger.
|
|
1236
|
+
this.logger.error(message, ...args);
|
|
1205
1237
|
break;
|
|
1206
1238
|
}
|
|
1207
1239
|
}
|
|
@@ -1213,13 +1245,27 @@ var WebWorkerTelemetryClient = class {
|
|
|
1213
1245
|
return this.performClose();
|
|
1214
1246
|
}
|
|
1215
1247
|
performClose() {
|
|
1216
|
-
|
|
1248
|
+
try {
|
|
1249
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
1250
|
+
} catch (error) {
|
|
1251
|
+
this.logger.warn("[WebWorkerTelemetryClient] Could not remove 'visibilitychange' listener: ", error);
|
|
1252
|
+
}
|
|
1217
1253
|
this.closePromise = new Promise((resolve) => {
|
|
1218
1254
|
this.closeResolve = resolve;
|
|
1255
|
+
this.closeTimer = setTimeout(() => {
|
|
1256
|
+
this.logger.warn("[WebWorkerTelemetryClient] Timed out waiting for the telemetry worker to close. Terminating the worker.");
|
|
1257
|
+
this.finishClose();
|
|
1258
|
+
}, this.workerCloseTimeout);
|
|
1219
1259
|
this.worker.postMessage({ type: "Close" });
|
|
1220
1260
|
});
|
|
1221
1261
|
return this.closePromise;
|
|
1222
1262
|
}
|
|
1263
|
+
finishClose() {
|
|
1264
|
+
clearTimeout(this.closeTimer);
|
|
1265
|
+
this.closeTimer = void 0;
|
|
1266
|
+
this.worker.terminate();
|
|
1267
|
+
this.closeResolve?.();
|
|
1268
|
+
}
|
|
1223
1269
|
};
|
|
1224
1270
|
//#endregion
|
|
1225
1271
|
//#region ../shared/src/base62-encoder.ts
|
|
@@ -1241,6 +1287,7 @@ const BASE62_LENGTH = Math.ceil(128 / Math.log2(62));
|
|
|
1241
1287
|
//#region ../js-browser-client/src/telemetry/value-id-generator.ts
|
|
1242
1288
|
const encoder = new TextEncoder();
|
|
1243
1289
|
const generateValueId = async (v) => {
|
|
1290
|
+
if (typeof crypto === "undefined" || crypto.subtle == null) return;
|
|
1244
1291
|
const hashBuffer = await crypto.subtle.digest("SHA-256", encoder.encode(v?.toString() ?? ""));
|
|
1245
1292
|
return toBase62(new Uint8Array(hashBuffer, 0, 16), BASE62_LENGTH);
|
|
1246
1293
|
};
|
|
@@ -1317,7 +1364,7 @@ var plugin_client_default = defineNuxtPlugin((nuxtApp) => {
|
|
|
1317
1364
|
const { clientSdkKey, appName, appVersion, baseUrl } = runtimeConfig.public.configdirector;
|
|
1318
1365
|
const client = createBrowserClient(clientSdkKey, {
|
|
1319
1366
|
sdkName: "nuxt-sdk",
|
|
1320
|
-
sdkVersion: "
|
|
1367
|
+
sdkVersion: "1.2.0"
|
|
1321
1368
|
}, {
|
|
1322
1369
|
metadata: {
|
|
1323
1370
|
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": "
|
|
3
|
+
"version": "1.2.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",
|