@configdirector/nuxt-sdk 1.0.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 +145 -62
- 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.
|
|
@@ -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
|
|
@@ -7887,7 +7970,7 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
|
|
|
7887
7970
|
const { serverSdkKey, baseUrl } = runtimeConfig.configdirector;
|
|
7888
7971
|
const client = new DefaultConfigDirectorClient(serverSdkKey, {
|
|
7889
7972
|
sdkName: "nuxt-sdk",
|
|
7890
|
-
sdkVersion: "1.
|
|
7973
|
+
sdkVersion: "1.2.0"
|
|
7891
7974
|
}, {
|
|
7892
7975
|
logger,
|
|
7893
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: "1.
|
|
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": "1.
|
|
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",
|