@configdirector/nuxt-sdk 1.11.1 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.d.mts +13 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -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/app/composables/useConfigDirectorContext.d.mts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorContext.d.ts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorContext.mjs +41 -2
- package/dist/runtime/app/composables/useConfigDirectorValue.d.mts +1 -1
- package/dist/runtime/app/composables/useConfigDirectorValue.d.ts +1 -1
- package/dist/runtime/{native-values-BV2k_xR2.d.mts → native-values-ChQdi7c7.d.mts} +6 -4
- package/dist/runtime/nitro/offline-values.d.mts +1 -1
- package/dist/runtime/nitro/offline-values.d.ts +1 -1
- package/dist/runtime/nitro/plugin.d.mts +2 -2
- package/dist/runtime/nitro/plugin.d.ts +2 -2
- package/dist/runtime/nitro/plugin.mjs +1 -1
- package/dist/runtime/plugin.client.mjs +385 -69
- package/dist/runtime/plugin.server.mjs +46 -2
- package/dist/runtime/server/composables/useConfigDirectorServerClient.d.mts +1 -1
- package/dist/runtime/server/composables/useConfigDirectorServerClient.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-CS7AMNWe.d.mts → types-BoDJiQsr.d.mts} +1 -1
- package/dist/runtime/{types-Dt3bENKf.d.mts → types-CuzrFq9I.d.mts} +3 -1
- package/dist/testing.d.mts +7 -4
- package/dist/testing.mjs +278 -30
- package/package.json +1 -1
|
@@ -923,10 +923,27 @@ const generateInstanceId = () => {
|
|
|
923
923
|
});
|
|
924
924
|
};
|
|
925
925
|
//#endregion
|
|
926
|
+
//#region ../js-client-core/src/browser-globals.ts
|
|
927
|
+
const read = (readValue) => {
|
|
928
|
+
try {
|
|
929
|
+
return readValue(globalThis);
|
|
930
|
+
} catch {
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
const readUserAgent = () => read((globals) => globals.navigator?.userAgent);
|
|
935
|
+
const readHost = () => read((globals) => globals.location?.host);
|
|
936
|
+
const localStorageIfAvailable = () => {
|
|
937
|
+
try {
|
|
938
|
+
return globalThis.localStorage ?? void 0;
|
|
939
|
+
} catch {
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
//#endregion
|
|
926
944
|
//#region ../js-client-core/src/client-session.ts
|
|
927
945
|
const STORAGE_KEY = "configdirector-sdk:instance-id";
|
|
928
946
|
const RECORD_VERSION = 1;
|
|
929
|
-
const MAX_AGE_MS = 360 * 60 * 1e3;
|
|
930
947
|
const STORAGE_DEADLINE_MS = 1e3;
|
|
931
948
|
const CHECKSUM_SALT = "configdirector.instanceId.v1";
|
|
932
949
|
const UUID_V4_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
@@ -943,13 +960,6 @@ const checksumFor = (id, createdAt) => {
|
|
|
943
960
|
const low = fnv1a(payload, 2166136261);
|
|
944
961
|
return fnv1a(`${low}|${payload}`, 16777619).toString(16).padStart(8, "0") + low.toString(16).padStart(8, "0");
|
|
945
962
|
};
|
|
946
|
-
const localStorageIfAvailable = () => {
|
|
947
|
-
try {
|
|
948
|
-
return globalThis.localStorage ?? void 0;
|
|
949
|
-
} catch {
|
|
950
|
-
return;
|
|
951
|
-
}
|
|
952
|
-
};
|
|
953
963
|
const isUsableRecord = (record) => {
|
|
954
964
|
if (!record || typeof record !== "object") return false;
|
|
955
965
|
const { v, id, createdAt, checksum } = record;
|
|
@@ -978,11 +988,6 @@ const readStoredInstanceId = async (storage, logger) => {
|
|
|
978
988
|
logger.debug("[ConfigDirectorClient] The stored instance id failed validation, generating a new one");
|
|
979
989
|
return { outcome: "nothing usable" };
|
|
980
990
|
}
|
|
981
|
-
const age = Date.now() - record.createdAt;
|
|
982
|
-
if (age < 0 || age >= MAX_AGE_MS) {
|
|
983
|
-
logger.debug("[ConfigDirectorClient] The stored instance id has expired, generating a new one");
|
|
984
|
-
return { outcome: "nothing usable" };
|
|
985
|
-
}
|
|
986
991
|
return {
|
|
987
992
|
outcome: "stored",
|
|
988
993
|
instanceId: record.id
|
|
@@ -1198,17 +1203,6 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
1198
1203
|
}
|
|
1199
1204
|
};
|
|
1200
1205
|
//#endregion
|
|
1201
|
-
//#region ../js-client-core/src/browser-globals.ts
|
|
1202
|
-
const read = (readValue) => {
|
|
1203
|
-
try {
|
|
1204
|
-
return readValue(globalThis);
|
|
1205
|
-
} catch {
|
|
1206
|
-
return;
|
|
1207
|
-
}
|
|
1208
|
-
};
|
|
1209
|
-
const readUserAgent = () => read((globals) => globals.navigator?.userAgent);
|
|
1210
|
-
const readHost = () => read((globals) => globals.location?.host);
|
|
1211
|
-
//#endregion
|
|
1212
1206
|
//#region ../shared/src/context-kind.ts
|
|
1213
1207
|
const CONTEXT_KIND_MAXIMUM_LENGTH = 64;
|
|
1214
1208
|
const KIND_SAMPLE_MAXIMUM_CHARACTERS = 128;
|
|
@@ -1253,6 +1247,178 @@ const admitContext = (context) => {
|
|
|
1253
1247
|
};
|
|
1254
1248
|
};
|
|
1255
1249
|
//#endregion
|
|
1250
|
+
//#region ../shared/src/utf8-byte-length.ts
|
|
1251
|
+
const isHighSurrogate = (codeUnit) => codeUnit >= 55296 && codeUnit <= 56319;
|
|
1252
|
+
const isLowSurrogate = (codeUnit) => codeUnit >= 56320 && codeUnit <= 57343;
|
|
1253
|
+
const utf8ByteLength = (text) => {
|
|
1254
|
+
let bytes = 0;
|
|
1255
|
+
for (let i = 0; i < text.length; i++) {
|
|
1256
|
+
const codeUnit = text.charCodeAt(i);
|
|
1257
|
+
if (codeUnit <= 127) bytes += 1;
|
|
1258
|
+
else if (codeUnit <= 2047) bytes += 2;
|
|
1259
|
+
else if (isHighSurrogate(codeUnit) && i + 1 < text.length && isLowSurrogate(text.charCodeAt(i + 1))) {
|
|
1260
|
+
bytes += 4;
|
|
1261
|
+
i++;
|
|
1262
|
+
} else bytes += 3;
|
|
1263
|
+
}
|
|
1264
|
+
return bytes;
|
|
1265
|
+
};
|
|
1266
|
+
//#endregion
|
|
1267
|
+
//#region ../js-client-core/src/cached-values.ts
|
|
1268
|
+
const ENTRY_FORMAT_VERSION = 1;
|
|
1269
|
+
const DEFAULT_CONTEXT_KIND = "user";
|
|
1270
|
+
const CACHED_VALUES_STORAGE_KEY_PREFIX = "configdirector-sdk:cached-values:";
|
|
1271
|
+
const DEFAULT_CACHING_SIZE_LIMIT_BYTES = 256 * 1024;
|
|
1272
|
+
const planCaching = (connectionPlan, caching, platformProvider) => {
|
|
1273
|
+
if (connectionPlan.kind === "stay-offline") return {
|
|
1274
|
+
kind: "no cache",
|
|
1275
|
+
reason: "offline mode"
|
|
1276
|
+
};
|
|
1277
|
+
if (caching?.enabled === false) return {
|
|
1278
|
+
kind: "no cache",
|
|
1279
|
+
reason: "turned off"
|
|
1280
|
+
};
|
|
1281
|
+
const provider = caching?.provider ?? platformProvider?.(connectionPlan.clientSdkKey);
|
|
1282
|
+
if (!provider) return {
|
|
1283
|
+
kind: "no cache",
|
|
1284
|
+
reason: "no provider available"
|
|
1285
|
+
};
|
|
1286
|
+
return {
|
|
1287
|
+
kind: "cache",
|
|
1288
|
+
provider,
|
|
1289
|
+
clientSdkKey: connectionPlan.clientSdkKey
|
|
1290
|
+
};
|
|
1291
|
+
};
|
|
1292
|
+
const contextTagFor = (context, instanceId) => {
|
|
1293
|
+
const contextKind = context?.kind ?? DEFAULT_CONTEXT_KIND;
|
|
1294
|
+
if (context?.id != null) return {
|
|
1295
|
+
contextKind,
|
|
1296
|
+
contextId: context.id
|
|
1297
|
+
};
|
|
1298
|
+
return {
|
|
1299
|
+
contextKind,
|
|
1300
|
+
anonymousInstanceId: instanceId
|
|
1301
|
+
};
|
|
1302
|
+
};
|
|
1303
|
+
const sameContextTag = (stored, launch) => {
|
|
1304
|
+
if (stored.contextKind !== launch.contextKind) return false;
|
|
1305
|
+
if ("contextId" in stored && "contextId" in launch) return stored.contextId === launch.contextId;
|
|
1306
|
+
if ("anonymousInstanceId" in stored && "anonymousInstanceId" in launch) return stored.anonymousInstanceId === launch.anonymousInstanceId;
|
|
1307
|
+
return false;
|
|
1308
|
+
};
|
|
1309
|
+
const decideCachedValuesAdoption = (cachedValues, launchContextTag, hasReceivedLiveValues) => {
|
|
1310
|
+
if (hasReceivedLiveValues) return "live values already arrived";
|
|
1311
|
+
if (!sameContextTag(cachedValues.contextTag, launchContextTag)) return "context mismatch";
|
|
1312
|
+
return "adopt";
|
|
1313
|
+
};
|
|
1314
|
+
const isContextTag = (candidate) => {
|
|
1315
|
+
if (!candidate || typeof candidate !== "object") return false;
|
|
1316
|
+
const { contextKind, contextId, anonymousInstanceId } = candidate;
|
|
1317
|
+
if (typeof contextKind !== "string") return false;
|
|
1318
|
+
return typeof contextId === "string" || typeof anonymousInstanceId === "string";
|
|
1319
|
+
};
|
|
1320
|
+
const isConfigStateMap = (candidate) => {
|
|
1321
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return false;
|
|
1322
|
+
return Object.values(candidate).every((config) => config && typeof config === "object" && typeof config.id === "string" && typeof config.key === "string");
|
|
1323
|
+
};
|
|
1324
|
+
const isEntryFor = (clientSdkKey, candidate) => {
|
|
1325
|
+
if (!candidate || typeof candidate !== "object") return false;
|
|
1326
|
+
const entry = candidate;
|
|
1327
|
+
return entry.formatVersion === ENTRY_FORMAT_VERSION && entry.clientSdkKey === clientSdkKey && isContextTag(entry.contextTag) && typeof entry.environmentId === "string" && typeof entry.projectId === "string" && isConfigStateMap(entry.configs) && typeof entry.receivedAt === "number";
|
|
1328
|
+
};
|
|
1329
|
+
var CachedValuesStore = class {
|
|
1330
|
+
warned = /* @__PURE__ */ new Set();
|
|
1331
|
+
pendingRead;
|
|
1332
|
+
queuedCachedValues;
|
|
1333
|
+
writing = false;
|
|
1334
|
+
constructor(provider, clientSdkKey, logger) {
|
|
1335
|
+
this.provider = provider;
|
|
1336
|
+
this.clientSdkKey = clientSdkKey;
|
|
1337
|
+
this.logger = logger;
|
|
1338
|
+
}
|
|
1339
|
+
read() {
|
|
1340
|
+
this.pendingRead ??= this.readEntry();
|
|
1341
|
+
return this.pendingRead;
|
|
1342
|
+
}
|
|
1343
|
+
replace(cachedValues) {
|
|
1344
|
+
this.queuedCachedValues = cachedValues;
|
|
1345
|
+
this.writeQueued();
|
|
1346
|
+
}
|
|
1347
|
+
async readEntry() {
|
|
1348
|
+
let raw;
|
|
1349
|
+
try {
|
|
1350
|
+
raw = await this.provider.read();
|
|
1351
|
+
} catch (error) {
|
|
1352
|
+
this.warnOnce("read failed", "Could not read the values kept on the device, starting without them:", error);
|
|
1353
|
+
return { outcome: "failed" };
|
|
1354
|
+
}
|
|
1355
|
+
if (raw == null || raw === "") return { outcome: "nothing stored" };
|
|
1356
|
+
const entry = this.parseEntry(raw);
|
|
1357
|
+
if (!entry) {
|
|
1358
|
+
this.warnOnce("unreadable", "The values kept on the device could not be read and were removed, starting without them");
|
|
1359
|
+
await this.removeEntry();
|
|
1360
|
+
return { outcome: "unreadable" };
|
|
1361
|
+
}
|
|
1362
|
+
return {
|
|
1363
|
+
outcome: "stored",
|
|
1364
|
+
cachedValues: entry
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
parseEntry(raw) {
|
|
1368
|
+
try {
|
|
1369
|
+
const candidate = JSON.parse(raw);
|
|
1370
|
+
return isEntryFor(this.clientSdkKey, candidate) ? candidate : void 0;
|
|
1371
|
+
} catch {
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
async writeQueued() {
|
|
1376
|
+
if (this.writing) return;
|
|
1377
|
+
this.writing = true;
|
|
1378
|
+
try {
|
|
1379
|
+
while (this.queuedCachedValues) {
|
|
1380
|
+
const cachedValues = this.queuedCachedValues;
|
|
1381
|
+
this.queuedCachedValues = void 0;
|
|
1382
|
+
await this.writeEntry(cachedValues);
|
|
1383
|
+
}
|
|
1384
|
+
} finally {
|
|
1385
|
+
this.writing = false;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
async writeEntry(cachedValues) {
|
|
1389
|
+
const entry = {
|
|
1390
|
+
formatVersion: ENTRY_FORMAT_VERSION,
|
|
1391
|
+
clientSdkKey: this.clientSdkKey,
|
|
1392
|
+
...cachedValues
|
|
1393
|
+
};
|
|
1394
|
+
const serialized = JSON.stringify(entry);
|
|
1395
|
+
const size = utf8ByteLength(serialized);
|
|
1396
|
+
const limit = this.provider.sizeLimit();
|
|
1397
|
+
if (size > limit) {
|
|
1398
|
+
this.warnOnce("over the size limit", `The values received (${size} bytes) exceed the caching provider's limit of ${limit} bytes, so no values are kept on the device`);
|
|
1399
|
+
await this.removeEntry();
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
try {
|
|
1403
|
+
await this.provider.write(serialized);
|
|
1404
|
+
} catch (error) {
|
|
1405
|
+
this.warnOnce("write failed", "Could not keep the values on the device:", error);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
async removeEntry() {
|
|
1409
|
+
try {
|
|
1410
|
+
await this.provider.remove();
|
|
1411
|
+
} catch (error) {
|
|
1412
|
+
this.warnOnce("remove failed", "Could not remove the values kept on the device:", error);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
warnOnce(warning, message, ...details) {
|
|
1416
|
+
if (this.warned.has(warning)) return;
|
|
1417
|
+
this.warned.add(warning);
|
|
1418
|
+
this.logger.warn(`[ConfigDirectorClient] ${message}`, ...details);
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
//#endregion
|
|
1256
1422
|
//#region ../js-client-core/src/DefaultConfigDirectorClient.ts
|
|
1257
1423
|
const MAX_EXPONENTIAL_DELAY = 9;
|
|
1258
1424
|
var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
@@ -1270,11 +1436,15 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1270
1436
|
readyPromise;
|
|
1271
1437
|
readyResolve;
|
|
1272
1438
|
currentContext;
|
|
1439
|
+
submittedContext;
|
|
1273
1440
|
connectionPlan;
|
|
1274
1441
|
clientSession;
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1442
|
+
cachedValuesStore;
|
|
1443
|
+
servingCachedValues = false;
|
|
1444
|
+
cachedValuesAdoptionStarted = false;
|
|
1445
|
+
static from(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions, internalClientOptions) {
|
|
1446
|
+
if (typeof clientSdkKeyOrOptions === "object") return new DefaultConfigDirectorClient(liveTelemetryClient, void 0, sdkOptions, clientSdkKeyOrOptions, internalClientOptions);
|
|
1447
|
+
return new DefaultConfigDirectorClient(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions, internalClientOptions);
|
|
1278
1448
|
}
|
|
1279
1449
|
constructor(liveTelemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
|
|
1280
1450
|
this.logger = clientOptions?.logger ?? createDefaultLogger$1();
|
|
@@ -1282,10 +1452,12 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1282
1452
|
this.timeout = clientOptions?.connection?.timeout ?? 3e3;
|
|
1283
1453
|
const urlFactory = internalClientOptions?.urlFactory ?? defaultUrlFactory;
|
|
1284
1454
|
this.connectionPlan = planConnection(clientSdkKey, clientOptions?.connection, urlFactory);
|
|
1455
|
+
this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions?.caching, internalClientOptions?.platformCachingProvider));
|
|
1285
1456
|
this.telemetryClient = this.buildTelemetryClient(liveTelemetryClient, clientOptions);
|
|
1286
1457
|
this.transport = internalClientOptions?.transport ?? this.buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions);
|
|
1287
1458
|
this.transport.on("configSetReceived", (configSet) => {
|
|
1288
1459
|
this.hasReceivedConfigSet = true;
|
|
1460
|
+
this.servingCachedValues = false;
|
|
1289
1461
|
this.readyResolve?.();
|
|
1290
1462
|
const removedKeys = this.findKeysRemovedBy(configSet);
|
|
1291
1463
|
if (!this.configSet || configSet.kind == "full") this.configSet = configSet;
|
|
@@ -1293,6 +1465,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1293
1465
|
...this.configSet.configs,
|
|
1294
1466
|
...configSet.configs
|
|
1295
1467
|
};
|
|
1468
|
+
this.keepValuesOnTheDevice(this.configSet);
|
|
1296
1469
|
const keys = Object.keys(configSet.configs);
|
|
1297
1470
|
this.emit("configsUpdated", {
|
|
1298
1471
|
keys,
|
|
@@ -1314,6 +1487,80 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1314
1487
|
this.registerHandler("configsUpdated", clientOptions?.hooks?.configsUpdated);
|
|
1315
1488
|
this.registerHandler("contextUpdated", clientOptions?.hooks?.contextUpdated);
|
|
1316
1489
|
this.registerHandler("connectionError", clientOptions?.hooks?.connectionError);
|
|
1490
|
+
this.registerHandler("cachedValuesLoaded", clientOptions?.hooks?.cachedValuesLoaded);
|
|
1491
|
+
}
|
|
1492
|
+
buildCachedValuesStore(cachingPlan) {
|
|
1493
|
+
switch (cachingPlan.kind) {
|
|
1494
|
+
case "no cache":
|
|
1495
|
+
this.logger.debug(`[ConfigDirectorClient] No values are kept on the device: ${cachingPlan.reason}`);
|
|
1496
|
+
return;
|
|
1497
|
+
case "cache": {
|
|
1498
|
+
const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.clientSdkKey, this.logger);
|
|
1499
|
+
store.read();
|
|
1500
|
+
return store;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
startCachedValuesAdoption() {
|
|
1505
|
+
const store = this.cachedValuesStore;
|
|
1506
|
+
if (!store || this.cachedValuesAdoptionStarted) return;
|
|
1507
|
+
this.cachedValuesAdoptionStarted = true;
|
|
1508
|
+
this.adoptCachedValues(store);
|
|
1509
|
+
}
|
|
1510
|
+
async adoptCachedValues(store) {
|
|
1511
|
+
const read = await store.read();
|
|
1512
|
+
switch (read.outcome) {
|
|
1513
|
+
case "nothing stored":
|
|
1514
|
+
case "unreadable":
|
|
1515
|
+
case "failed": return;
|
|
1516
|
+
case "stored": break;
|
|
1517
|
+
}
|
|
1518
|
+
const { instanceId } = await this.clientSession;
|
|
1519
|
+
const launchContextTag = contextTagFor(this.submittedContext, instanceId);
|
|
1520
|
+
const adoption = decideCachedValuesAdoption(read.cachedValues, launchContextTag, this.hasReceivedConfigSet);
|
|
1521
|
+
switch (adoption) {
|
|
1522
|
+
case "adopt":
|
|
1523
|
+
this.serveCachedValues(read.cachedValues);
|
|
1524
|
+
return;
|
|
1525
|
+
case "live values already arrived":
|
|
1526
|
+
case "context mismatch":
|
|
1527
|
+
this.logger.debug(`[ConfigDirectorClient] The values kept on the device were not adopted: ${adoption}`);
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
serveCachedValues(cachedValues) {
|
|
1532
|
+
const { environmentId, projectId, configs } = cachedValues;
|
|
1533
|
+
this.configSet = {
|
|
1534
|
+
environmentId,
|
|
1535
|
+
projectId,
|
|
1536
|
+
configs,
|
|
1537
|
+
kind: "full"
|
|
1538
|
+
};
|
|
1539
|
+
this.servingCachedValues = true;
|
|
1540
|
+
const keys = Object.keys(configs);
|
|
1541
|
+
this.emit("cachedValuesLoaded", {});
|
|
1542
|
+
this.emit("configsUpdated", {
|
|
1543
|
+
keys,
|
|
1544
|
+
removedKeys: []
|
|
1545
|
+
});
|
|
1546
|
+
this.updateWatchers(keys);
|
|
1547
|
+
this.logger.debug("[ConfigDirectorClient] Serving the values kept on the device until live values arrive:", { keys });
|
|
1548
|
+
}
|
|
1549
|
+
keepValuesOnTheDevice(configSet) {
|
|
1550
|
+
const store = this.cachedValuesStore;
|
|
1551
|
+
if (!store) return;
|
|
1552
|
+
const context = this.submittedContext;
|
|
1553
|
+
const receivedAt = Date.now();
|
|
1554
|
+
this.clientSession.then(({ instanceId }) => store.replace({
|
|
1555
|
+
contextTag: contextTagFor(context, instanceId),
|
|
1556
|
+
environmentId: configSet.environmentId,
|
|
1557
|
+
projectId: configSet.projectId,
|
|
1558
|
+
configs: configSet.configs,
|
|
1559
|
+
receivedAt
|
|
1560
|
+
}));
|
|
1561
|
+
}
|
|
1562
|
+
servedReason(reason) {
|
|
1563
|
+
return this.servingCachedValues && reason === "found-match" ? "cached" : reason;
|
|
1317
1564
|
}
|
|
1318
1565
|
registerHandler(event, handler) {
|
|
1319
1566
|
if (!handler) return;
|
|
@@ -1407,6 +1654,8 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1407
1654
|
return this.currentContext;
|
|
1408
1655
|
}
|
|
1409
1656
|
async connectToTransport(context, caller) {
|
|
1657
|
+
this.submittedContext = context;
|
|
1658
|
+
if (caller === "initialization") this.startCachedValuesAdoption();
|
|
1410
1659
|
try {
|
|
1411
1660
|
this.ready = false;
|
|
1412
1661
|
this.readyPromise = new Promise((resolve) => {
|
|
@@ -1507,6 +1756,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1507
1756
|
return defaultValue;
|
|
1508
1757
|
}
|
|
1509
1758
|
const parseResult = parseConfigValue(configState, defaultValue);
|
|
1759
|
+
const reason = this.servedReason(parseResult.reason);
|
|
1510
1760
|
this.telemetryClient.evaluatedConfig({
|
|
1511
1761
|
contextId: this.currentContext?.id,
|
|
1512
1762
|
type: configState.type,
|
|
@@ -1516,14 +1766,14 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1516
1766
|
evaluatedValue: parseResult.parsedValue,
|
|
1517
1767
|
evaluatedValueId: parseResult.parsedValueId,
|
|
1518
1768
|
usedDefault: parseResult.usedDefault,
|
|
1519
|
-
evaluationReason:
|
|
1769
|
+
evaluationReason: reason
|
|
1520
1770
|
});
|
|
1521
1771
|
this.dispatchEvaluationEvent({
|
|
1522
1772
|
key: configKey,
|
|
1523
1773
|
value: parseResult.parsedValue,
|
|
1524
1774
|
valueId: parseResult.parsedValueId ?? void 0,
|
|
1525
1775
|
isDefaultValue: parseResult.usedDefault,
|
|
1526
|
-
reason
|
|
1776
|
+
reason,
|
|
1527
1777
|
context: this.context
|
|
1528
1778
|
});
|
|
1529
1779
|
this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
|
|
@@ -1879,6 +2129,25 @@ const generateValueId = async (v) => {
|
|
|
1879
2129
|
return toBase62(new Uint8Array(hashBuffer, 0, 16), BASE62_LENGTH);
|
|
1880
2130
|
};
|
|
1881
2131
|
//#endregion
|
|
2132
|
+
//#region ../js-browser-client/src/local-storage-caching-provider.ts
|
|
2133
|
+
const toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2134
|
+
const storageKeyFor = async (clientSdkKey) => {
|
|
2135
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(clientSdkKey));
|
|
2136
|
+
return CACHED_VALUES_STORAGE_KEY_PREFIX + toHex(new Uint8Array(digest));
|
|
2137
|
+
};
|
|
2138
|
+
const subtleCryptoAvailable = () => typeof crypto !== "undefined" && crypto.subtle != null;
|
|
2139
|
+
const localStorageCachingProvider = (clientSdkKey) => {
|
|
2140
|
+
const storage = localStorageIfAvailable();
|
|
2141
|
+
if (!storage || !subtleCryptoAvailable()) return;
|
|
2142
|
+
const storageKey = storageKeyFor(clientSdkKey);
|
|
2143
|
+
return {
|
|
2144
|
+
read: async () => storage.getItem(await storageKey),
|
|
2145
|
+
write: async (value) => storage.setItem(await storageKey, value),
|
|
2146
|
+
remove: async () => storage.removeItem(await storageKey),
|
|
2147
|
+
sizeLimit: () => DEFAULT_CACHING_SIZE_LIMIT_BYTES
|
|
2148
|
+
};
|
|
2149
|
+
};
|
|
2150
|
+
//#endregion
|
|
1882
2151
|
//#region ../js-browser-client/src/BrowserConfigDirectorClient.ts
|
|
1883
2152
|
/**
|
|
1884
2153
|
* Creates a browser client. With a client SDK key it connects to ConfigDirector; with
|
|
@@ -1901,13 +2170,26 @@ const createBrowserClient = (clientSdkKeyOrOptions, sdkOptions, clientOptions) =
|
|
|
1901
2170
|
baseUrl,
|
|
1902
2171
|
urlFactory: defaultUrlFactory,
|
|
1903
2172
|
valueIdGenerator: generateValueId
|
|
1904
|
-
}), clientSdkKeyOrOptions, sdkOptions, clientOptions);
|
|
2173
|
+
}), clientSdkKeyOrOptions, sdkOptions, clientOptions, { platformCachingProvider: localStorageCachingProvider });
|
|
1905
2174
|
};
|
|
1906
2175
|
//#endregion
|
|
1907
2176
|
//#region ../js-client-core/src/installedTestClient.ts
|
|
1908
2177
|
const INSTALLED_TEST_CLIENT_SLOT = Symbol.for("@configdirector/installed-test-client");
|
|
1909
2178
|
const readInstalledTestClient = () => globalThis[INSTALLED_TEST_CLIENT_SLOT];
|
|
1910
2179
|
//#endregion
|
|
2180
|
+
//#region ../shared/src/browser-half.ts
|
|
2181
|
+
const decideBrowserHalfSource = (browserHalf, serverConnectionMode) => {
|
|
2182
|
+
if (browserHalf.mode !== void 0 || browserHalf.values !== void 0) return "as-given";
|
|
2183
|
+
if (serverConnectionMode === "offline") return "inherit-offline";
|
|
2184
|
+
return "connect";
|
|
2185
|
+
};
|
|
2186
|
+
//#endregion
|
|
2187
|
+
//#region src/runtime/app/composables/useConfigDirectorClient.ts
|
|
2188
|
+
const useConfigDirectorClient = () => {
|
|
2189
|
+
const { $configDirectorClient } = useNuxtApp();
|
|
2190
|
+
return { client: $configDirectorClient };
|
|
2191
|
+
};
|
|
2192
|
+
//#endregion
|
|
1911
2193
|
//#region ../shared/src/deepEqual.ts
|
|
1912
2194
|
const deepEqual = (left, right) => {
|
|
1913
2195
|
if (Object.is(left, right)) return true;
|
|
@@ -1933,17 +2215,17 @@ const decideMountAction = (client, context) => {
|
|
|
1933
2215
|
return "none";
|
|
1934
2216
|
};
|
|
1935
2217
|
//#endregion
|
|
1936
|
-
//#region
|
|
1937
|
-
const
|
|
1938
|
-
|
|
1939
|
-
if (
|
|
1940
|
-
return "
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
const
|
|
1945
|
-
|
|
1946
|
-
return
|
|
2218
|
+
//#region src/runtime/client-startup.ts
|
|
2219
|
+
const decideStartupAction = (client, context, waitsForFirstContext) => {
|
|
2220
|
+
const mountAction = decideMountAction(client, context);
|
|
2221
|
+
if (!waitsForFirstContext) return mountAction;
|
|
2222
|
+
if (mountAction === "initialize" && context === void 0) return "waitForContext";
|
|
2223
|
+
if (mountAction === "none" && client.isInitializing) return "firstStartInFlight";
|
|
2224
|
+
return mountAction;
|
|
2225
|
+
};
|
|
2226
|
+
const decideContextUpdateAction = (client, context, waitsForFirstContext) => {
|
|
2227
|
+
if (waitsForFirstContext && decideMountAction(client, context) === "initialize") return "startClient";
|
|
2228
|
+
return "updateContext";
|
|
1947
2229
|
};
|
|
1948
2230
|
//#endregion
|
|
1949
2231
|
//#region src/runtime/app/composables/useConfigDirectorContext.ts
|
|
@@ -1954,6 +2236,14 @@ const getEffectiveTimeout = (option) => {
|
|
|
1954
2236
|
const useConfigDirectorContext = () => {
|
|
1955
2237
|
const state = useState("configdirector-context");
|
|
1956
2238
|
const { client } = useConfigDirectorClient();
|
|
2239
|
+
const { $configDirectorClientStartup } = useNuxtApp();
|
|
2240
|
+
const startup = $configDirectorClientStartup;
|
|
2241
|
+
const handContextToClient = (rawContext) => {
|
|
2242
|
+
switch (decideContextUpdateAction(client, rawContext, startup.waitsForFirstContext)) {
|
|
2243
|
+
case "startClient": return startup.start(rawContext);
|
|
2244
|
+
case "updateContext": return client.updateContext(rawContext);
|
|
2245
|
+
}
|
|
2246
|
+
};
|
|
1957
2247
|
const updateContext = async (context, options) => {
|
|
1958
2248
|
const rawContext = toRaw(context);
|
|
1959
2249
|
const oldContext = JSON.stringify(state.value);
|
|
@@ -1963,7 +2253,7 @@ const useConfigDirectorContext = () => {
|
|
|
1963
2253
|
const timeout = getEffectiveTimeout(options?.timeoutMilliseconds);
|
|
1964
2254
|
let timer;
|
|
1965
2255
|
try {
|
|
1966
|
-
await Promise.race([
|
|
2256
|
+
await Promise.race([handContextToClient(rawContext), new Promise((resolve) => {
|
|
1967
2257
|
timer = setTimeout(() => resolve(), timeout);
|
|
1968
2258
|
})]);
|
|
1969
2259
|
} finally {
|
|
@@ -1990,28 +2280,51 @@ const OFFLINE_VALUES_ROUTE = "/_configdirector/offline-values";
|
|
|
1990
2280
|
//#region src/runtime/plugin.client.ts
|
|
1991
2281
|
const DEFAULT_INITIALIZATION_TIMEOUT_MILLISECONDS = 2e3;
|
|
1992
2282
|
var plugin_client_default = defineNuxtPlugin(async (nuxtApp) => {
|
|
1993
|
-
const
|
|
1994
|
-
const logger = createDefaultLogger(
|
|
2283
|
+
const configdirector = useRuntimeConfig().public?.configdirector;
|
|
2284
|
+
const logger = createDefaultLogger(configdirector?.logLevel);
|
|
1995
2285
|
const installedTestClient = readInstalledTestClient();
|
|
1996
|
-
const client = installedTestClient
|
|
2286
|
+
const { client, runsOffline } = installedTestClient ? {
|
|
2287
|
+
client: installedTestClient,
|
|
2288
|
+
runsOffline: false
|
|
2289
|
+
} : await buildClient(configdirector, logger);
|
|
1997
2290
|
logger.debug(installedTestClient ? "Installed ConfigDirector Nuxt plugin with a test client" : "Installed ConfigDirector Nuxt plugin");
|
|
1998
2291
|
const readyStatus = shallowRef(client.isReady ? "ready" : "loading");
|
|
1999
2292
|
client.on("clientReady", () => {
|
|
2000
2293
|
readyStatus.value = "ready";
|
|
2001
2294
|
});
|
|
2295
|
+
const settleReadyStatus = () => {
|
|
2296
|
+
if (!client.isReady) readyStatus.value = "default";
|
|
2297
|
+
};
|
|
2298
|
+
const startup = {
|
|
2299
|
+
waitsForFirstContext: configdirector?.deferInitialization === true && !runsOffline,
|
|
2300
|
+
start: async (context) => {
|
|
2301
|
+
await client.initialize(context);
|
|
2302
|
+
settleReadyStatus();
|
|
2303
|
+
}
|
|
2304
|
+
};
|
|
2002
2305
|
nuxtApp.hooks.hook("app:created", async () => {
|
|
2003
2306
|
const { context } = useConfigDirectorContext();
|
|
2004
2307
|
const rawContext = toRaw(context.value);
|
|
2005
|
-
const
|
|
2006
|
-
logger.debug(`Browser client
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2308
|
+
const startupAction = decideStartupAction(client, rawContext, startup.waitsForFirstContext);
|
|
2309
|
+
logger.debug(`Browser client startup action: ${startupAction}`);
|
|
2310
|
+
switch (startupAction) {
|
|
2311
|
+
case "waitForContext": return;
|
|
2312
|
+
case "firstStartInFlight": return;
|
|
2313
|
+
case "initialize":
|
|
2314
|
+
await client.initialize(rawContext);
|
|
2315
|
+
break;
|
|
2316
|
+
case "updateContext":
|
|
2317
|
+
await client.updateContext(rawContext);
|
|
2318
|
+
break;
|
|
2319
|
+
case "none": break;
|
|
2320
|
+
}
|
|
2321
|
+
settleReadyStatus();
|
|
2322
|
+
logger.debug(`Browser client startup action awaited, ready status: ${client.isReady}`);
|
|
2011
2323
|
});
|
|
2012
2324
|
return { provide: {
|
|
2013
2325
|
configDirectorClient: client,
|
|
2014
|
-
configDirectorClientReadyStatus: readonly(readyStatus)
|
|
2326
|
+
configDirectorClientReadyStatus: readonly(readyStatus),
|
|
2327
|
+
configDirectorClientStartup: startup
|
|
2015
2328
|
} };
|
|
2016
2329
|
});
|
|
2017
2330
|
const buildClient = async (configdirector, logger) => {
|
|
@@ -2021,23 +2334,26 @@ const buildClient = async (configdirector, logger) => {
|
|
|
2021
2334
|
logger.error(message);
|
|
2022
2335
|
throw new ConfigDirectorInitializationError(message);
|
|
2023
2336
|
}
|
|
2024
|
-
return
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2337
|
+
return {
|
|
2338
|
+
client: createBrowserClient(configdirector?.clientSdkKey || void 0, {
|
|
2339
|
+
sdkName: "nuxt-sdk",
|
|
2340
|
+
sdkVersion: "1.12.0"
|
|
2341
|
+
}, {
|
|
2342
|
+
metadata: {
|
|
2343
|
+
appName: configdirector?.appName,
|
|
2344
|
+
appVersion: configdirector?.appVersion
|
|
2345
|
+
},
|
|
2346
|
+
connection: {
|
|
2347
|
+
url: configdirector?.baseUrl || void 0,
|
|
2348
|
+
mode: browserHalf.mode,
|
|
2349
|
+
values: browserHalf.values,
|
|
2350
|
+
pollingInterval: configdirector?.connection?.pollingInterval || void 0,
|
|
2351
|
+
timeout: configdirector?.connection?.timeout || DEFAULT_INITIALIZATION_TIMEOUT_MILLISECONDS
|
|
2352
|
+
},
|
|
2353
|
+
logger
|
|
2354
|
+
}),
|
|
2355
|
+
runsOffline: browserHalf.mode === "offline"
|
|
2356
|
+
};
|
|
2041
2357
|
};
|
|
2042
2358
|
const browserHalfConnectionOf = async (configdirector, logger) => {
|
|
2043
2359
|
const givenConnection = {
|