@configdirector/nuxt-sdk 1.11.1 → 1.12.1

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.
Files changed (29) hide show
  1. package/dist/module.d.mts +13 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +2 -1
  4. package/dist/runtime/app/composables/useConfigDirectorClient.d.mts +1 -1
  5. package/dist/runtime/app/composables/useConfigDirectorClient.d.ts +1 -1
  6. package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.mts +1 -1
  7. package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.ts +1 -1
  8. package/dist/runtime/app/composables/useConfigDirectorContext.d.mts +1 -1
  9. package/dist/runtime/app/composables/useConfigDirectorContext.d.ts +1 -1
  10. package/dist/runtime/app/composables/useConfigDirectorContext.mjs +41 -2
  11. package/dist/runtime/app/composables/useConfigDirectorValue.d.mts +1 -1
  12. package/dist/runtime/app/composables/useConfigDirectorValue.d.ts +1 -1
  13. package/dist/runtime/{native-values-BV2k_xR2.d.mts → native-values-ChQdi7c7.d.mts} +6 -4
  14. package/dist/runtime/nitro/offline-values.d.mts +1 -1
  15. package/dist/runtime/nitro/offline-values.d.ts +1 -1
  16. package/dist/runtime/nitro/plugin.d.mts +2 -2
  17. package/dist/runtime/nitro/plugin.d.ts +2 -2
  18. package/dist/runtime/nitro/plugin.mjs +1 -1
  19. package/dist/runtime/plugin.client.mjs +403 -73
  20. package/dist/runtime/plugin.server.mjs +46 -2
  21. package/dist/runtime/server/composables/useConfigDirectorServerClient.d.mts +1 -1
  22. package/dist/runtime/server/composables/useConfigDirectorServerClient.d.ts +1 -1
  23. package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.mts +1 -1
  24. package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.ts +1 -1
  25. package/dist/runtime/{types-CS7AMNWe.d.mts → types-BoDJiQsr.d.mts} +1 -1
  26. package/dist/runtime/{types-Dt3bENKf.d.mts → types-CuzrFq9I.d.mts} +3 -1
  27. package/dist/testing.d.mts +7 -4
  28. package/dist/testing.mjs +280 -31
  29. package/package.json +2 -2
@@ -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
- static from(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions) {
1276
- if (typeof clientSdkKeyOrOptions === "object") return new DefaultConfigDirectorClient(liveTelemetryClient, void 0, sdkOptions, clientSdkKeyOrOptions);
1277
- return new DefaultConfigDirectorClient(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions);
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;
@@ -1337,7 +1584,8 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1337
1584
  clientSdkKey: plan.clientSdkKey,
1338
1585
  baseUrl: plan.baseUrl,
1339
1586
  logger: this.logger,
1340
- metadata: clientOptions?.metadata
1587
+ metadata: clientOptions?.metadata,
1588
+ clientSession: this.clientSession
1341
1589
  });
1342
1590
  }
1343
1591
  }
@@ -1407,6 +1655,8 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1407
1655
  return this.currentContext;
1408
1656
  }
1409
1657
  async connectToTransport(context, caller) {
1658
+ this.submittedContext = context;
1659
+ if (caller === "initialization") this.startCachedValuesAdoption();
1410
1660
  try {
1411
1661
  this.ready = false;
1412
1662
  this.readyPromise = new Promise((resolve) => {
@@ -1507,6 +1757,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1507
1757
  return defaultValue;
1508
1758
  }
1509
1759
  const parseResult = parseConfigValue(configState, defaultValue);
1760
+ const reason = this.servedReason(parseResult.reason);
1510
1761
  this.telemetryClient.evaluatedConfig({
1511
1762
  contextId: this.currentContext?.id,
1512
1763
  type: configState.type,
@@ -1516,14 +1767,14 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
1516
1767
  evaluatedValue: parseResult.parsedValue,
1517
1768
  evaluatedValueId: parseResult.parsedValueId,
1518
1769
  usedDefault: parseResult.usedDefault,
1519
- evaluationReason: parseResult.reason
1770
+ evaluationReason: reason
1520
1771
  });
1521
1772
  this.dispatchEvaluationEvent({
1522
1773
  key: configKey,
1523
1774
  value: parseResult.parsedValue,
1524
1775
  valueId: parseResult.parsedValueId ?? void 0,
1525
1776
  isDefaultValue: parseResult.usedDefault,
1526
- reason: parseResult.reason,
1777
+ reason,
1527
1778
  context: this.context
1528
1779
  });
1529
1780
  this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
@@ -1681,6 +1932,7 @@ const DEFAULT_WORKER_CLOSE_TIMEOUT = 6e3;
1681
1932
  var WebWorkerTelemetryClient = class {
1682
1933
  logger;
1683
1934
  initializeMessage;
1935
+ clientSession;
1684
1936
  worker;
1685
1937
  turnedOff = false;
1686
1938
  pendingEvaluations = [];
@@ -1695,6 +1947,7 @@ var WebWorkerTelemetryClient = class {
1695
1947
  this.logger = options.logger;
1696
1948
  this.workerCloseTimeout = options.workerCloseTimeout ?? DEFAULT_WORKER_CLOSE_TIMEOUT;
1697
1949
  this.pendingEvaluationLimit = options.evaluationQueueLimit ?? 1e3;
1950
+ this.clientSession = options.clientSession;
1698
1951
  this.initializeMessage = {
1699
1952
  type: "Initialize",
1700
1953
  payload: {
@@ -1717,7 +1970,7 @@ var WebWorkerTelemetryClient = class {
1717
1970
  return;
1718
1971
  }
1719
1972
  try {
1720
- return new Worker(URL.createObjectURL(new Blob(["(function(){let e=async(e,t)=>e.valueId?{valueId:e.valueId}:e.value?e.value.length<=500&&e.type!=`json`?{value:e.value}:{valueId:await t(e.value)}:e,t=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},n=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0},r=async(e,t,n,r)=>{let i=new AbortController,a=setTimeout(()=>{r.debug(`[fetchWithTimeout] Reached timeout, aborting request`),i.abort()},e);try{let e=await fetch(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}},i=e=>e?.name===`NotAllowedError`?!0:e instanceof TypeError;var a=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.sdkIdentity=e.sdkIdentity,this.metaContext=e.metaContext,this.url=e.urlFactory(`client/telemetry/v1`,e.baseUrl)}async report({context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r}){if(!this.executeRequests)return{success:!1,fatalError:!0};let i={clientSdkKey:this.sdkKey,metaContext:{sdkName:this.sdkIdentity.sdkName,sdkVersion:this.sdkIdentity.sdkVersion,appName:this.metaContext.appName,appVersion:this.metaContext.appVersion},context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r};if(this.isReportEmpty(i))return{success:!0,fatalError:!1};let a=await this.sendReport(i);return a.fatalError&&(this.executeRequests=!1),a}isReportEmpty(e){return t(e.discreteEvents)&&t(e.aggregatedEvents)&&n(e.droppedEvents)}async sendReport(e){try{let t=await r(5e3,this.url,{method:`POST`,body:JSON.stringify(e),keepalive:!0},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){let t=i(e);return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:t}}}isStatusFatal(e){return!!e&&e>=400&&e<500&&e!=429}},o=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},s=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||=new Date;let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},c=class{constructor(e){this.aggregator=new o,this.collectEvents=!0,this.started=!1,this.logger=e.logger,this.evaluationEventQueue=new s(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4,this.initialFlushIntervalDelay=e.initialFlushIntervalDelay??5e3}start(){this.started||!this.collectEvents||(this.started=!0,this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.initialFlushIntervalDelay))}async flushAndScheduleNext(){if(!this.collectEvents)return;let e;try{e=await this.flush()}catch(t){this.logger.warn(`[TelemetryEventCollector] Error flushing telemetry events: `,t),e={success:!1,fatalError:!1}}e.fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.collectEvents&&(this.cancelScheduledFlush(),this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay))}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout);try{await this.flush()}catch(e){this.logger.warn(`[TelemetryEventCollector] Error flushing telemetry events during close: `,e)}this.evaluationEventQueue.clear()}dispose(){this.close()}},l=class{constructor(e){this.valueIdGenerator=e,this.valueIdGenerator=e}async process(t){let n=await Promise.all(t.events.map(async t=>({...t,defaultValue:await e(t.defaultValue,this.valueIdGenerator),evaluatedValue:await e(t.evaluatedValue,this.valueIdGenerator)})));return{...t,events:n}}},u=class{constructor(){this.countsByKindSample=new Map,this.countBeyondSampleLimit=0}record(e){return this.startTime??=new Date,this.add(e,1)}absorb(e){for(let t of e)(this.startTime===void 0||t.startTime<this.startTime)&&(this.startTime=t.startTime),this.add(t.event.kindSample,t.count)}add(e,t){if(e!==void 0){let n=this.countsByKindSample.get(e);if(n!==void 0)return this.countsByKindSample.set(e,n+t),`known-kind-sample`;if(this.countsByKindSample.size<20)return this.countsByKindSample.set(e,t),`new-kind-sample`}let n=this.countBeyondSampleLimit===0;return this.countBeyondSampleLimit+=t,n?`first-beyond-sample-limit`:`beyond-sample-limit`}takeSnapshot(){let e=new Date,t=this.startTime??e,n=(n,r)=>({startTime:t,endTime:e,count:n,event:{reason:`invalid-context-kind`,kindSample:r}}),r=Array.from(this.countsByKindSample,([e,t])=>n(t,e)),i=this.countBeyondSampleLimit>0?[...r,n(this.countBeyondSampleLimit)]:r;return this.clear(),i}clear(){this.countsByKindSample.clear(),this.countBeyondSampleLimit=0,this.startTime=void 0}},d=class extends c{constructor(e){super(e),this.invalidContextSubmissions=new u,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)}recordInvalidContext(e){this.invalidContextSubmissions.record(e)}absorbInvalidContextSubmissions(e){this.invalidContextSubmissions.absorb(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),invalidContext:this.invalidContextSubmissions.takeSnapshot()},droppedEvents:{evaluatedConfig:e.droppedCount}})}};let f=(e,t)=>new URL(e,t?.toString()),p=(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`)},m=Math.ceil(128/Math.log2(62)),h=new TextEncoder,g=async e=>{if(typeof crypto>`u`||crypto.subtle==null)return;let t=await crypto.subtle.digest(`SHA-256`,h.encode(e?.toString()??``));return p(new Uint8Array(t,0,16),m)},_,v=(e,t,...n)=>({type:`Log`,payload:{level:e,message:t,args:n}}),y=e=>{try{self.postMessage(e)}catch{self.postMessage(v(e.payload.level,e.payload.message))}},b={debug:function(e,...t){y(v(`debug`,e,t))},info:function(e,...t){y(v(`info`,e,t))},warn:function(e,...t){y(v(`warn`,e,t))},error:function(e,...t){y(v(`error`,e,t))}};addEventListener(`message`,e=>{let t=e.data;if(t.type!=`Initialize`&&!_){b.info(`[TelemetryWorker] Ignoring ${t.type} because no collector is configured`);return}switch(b.debug(`[TelemetryWorker] Received '${t.type}' event:`,t),t.type){case`Initialize`:if(_){b.warn(`[TelemetryWorker] Received a duplicate Initialize event, ignoring it`);break}_=new d({...t.payload,baseUrl:f(t.payload.baseUrl),urlFactory:f,valueIdGenerator:g,logger:b}),_.start();break;case`EvaluatedConfigEvent`:_?.evaluatedConfig(t.payload);break;case`InvalidContextSubmissions`:_?.absorbInvalidContextSubmissions(t.payload.entries);break;case`UpdateContext`:_?.updateContext(t.payload.context);break;case`Flush`:_?.forceFlush();break;case`Close`:(_?.close()??Promise.resolve()).then(()=>{self.postMessage({type:`Closed`}),self.close()});break}})})();"], { type: "application/javascript" })));
1973
+ return new Worker(URL.createObjectURL(new Blob(["(function(){let e=async(e,t)=>e.valueId?{valueId:e.valueId}:e.value?e.value.length<=500&&e.type!=`json`?{value:e.value}:{valueId:await t(e.value)}:e,t=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},n=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0},r=async(e,t,n,r)=>{let i=new AbortController,a=setTimeout(()=>{r.debug(`[fetchWithTimeout] Reached timeout, aborting request`),i.abort()},e);try{let e=await fetch(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}},i=e=>e?.name===`NotAllowedError`?!0:e instanceof TypeError;var a=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.sdkIdentity=e.sdkIdentity,this.metaContext=e.metaContext,this.url=e.urlFactory(`client/telemetry/v1`,e.baseUrl),this.clientSession=e.clientSession}async report({context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r}){if(!this.executeRequests)return{success:!1,fatalError:!0};let{instanceId:i}=await this.clientSession,a={clientSdkKey:this.sdkKey,instanceId:i,metaContext:{sdkName:this.sdkIdentity.sdkName,sdkVersion:this.sdkIdentity.sdkVersion,appName:this.metaContext.appName,appVersion:this.metaContext.appVersion},context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r};if(this.isReportEmpty(a))return{success:!0,fatalError:!1};let o=await this.sendReport(a);return o.fatalError&&(this.executeRequests=!1),o}isReportEmpty(e){return t(e.discreteEvents)&&t(e.aggregatedEvents)&&n(e.droppedEvents)}async sendReport(e){try{let t=await r(5e3,this.url,{method:`POST`,body:JSON.stringify(e),keepalive:!0},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){let t=i(e);return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:t}}}isStatusFatal(e){return!!e&&e>=400&&e<500&&e!=429}},o=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},s=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||=new Date;let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},c=class{constructor(e){this.aggregator=new o,this.collectEvents=!0,this.started=!1,this.logger=e.logger,this.evaluationEventQueue=new s(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4,this.initialFlushIntervalDelay=e.initialFlushIntervalDelay??5e3}start(){this.started||!this.collectEvents||(this.started=!0,this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.initialFlushIntervalDelay))}async flushAndScheduleNext(){if(!this.collectEvents)return;let e;try{e=await this.flush()}catch(t){this.logger.warn(`[TelemetryEventCollector] Error flushing telemetry events: `,t),e={success:!1,fatalError:!1}}e.fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.collectEvents&&(this.cancelScheduledFlush(),this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay))}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout);try{await this.flush()}catch(e){this.logger.warn(`[TelemetryEventCollector] Error flushing telemetry events during close: `,e)}this.evaluationEventQueue.clear()}dispose(){this.close()}},l=class{constructor(e){this.valueIdGenerator=e,this.valueIdGenerator=e}async process(t){let n=await Promise.all(t.events.map(async t=>({...t,defaultValue:await e(t.defaultValue,this.valueIdGenerator),evaluatedValue:await e(t.evaluatedValue,this.valueIdGenerator)})));return{...t,events:n}}},u=class{constructor(){this.countsByKindSample=new Map,this.countBeyondSampleLimit=0}record(e){return this.startTime??=new Date,this.add(e,1)}absorb(e){for(let t of e)(this.startTime===void 0||t.startTime<this.startTime)&&(this.startTime=t.startTime),this.add(t.event.kindSample,t.count)}add(e,t){if(e!==void 0){let n=this.countsByKindSample.get(e);if(n!==void 0)return this.countsByKindSample.set(e,n+t),`known-kind-sample`;if(this.countsByKindSample.size<20)return this.countsByKindSample.set(e,t),`new-kind-sample`}let n=this.countBeyondSampleLimit===0;return this.countBeyondSampleLimit+=t,n?`first-beyond-sample-limit`:`beyond-sample-limit`}takeSnapshot(){let e=new Date,t=this.startTime??e,n=(n,r)=>({startTime:t,endTime:e,count:n,event:{reason:`invalid-context-kind`,kindSample:r}}),r=Array.from(this.countsByKindSample,([e,t])=>n(t,e)),i=this.countBeyondSampleLimit>0?[...r,n(this.countBeyondSampleLimit)]:r;return this.clear(),i}clear(){this.countsByKindSample.clear(),this.countBeyondSampleLimit=0,this.startTime=void 0}},d=class extends c{constructor(e){super(e),this.invalidContextSubmissions=new u,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)}recordInvalidContext(e){this.invalidContextSubmissions.record(e)}absorbInvalidContextSubmissions(e){this.invalidContextSubmissions.absorb(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),invalidContext:this.invalidContextSubmissions.takeSnapshot()},droppedEvents:{evaluatedConfig:e.droppedCount}})}};let f=(e,t)=>new URL(e,t?.toString()),p=(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`)},m=Math.ceil(128/Math.log2(62)),h=new TextEncoder,g=async e=>{if(typeof crypto>`u`||crypto.subtle==null)return;let t=await crypto.subtle.digest(`SHA-256`,h.encode(e?.toString()??``));return p(new Uint8Array(t,0,16),m)},_,v,y=new Promise(e=>{v=e}),b=(e,t,...n)=>({type:`Log`,payload:{level:e,message:t,args:n}}),x=e=>{try{self.postMessage(e)}catch{self.postMessage(b(e.payload.level,e.payload.message))}},S={debug:function(e,...t){x(b(`debug`,e,t))},info:function(e,...t){x(b(`info`,e,t))},warn:function(e,...t){x(b(`warn`,e,t))},error:function(e,...t){x(b(`error`,e,t))}};addEventListener(`message`,e=>{let t=e.data;if(t.type!=`Initialize`&&!_){S.info(`[TelemetryWorker] Ignoring ${t.type} because no collector is configured`);return}switch(S.debug(`[TelemetryWorker] Received '${t.type}' event:`,t),t.type){case`Initialize`:if(_){S.warn(`[TelemetryWorker] Received a duplicate Initialize event, ignoring it`);break}_=new d({...t.payload,baseUrl:f(t.payload.baseUrl),urlFactory:f,valueIdGenerator:g,logger:S,clientSession:y}),_.start();break;case`ClientSession`:v(t.payload);break;case`EvaluatedConfigEvent`:_?.evaluatedConfig(t.payload);break;case`InvalidContextSubmissions`:_?.absorbInvalidContextSubmissions(t.payload.entries);break;case`UpdateContext`:_?.updateContext(t.payload.context);break;case`Flush`:_?.forceFlush();break;case`Close`:(_?.close()??Promise.resolve()).then(()=>{self.postMessage({type:`Closed`}),self.close()});break}})})();"], { type: "application/javascript" })));
1721
1974
  } catch (error) {
1722
1975
  this.turnOff("[WebWorkerTelemetryClient] Telemetry is off because the telemetry Web Worker could not be created: ", error);
1723
1976
  return;
@@ -1736,6 +1989,7 @@ var WebWorkerTelemetryClient = class {
1736
1989
  this.handleWorkerEvent(message.data);
1737
1990
  };
1738
1991
  worker.postMessage(this.initializeMessage);
1992
+ this.forwardClientSession(worker);
1739
1993
  this.worker = worker;
1740
1994
  try {
1741
1995
  document.addEventListener("visibilitychange", this.visibilityHandler);
@@ -1747,6 +2001,15 @@ var WebWorkerTelemetryClient = class {
1747
2001
  this.forwardInvalidContextSubmissions(worker);
1748
2002
  return worker;
1749
2003
  }
2004
+ forwardClientSession(worker) {
2005
+ this.clientSession.then((clientSession) => {
2006
+ const clientSessionMessage = {
2007
+ type: "ClientSession",
2008
+ payload: clientSession
2009
+ };
2010
+ worker.postMessage(clientSessionMessage);
2011
+ });
2012
+ }
1750
2013
  forwardInvalidContextSubmissions(worker) {
1751
2014
  if (!worker) return;
1752
2015
  const entries = this.pendingInvalidContextSubmissions.takeSnapshot();
@@ -1879,6 +2142,25 @@ const generateValueId = async (v) => {
1879
2142
  return toBase62(new Uint8Array(hashBuffer, 0, 16), BASE62_LENGTH);
1880
2143
  };
1881
2144
  //#endregion
2145
+ //#region ../js-browser-client/src/local-storage-caching-provider.ts
2146
+ const toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
2147
+ const storageKeyFor = async (clientSdkKey) => {
2148
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(clientSdkKey));
2149
+ return CACHED_VALUES_STORAGE_KEY_PREFIX + toHex(new Uint8Array(digest));
2150
+ };
2151
+ const subtleCryptoAvailable = () => typeof crypto !== "undefined" && crypto.subtle != null;
2152
+ const localStorageCachingProvider = (clientSdkKey) => {
2153
+ const storage = localStorageIfAvailable();
2154
+ if (!storage || !subtleCryptoAvailable()) return;
2155
+ const storageKey = storageKeyFor(clientSdkKey);
2156
+ return {
2157
+ read: async () => storage.getItem(await storageKey),
2158
+ write: async (value) => storage.setItem(await storageKey, value),
2159
+ remove: async () => storage.removeItem(await storageKey),
2160
+ sizeLimit: () => DEFAULT_CACHING_SIZE_LIMIT_BYTES
2161
+ };
2162
+ };
2163
+ //#endregion
1882
2164
  //#region ../js-browser-client/src/BrowserConfigDirectorClient.ts
1883
2165
  /**
1884
2166
  * Creates a browser client. With a client SDK key it connects to ConfigDirector; with
@@ -1887,7 +2169,7 @@ const generateValueId = async (v) => {
1887
2169
  * and fails like a missing key otherwise.
1888
2170
  */
1889
2171
  const createBrowserClient = (clientSdkKeyOrOptions, sdkOptions, clientOptions) => {
1890
- return DefaultConfigDirectorClient.from(({ clientSdkKey, baseUrl, logger, metadata }) => new WebWorkerTelemetryClient({
2172
+ return DefaultConfigDirectorClient.from(({ clientSdkKey, baseUrl, logger, metadata, clientSession }) => new WebWorkerTelemetryClient({
1891
2173
  sdkKey: clientSdkKey,
1892
2174
  sdkIdentity: {
1893
2175
  sdkName: sdkOptions.sdkName,
@@ -1900,14 +2182,28 @@ const createBrowserClient = (clientSdkKeyOrOptions, sdkOptions, clientOptions) =
1900
2182
  logger,
1901
2183
  baseUrl,
1902
2184
  urlFactory: defaultUrlFactory,
1903
- valueIdGenerator: generateValueId
1904
- }), clientSdkKeyOrOptions, sdkOptions, clientOptions);
2185
+ valueIdGenerator: generateValueId,
2186
+ clientSession
2187
+ }), clientSdkKeyOrOptions, sdkOptions, clientOptions, { platformCachingProvider: localStorageCachingProvider });
1905
2188
  };
1906
2189
  //#endregion
1907
2190
  //#region ../js-client-core/src/installedTestClient.ts
1908
2191
  const INSTALLED_TEST_CLIENT_SLOT = Symbol.for("@configdirector/installed-test-client");
1909
2192
  const readInstalledTestClient = () => globalThis[INSTALLED_TEST_CLIENT_SLOT];
1910
2193
  //#endregion
2194
+ //#region ../shared/src/browser-half.ts
2195
+ const decideBrowserHalfSource = (browserHalf, serverConnectionMode) => {
2196
+ if (browserHalf.mode !== void 0 || browserHalf.values !== void 0) return "as-given";
2197
+ if (serverConnectionMode === "offline") return "inherit-offline";
2198
+ return "connect";
2199
+ };
2200
+ //#endregion
2201
+ //#region src/runtime/app/composables/useConfigDirectorClient.ts
2202
+ const useConfigDirectorClient = () => {
2203
+ const { $configDirectorClient } = useNuxtApp();
2204
+ return { client: $configDirectorClient };
2205
+ };
2206
+ //#endregion
1911
2207
  //#region ../shared/src/deepEqual.ts
1912
2208
  const deepEqual = (left, right) => {
1913
2209
  if (Object.is(left, right)) return true;
@@ -1933,17 +2229,17 @@ const decideMountAction = (client, context) => {
1933
2229
  return "none";
1934
2230
  };
1935
2231
  //#endregion
1936
- //#region ../shared/src/browser-half.ts
1937
- const decideBrowserHalfSource = (browserHalf, serverConnectionMode) => {
1938
- if (browserHalf.mode !== void 0 || browserHalf.values !== void 0) return "as-given";
1939
- if (serverConnectionMode === "offline") return "inherit-offline";
1940
- return "connect";
1941
- };
1942
- //#endregion
1943
- //#region src/runtime/app/composables/useConfigDirectorClient.ts
1944
- const useConfigDirectorClient = () => {
1945
- const { $configDirectorClient } = useNuxtApp();
1946
- return { client: $configDirectorClient };
2232
+ //#region src/runtime/client-startup.ts
2233
+ const decideStartupAction = (client, context, waitsForFirstContext) => {
2234
+ const mountAction = decideMountAction(client, context);
2235
+ if (!waitsForFirstContext) return mountAction;
2236
+ if (mountAction === "initialize" && context === void 0) return "waitForContext";
2237
+ if (mountAction === "none" && client.isInitializing) return "firstStartInFlight";
2238
+ return mountAction;
2239
+ };
2240
+ const decideContextUpdateAction = (client, context, waitsForFirstContext) => {
2241
+ if (waitsForFirstContext && decideMountAction(client, context) === "initialize") return "startClient";
2242
+ return "updateContext";
1947
2243
  };
1948
2244
  //#endregion
1949
2245
  //#region src/runtime/app/composables/useConfigDirectorContext.ts
@@ -1954,6 +2250,14 @@ const getEffectiveTimeout = (option) => {
1954
2250
  const useConfigDirectorContext = () => {
1955
2251
  const state = useState("configdirector-context");
1956
2252
  const { client } = useConfigDirectorClient();
2253
+ const { $configDirectorClientStartup } = useNuxtApp();
2254
+ const startup = $configDirectorClientStartup;
2255
+ const handContextToClient = (rawContext) => {
2256
+ switch (decideContextUpdateAction(client, rawContext, startup.waitsForFirstContext)) {
2257
+ case "startClient": return startup.start(rawContext);
2258
+ case "updateContext": return client.updateContext(rawContext);
2259
+ }
2260
+ };
1957
2261
  const updateContext = async (context, options) => {
1958
2262
  const rawContext = toRaw(context);
1959
2263
  const oldContext = JSON.stringify(state.value);
@@ -1963,7 +2267,7 @@ const useConfigDirectorContext = () => {
1963
2267
  const timeout = getEffectiveTimeout(options?.timeoutMilliseconds);
1964
2268
  let timer;
1965
2269
  try {
1966
- await Promise.race([client.updateContext(rawContext), new Promise((resolve) => {
2270
+ await Promise.race([handContextToClient(rawContext), new Promise((resolve) => {
1967
2271
  timer = setTimeout(() => resolve(), timeout);
1968
2272
  })]);
1969
2273
  } finally {
@@ -1990,28 +2294,51 @@ const OFFLINE_VALUES_ROUTE = "/_configdirector/offline-values";
1990
2294
  //#region src/runtime/plugin.client.ts
1991
2295
  const DEFAULT_INITIALIZATION_TIMEOUT_MILLISECONDS = 2e3;
1992
2296
  var plugin_client_default = defineNuxtPlugin(async (nuxtApp) => {
1993
- const runtimeConfig = useRuntimeConfig();
1994
- const logger = createDefaultLogger(runtimeConfig.public?.configdirector?.logLevel);
2297
+ const configdirector = useRuntimeConfig().public?.configdirector;
2298
+ const logger = createDefaultLogger(configdirector?.logLevel);
1995
2299
  const installedTestClient = readInstalledTestClient();
1996
- const client = installedTestClient ?? await buildClient(runtimeConfig.public?.configdirector, logger);
2300
+ const { client, runsOffline } = installedTestClient ? {
2301
+ client: installedTestClient,
2302
+ runsOffline: false
2303
+ } : await buildClient(configdirector, logger);
1997
2304
  logger.debug(installedTestClient ? "Installed ConfigDirector Nuxt plugin with a test client" : "Installed ConfigDirector Nuxt plugin");
1998
2305
  const readyStatus = shallowRef(client.isReady ? "ready" : "loading");
1999
2306
  client.on("clientReady", () => {
2000
2307
  readyStatus.value = "ready";
2001
2308
  });
2309
+ const settleReadyStatus = () => {
2310
+ if (!client.isReady) readyStatus.value = "default";
2311
+ };
2312
+ const startup = {
2313
+ waitsForFirstContext: configdirector?.deferInitialization === true && !runsOffline,
2314
+ start: async (context) => {
2315
+ await client.initialize(context);
2316
+ settleReadyStatus();
2317
+ }
2318
+ };
2002
2319
  nuxtApp.hooks.hook("app:created", async () => {
2003
2320
  const { context } = useConfigDirectorContext();
2004
2321
  const rawContext = toRaw(context.value);
2005
- const mountAction = decideMountAction(client, rawContext);
2006
- logger.debug(`Browser client mount action: ${mountAction}`);
2007
- if (mountAction === "initialize") await client.initialize(rawContext);
2008
- else if (mountAction === "updateContext" && rawContext !== void 0) await client.updateContext(rawContext);
2009
- if (!client.isReady) readyStatus.value = "default";
2010
- logger.debug(`Browser client mount action awaited, ready status: ${client.isReady}`);
2322
+ const startupAction = decideStartupAction(client, rawContext, startup.waitsForFirstContext);
2323
+ logger.debug(`Browser client startup action: ${startupAction}`);
2324
+ switch (startupAction) {
2325
+ case "waitForContext": return;
2326
+ case "firstStartInFlight": return;
2327
+ case "initialize":
2328
+ await client.initialize(rawContext);
2329
+ break;
2330
+ case "updateContext":
2331
+ await client.updateContext(rawContext);
2332
+ break;
2333
+ case "none": break;
2334
+ }
2335
+ settleReadyStatus();
2336
+ logger.debug(`Browser client startup action awaited, ready status: ${client.isReady}`);
2011
2337
  });
2012
2338
  return { provide: {
2013
2339
  configDirectorClient: client,
2014
- configDirectorClientReadyStatus: readonly(readyStatus)
2340
+ configDirectorClientReadyStatus: readonly(readyStatus),
2341
+ configDirectorClientStartup: startup
2015
2342
  } };
2016
2343
  });
2017
2344
  const buildClient = async (configdirector, logger) => {
@@ -2021,23 +2348,26 @@ const buildClient = async (configdirector, logger) => {
2021
2348
  logger.error(message);
2022
2349
  throw new ConfigDirectorInitializationError(message);
2023
2350
  }
2024
- return createBrowserClient(configdirector?.clientSdkKey || void 0, {
2025
- sdkName: "nuxt-sdk",
2026
- sdkVersion: "1.11.1"
2027
- }, {
2028
- metadata: {
2029
- appName: configdirector?.appName,
2030
- appVersion: configdirector?.appVersion
2031
- },
2032
- connection: {
2033
- url: configdirector?.baseUrl || void 0,
2034
- mode: browserHalf.mode,
2035
- values: browserHalf.values,
2036
- pollingInterval: configdirector?.connection?.pollingInterval || void 0,
2037
- timeout: configdirector?.connection?.timeout || DEFAULT_INITIALIZATION_TIMEOUT_MILLISECONDS
2038
- },
2039
- logger
2040
- });
2351
+ return {
2352
+ client: createBrowserClient(configdirector?.clientSdkKey || void 0, {
2353
+ sdkName: "nuxt-sdk",
2354
+ sdkVersion: "1.12.1"
2355
+ }, {
2356
+ metadata: {
2357
+ appName: configdirector?.appName,
2358
+ appVersion: configdirector?.appVersion
2359
+ },
2360
+ connection: {
2361
+ url: configdirector?.baseUrl || void 0,
2362
+ mode: browserHalf.mode,
2363
+ values: browserHalf.values,
2364
+ pollingInterval: configdirector?.connection?.pollingInterval || void 0,
2365
+ timeout: configdirector?.connection?.timeout || DEFAULT_INITIALIZATION_TIMEOUT_MILLISECONDS
2366
+ },
2367
+ logger
2368
+ }),
2369
+ runsOffline: browserHalf.mode === "offline"
2370
+ };
2041
2371
  };
2042
2372
  const browserHalfConnectionOf = async (configdirector, logger) => {
2043
2373
  const givenConnection = {