@configdirector/nuxt-sdk 1.11.0 → 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/useConfigDirectorServerClient.mjs +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/server/composables/useConfigDirectorServerHooks.mjs +2 -3
- 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
|
@@ -58,6 +58,37 @@ const admitContext = (context) => {
|
|
|
58
58
|
};
|
|
59
59
|
};
|
|
60
60
|
//#endregion
|
|
61
|
+
//#region ../shared/src/deepEqual.ts
|
|
62
|
+
const deepEqual = (left, right) => {
|
|
63
|
+
if (Object.is(left, right)) return true;
|
|
64
|
+
if (typeof left !== "object" || typeof right !== "object" || left === null || right === null) return false;
|
|
65
|
+
if (Object.getPrototypeOf(left) !== Object.getPrototypeOf(right)) return false;
|
|
66
|
+
if (left instanceof Date && right instanceof Date) return left.getTime() === right.getTime();
|
|
67
|
+
if (Array.isArray(left) && Array.isArray(right)) return left.length === right.length && left.every((item, index) => deepEqual(item, right[index]));
|
|
68
|
+
if (!isPlainObject(left) || !isPlainObject(right)) return false;
|
|
69
|
+
const leftKeys = Object.keys(left);
|
|
70
|
+
const rightKeys = Object.keys(right);
|
|
71
|
+
return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.prototype.hasOwnProperty.call(right, key) && deepEqual(left[key], right[key]));
|
|
72
|
+
};
|
|
73
|
+
const isPlainObject = (value) => {
|
|
74
|
+
const prototype = Object.getPrototypeOf(value);
|
|
75
|
+
return prototype === Object.prototype || prototype === null;
|
|
76
|
+
};
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region ../js-client-core/src/mountAction.ts
|
|
79
|
+
const decideMountAction = (client, context) => {
|
|
80
|
+
if (client.isInitializing) return "none";
|
|
81
|
+
if (!client.isReady) return "initialize";
|
|
82
|
+
if (context !== void 0 && !deepEqual(context, client.context)) return "updateContext";
|
|
83
|
+
return "none";
|
|
84
|
+
};
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/runtime/client-startup.ts
|
|
87
|
+
const decideContextUpdateAction = (client, context, waitsForFirstContext) => {
|
|
88
|
+
if (waitsForFirstContext && decideMountAction(client, context) === "initialize") return "startClient";
|
|
89
|
+
return "updateContext";
|
|
90
|
+
};
|
|
91
|
+
//#endregion
|
|
61
92
|
//#region src/runtime/app/composables/useConfigDirectorContext.ts
|
|
62
93
|
const DEFAULT_TIMEOUT = 3e3;
|
|
63
94
|
const getEffectiveTimeout = (option) => {
|
|
@@ -66,6 +97,14 @@ const getEffectiveTimeout = (option) => {
|
|
|
66
97
|
const useConfigDirectorContext = () => {
|
|
67
98
|
const state = useState("configdirector-context");
|
|
68
99
|
const { client } = useConfigDirectorClient();
|
|
100
|
+
const { $configDirectorClientStartup } = useNuxtApp();
|
|
101
|
+
const startup = $configDirectorClientStartup;
|
|
102
|
+
const handContextToClient = (rawContext) => {
|
|
103
|
+
switch (decideContextUpdateAction(client, rawContext, startup.waitsForFirstContext)) {
|
|
104
|
+
case "startClient": return startup.start(rawContext);
|
|
105
|
+
case "updateContext": return client.updateContext(rawContext);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
69
108
|
const updateContext = async (context, options) => {
|
|
70
109
|
const rawContext = toRaw(context);
|
|
71
110
|
const oldContext = JSON.stringify(state.value);
|
|
@@ -75,7 +114,7 @@ const useConfigDirectorContext = () => {
|
|
|
75
114
|
const timeout = getEffectiveTimeout(options?.timeoutMilliseconds);
|
|
76
115
|
let timer;
|
|
77
116
|
try {
|
|
78
|
-
await Promise.race([
|
|
117
|
+
await Promise.race([handContextToClient(rawContext), new Promise((resolve) => {
|
|
79
118
|
timer = setTimeout(() => resolve(), timeout);
|
|
80
119
|
})]);
|
|
81
120
|
} finally {
|
|
@@ -98,6 +137,10 @@ var plugin_server_default = defineNuxtPlugin((nuxtApp) => {
|
|
|
98
137
|
if (!serverSdkClient) throw "The nitro ConfigDirector plugin was not initialized";
|
|
99
138
|
const client = createSsrClient(serverSdkClient);
|
|
100
139
|
const readyStatus = shallowRef(serverSdkClient.isReady ? "ready" : "default");
|
|
140
|
+
const startup = {
|
|
141
|
+
waitsForFirstContext: false,
|
|
142
|
+
start: (context) => client.initialize(context)
|
|
143
|
+
};
|
|
101
144
|
nuxtApp.hooks.hook("app:created", async () => {
|
|
102
145
|
logger.debug("Updating ConfigDirector context for SSR evaluation");
|
|
103
146
|
const { context } = useConfigDirectorContext();
|
|
@@ -105,7 +148,8 @@ var plugin_server_default = defineNuxtPlugin((nuxtApp) => {
|
|
|
105
148
|
});
|
|
106
149
|
return { provide: {
|
|
107
150
|
configDirectorClient: client,
|
|
108
|
-
configDirectorClientReadyStatus: readonly(readyStatus)
|
|
151
|
+
configDirectorClientReadyStatus: readonly(readyStatus),
|
|
152
|
+
configDirectorClientStartup: startup
|
|
109
153
|
} };
|
|
110
154
|
});
|
|
111
155
|
var SsrClient = class {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as ConfigDirectorClient } from "../../types-
|
|
1
|
+
import { n as ConfigDirectorClient } from "../../types-BoDJiQsr.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/server/composables/useConfigDirectorServerClient.d.ts
|
|
4
4
|
declare const useConfigDirectorServerClient: () => ConfigDirectorClient;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as ConfigDirectorClient } from "../../types-
|
|
1
|
+
import { n as ConfigDirectorClient } from "../../types-BoDJiQsr.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/server/composables/useConfigDirectorServerClient.d.ts
|
|
4
4
|
declare const useConfigDirectorServerClient: () => ConfigDirectorClient;
|
|
@@ -2,7 +2,7 @@ import { useNitroApp } from "nitropack/runtime";
|
|
|
2
2
|
//#region src/runtime/server/composables/useConfigDirectorServerClient.ts
|
|
3
3
|
const useConfigDirectorServerClient = () => {
|
|
4
4
|
const serverSdkClient = useNitroApp().configDirectorClient;
|
|
5
|
-
if (!serverSdkClient) throw "The
|
|
5
|
+
if (!serverSdkClient) throw new Error("The ConfigDirector Nitro plugin was not initialized");
|
|
6
6
|
return serverSdkClient;
|
|
7
7
|
};
|
|
8
8
|
//#endregion
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ClientHooks } from "../../types-
|
|
1
|
+
import { t as ClientHooks } from "../../types-BoDJiQsr.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-BoDJiQsr.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/server/composables/useConfigDirectorServerHooks.d.ts
|
|
4
4
|
declare const useConfigDirectorServerHooks: (hooks: ClientHooks) => void;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useConfigDirectorServerClient } from "./useConfigDirectorServerClient.mjs";
|
|
2
2
|
//#region src/runtime/server/composables/useConfigDirectorServerHooks.ts
|
|
3
3
|
const useConfigDirectorServerHooks = (hooks) => {
|
|
4
|
-
const client =
|
|
5
|
-
if (!client) throw new Error("The ConfigDirector Nitro plugin was not initialized");
|
|
4
|
+
const client = useConfigDirectorServerClient();
|
|
6
5
|
const entries = Object.entries(hooks);
|
|
7
6
|
for (const [event, handler] of entries) {
|
|
8
7
|
if (!handler) continue;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ConfigValueType, i as ConfigState, n as ConfigDirectorContext, r as ConfigEvaluation } from "./native-values-
|
|
1
|
+
import { a as ConfigValueType, i as ConfigState, n as ConfigDirectorContext, r as ConfigEvaluation } from "./native-values-ChQdi7c7.mjs";
|
|
2
2
|
|
|
3
3
|
//#region ../js-server-sdk/src/types.d.ts
|
|
4
4
|
type ClientEvents = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ConfigValueType, n as ConfigDirectorContext, r as ConfigEvaluation } from "./native-values-
|
|
1
|
+
import { a as ConfigValueType, n as ConfigDirectorContext, r as ConfigEvaluation } from "./native-values-ChQdi7c7.mjs";
|
|
2
2
|
|
|
3
3
|
//#region ../js-client-core/src/Emitter.d.ts
|
|
4
4
|
type EventType = string | symbol;
|
|
@@ -17,6 +17,7 @@ type ClientHooks = {
|
|
|
17
17
|
contextUpdated?: HookHandler<"contextUpdated"> | HookHandler<"contextUpdated">[];
|
|
18
18
|
configEvaluated?: HookHandler<"configEvaluated"> | HookHandler<"configEvaluated">[];
|
|
19
19
|
connectionError?: HookHandler<"connectionError"> | HookHandler<"connectionError">[];
|
|
20
|
+
cachedValuesLoaded?: HookHandler<"cachedValuesLoaded"> | HookHandler<"cachedValuesLoaded">[];
|
|
20
21
|
};
|
|
21
22
|
type ClientConnectAction = "initialization" | "context update" | "network resume";
|
|
22
23
|
type HookHandler<TEvent extends keyof ClientEvents> = (payload: ClientEvents[TEvent]) => void;
|
|
@@ -37,6 +38,7 @@ type ClientEvents = {
|
|
|
37
38
|
connectionError: {
|
|
38
39
|
error: Error;
|
|
39
40
|
};
|
|
41
|
+
cachedValuesLoaded: Record<string, never>;
|
|
40
42
|
};
|
|
41
43
|
type WatchHandler<T extends ConfigValueType> = (message: T) => void;
|
|
42
44
|
/**
|
package/dist/testing.d.mts
CHANGED
|
@@ -19,7 +19,7 @@ interface ConfigDirectorLogger {
|
|
|
19
19
|
warn(message: string, ...args: any): void;
|
|
20
20
|
error(message: string, ...args: any): void;
|
|
21
21
|
}
|
|
22
|
-
type EvaluationReason = "found-match" | "config-state-missing" | "client-not-ready" | "type-mismatch" | "value-missing" | "invalid-number" | "invalid-json" | "invalid-boolean";
|
|
22
|
+
type EvaluationReason = "found-match" | "config-state-missing" | "client-not-ready" | "type-mismatch" | "value-missing" | "invalid-number" | "invalid-json" | "invalid-boolean" | "cached";
|
|
23
23
|
type ConfigEvaluation = {
|
|
24
24
|
key: string;
|
|
25
25
|
value: ConfigValueType;
|
|
@@ -38,9 +38,11 @@ type ConfigDirectorContext = {
|
|
|
38
38
|
* The context's identifier. This should be a value that uniquely identifies the user or other
|
|
39
39
|
* entity among contexts of the same {@link kind}.
|
|
40
40
|
* In the case of anonymous users, you could generate a UUID or alternatively not provide
|
|
41
|
-
* the {@link id}
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* the {@link id}. In a client SDK, ConfigDirector then derives an identifier from an ID the SDK
|
|
42
|
+
* stores for the app install or the website, so it stays the same until the app is uninstalled
|
|
43
|
+
* or the site's data is cleared. However, keep in mind that this value is used for segmenting
|
|
44
|
+
* contexts in percentage rollouts, and changes to the {@link id} could result in the context
|
|
45
|
+
* being assigned to a different percentile.
|
|
44
46
|
*/
|
|
45
47
|
id?: string;
|
|
46
48
|
/**
|
|
@@ -105,6 +107,7 @@ type ClientEvents = {
|
|
|
105
107
|
connectionError: {
|
|
106
108
|
error: Error;
|
|
107
109
|
};
|
|
110
|
+
cachedValuesLoaded: Record<string, never>;
|
|
108
111
|
};
|
|
109
112
|
type WatchHandler<T extends ConfigValueType> = (message: T) => void;
|
|
110
113
|
/**
|
package/dist/testing.mjs
CHANGED
|
@@ -920,10 +920,27 @@ const generateInstanceId = () => {
|
|
|
920
920
|
});
|
|
921
921
|
};
|
|
922
922
|
//#endregion
|
|
923
|
+
//#region ../js-client-core/src/browser-globals.ts
|
|
924
|
+
const read = (readValue) => {
|
|
925
|
+
try {
|
|
926
|
+
return readValue(globalThis);
|
|
927
|
+
} catch {
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
const readUserAgent = () => read((globals) => globals.navigator?.userAgent);
|
|
932
|
+
const readHost = () => read((globals) => globals.location?.host);
|
|
933
|
+
const localStorageIfAvailable = () => {
|
|
934
|
+
try {
|
|
935
|
+
return globalThis.localStorage ?? void 0;
|
|
936
|
+
} catch {
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
//#endregion
|
|
923
941
|
//#region ../js-client-core/src/client-session.ts
|
|
924
942
|
const STORAGE_KEY = "configdirector-sdk:instance-id";
|
|
925
943
|
const RECORD_VERSION = 1;
|
|
926
|
-
const MAX_AGE_MS = 360 * 60 * 1e3;
|
|
927
944
|
const STORAGE_DEADLINE_MS = 1e3;
|
|
928
945
|
const CHECKSUM_SALT = "configdirector.instanceId.v1";
|
|
929
946
|
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}$/;
|
|
@@ -940,13 +957,6 @@ const checksumFor = (id, createdAt) => {
|
|
|
940
957
|
const low = fnv1a(payload, 2166136261);
|
|
941
958
|
return fnv1a(`${low}|${payload}`, 16777619).toString(16).padStart(8, "0") + low.toString(16).padStart(8, "0");
|
|
942
959
|
};
|
|
943
|
-
const localStorageIfAvailable = () => {
|
|
944
|
-
try {
|
|
945
|
-
return globalThis.localStorage ?? void 0;
|
|
946
|
-
} catch {
|
|
947
|
-
return;
|
|
948
|
-
}
|
|
949
|
-
};
|
|
950
960
|
const isUsableRecord = (record) => {
|
|
951
961
|
if (!record || typeof record !== "object") return false;
|
|
952
962
|
const { v, id, createdAt, checksum } = record;
|
|
@@ -975,11 +985,6 @@ const readStoredInstanceId = async (storage, logger) => {
|
|
|
975
985
|
logger.debug("[ConfigDirectorClient] The stored instance id failed validation, generating a new one");
|
|
976
986
|
return { outcome: "nothing usable" };
|
|
977
987
|
}
|
|
978
|
-
const age = Date.now() - record.createdAt;
|
|
979
|
-
if (age < 0 || age >= MAX_AGE_MS) {
|
|
980
|
-
logger.debug("[ConfigDirectorClient] The stored instance id has expired, generating a new one");
|
|
981
|
-
return { outcome: "nothing usable" };
|
|
982
|
-
}
|
|
983
988
|
return {
|
|
984
989
|
outcome: "stored",
|
|
985
990
|
instanceId: record.id
|
|
@@ -1195,17 +1200,6 @@ var PollingTransport = class extends AbstractPollingTransport {
|
|
|
1195
1200
|
}
|
|
1196
1201
|
};
|
|
1197
1202
|
//#endregion
|
|
1198
|
-
//#region ../js-client-core/src/browser-globals.ts
|
|
1199
|
-
const read = (readValue) => {
|
|
1200
|
-
try {
|
|
1201
|
-
return readValue(globalThis);
|
|
1202
|
-
} catch {
|
|
1203
|
-
return;
|
|
1204
|
-
}
|
|
1205
|
-
};
|
|
1206
|
-
const readUserAgent = () => read((globals) => globals.navigator?.userAgent);
|
|
1207
|
-
const readHost = () => read((globals) => globals.location?.host);
|
|
1208
|
-
//#endregion
|
|
1209
1203
|
//#region ../shared/src/context-kind.ts
|
|
1210
1204
|
const CONTEXT_KIND_MAXIMUM_LENGTH = 64;
|
|
1211
1205
|
const KIND_SAMPLE_MAXIMUM_CHARACTERS = 128;
|
|
@@ -1246,6 +1240,176 @@ const admitContext = (context) => {
|
|
|
1246
1240
|
};
|
|
1247
1241
|
};
|
|
1248
1242
|
//#endregion
|
|
1243
|
+
//#region ../shared/src/utf8-byte-length.ts
|
|
1244
|
+
const isHighSurrogate = (codeUnit) => codeUnit >= 55296 && codeUnit <= 56319;
|
|
1245
|
+
const isLowSurrogate = (codeUnit) => codeUnit >= 56320 && codeUnit <= 57343;
|
|
1246
|
+
const utf8ByteLength = (text) => {
|
|
1247
|
+
let bytes = 0;
|
|
1248
|
+
for (let i = 0; i < text.length; i++) {
|
|
1249
|
+
const codeUnit = text.charCodeAt(i);
|
|
1250
|
+
if (codeUnit <= 127) bytes += 1;
|
|
1251
|
+
else if (codeUnit <= 2047) bytes += 2;
|
|
1252
|
+
else if (isHighSurrogate(codeUnit) && i + 1 < text.length && isLowSurrogate(text.charCodeAt(i + 1))) {
|
|
1253
|
+
bytes += 4;
|
|
1254
|
+
i++;
|
|
1255
|
+
} else bytes += 3;
|
|
1256
|
+
}
|
|
1257
|
+
return bytes;
|
|
1258
|
+
};
|
|
1259
|
+
//#endregion
|
|
1260
|
+
//#region ../js-client-core/src/cached-values.ts
|
|
1261
|
+
const ENTRY_FORMAT_VERSION = 1;
|
|
1262
|
+
const DEFAULT_CONTEXT_KIND = "user";
|
|
1263
|
+
const planCaching = (connectionPlan, caching, platformProvider) => {
|
|
1264
|
+
if (connectionPlan.kind === "stay-offline") return {
|
|
1265
|
+
kind: "no cache",
|
|
1266
|
+
reason: "offline mode"
|
|
1267
|
+
};
|
|
1268
|
+
if (caching?.enabled === false) return {
|
|
1269
|
+
kind: "no cache",
|
|
1270
|
+
reason: "turned off"
|
|
1271
|
+
};
|
|
1272
|
+
const provider = caching?.provider ?? platformProvider?.(connectionPlan.clientSdkKey);
|
|
1273
|
+
if (!provider) return {
|
|
1274
|
+
kind: "no cache",
|
|
1275
|
+
reason: "no provider available"
|
|
1276
|
+
};
|
|
1277
|
+
return {
|
|
1278
|
+
kind: "cache",
|
|
1279
|
+
provider,
|
|
1280
|
+
clientSdkKey: connectionPlan.clientSdkKey
|
|
1281
|
+
};
|
|
1282
|
+
};
|
|
1283
|
+
const contextTagFor = (context, instanceId) => {
|
|
1284
|
+
const contextKind = context?.kind ?? DEFAULT_CONTEXT_KIND;
|
|
1285
|
+
if (context?.id != null) return {
|
|
1286
|
+
contextKind,
|
|
1287
|
+
contextId: context.id
|
|
1288
|
+
};
|
|
1289
|
+
return {
|
|
1290
|
+
contextKind,
|
|
1291
|
+
anonymousInstanceId: instanceId
|
|
1292
|
+
};
|
|
1293
|
+
};
|
|
1294
|
+
const sameContextTag = (stored, launch) => {
|
|
1295
|
+
if (stored.contextKind !== launch.contextKind) return false;
|
|
1296
|
+
if ("contextId" in stored && "contextId" in launch) return stored.contextId === launch.contextId;
|
|
1297
|
+
if ("anonymousInstanceId" in stored && "anonymousInstanceId" in launch) return stored.anonymousInstanceId === launch.anonymousInstanceId;
|
|
1298
|
+
return false;
|
|
1299
|
+
};
|
|
1300
|
+
const decideCachedValuesAdoption = (cachedValues, launchContextTag, hasReceivedLiveValues) => {
|
|
1301
|
+
if (hasReceivedLiveValues) return "live values already arrived";
|
|
1302
|
+
if (!sameContextTag(cachedValues.contextTag, launchContextTag)) return "context mismatch";
|
|
1303
|
+
return "adopt";
|
|
1304
|
+
};
|
|
1305
|
+
const isContextTag = (candidate) => {
|
|
1306
|
+
if (!candidate || typeof candidate !== "object") return false;
|
|
1307
|
+
const { contextKind, contextId, anonymousInstanceId } = candidate;
|
|
1308
|
+
if (typeof contextKind !== "string") return false;
|
|
1309
|
+
return typeof contextId === "string" || typeof anonymousInstanceId === "string";
|
|
1310
|
+
};
|
|
1311
|
+
const isConfigStateMap = (candidate) => {
|
|
1312
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return false;
|
|
1313
|
+
return Object.values(candidate).every((config) => config && typeof config === "object" && typeof config.id === "string" && typeof config.key === "string");
|
|
1314
|
+
};
|
|
1315
|
+
const isEntryFor = (clientSdkKey, candidate) => {
|
|
1316
|
+
if (!candidate || typeof candidate !== "object") return false;
|
|
1317
|
+
const entry = candidate;
|
|
1318
|
+
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";
|
|
1319
|
+
};
|
|
1320
|
+
var CachedValuesStore = class {
|
|
1321
|
+
warned = /* @__PURE__ */ new Set();
|
|
1322
|
+
pendingRead;
|
|
1323
|
+
queuedCachedValues;
|
|
1324
|
+
writing = false;
|
|
1325
|
+
constructor(provider, clientSdkKey, logger) {
|
|
1326
|
+
this.provider = provider;
|
|
1327
|
+
this.clientSdkKey = clientSdkKey;
|
|
1328
|
+
this.logger = logger;
|
|
1329
|
+
}
|
|
1330
|
+
read() {
|
|
1331
|
+
this.pendingRead ??= this.readEntry();
|
|
1332
|
+
return this.pendingRead;
|
|
1333
|
+
}
|
|
1334
|
+
replace(cachedValues) {
|
|
1335
|
+
this.queuedCachedValues = cachedValues;
|
|
1336
|
+
this.writeQueued();
|
|
1337
|
+
}
|
|
1338
|
+
async readEntry() {
|
|
1339
|
+
let raw;
|
|
1340
|
+
try {
|
|
1341
|
+
raw = await this.provider.read();
|
|
1342
|
+
} catch (error) {
|
|
1343
|
+
this.warnOnce("read failed", "Could not read the values kept on the device, starting without them:", error);
|
|
1344
|
+
return { outcome: "failed" };
|
|
1345
|
+
}
|
|
1346
|
+
if (raw == null || raw === "") return { outcome: "nothing stored" };
|
|
1347
|
+
const entry = this.parseEntry(raw);
|
|
1348
|
+
if (!entry) {
|
|
1349
|
+
this.warnOnce("unreadable", "The values kept on the device could not be read and were removed, starting without them");
|
|
1350
|
+
await this.removeEntry();
|
|
1351
|
+
return { outcome: "unreadable" };
|
|
1352
|
+
}
|
|
1353
|
+
return {
|
|
1354
|
+
outcome: "stored",
|
|
1355
|
+
cachedValues: entry
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
parseEntry(raw) {
|
|
1359
|
+
try {
|
|
1360
|
+
const candidate = JSON.parse(raw);
|
|
1361
|
+
return isEntryFor(this.clientSdkKey, candidate) ? candidate : void 0;
|
|
1362
|
+
} catch {
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
async writeQueued() {
|
|
1367
|
+
if (this.writing) return;
|
|
1368
|
+
this.writing = true;
|
|
1369
|
+
try {
|
|
1370
|
+
while (this.queuedCachedValues) {
|
|
1371
|
+
const cachedValues = this.queuedCachedValues;
|
|
1372
|
+
this.queuedCachedValues = void 0;
|
|
1373
|
+
await this.writeEntry(cachedValues);
|
|
1374
|
+
}
|
|
1375
|
+
} finally {
|
|
1376
|
+
this.writing = false;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
async writeEntry(cachedValues) {
|
|
1380
|
+
const entry = {
|
|
1381
|
+
formatVersion: ENTRY_FORMAT_VERSION,
|
|
1382
|
+
clientSdkKey: this.clientSdkKey,
|
|
1383
|
+
...cachedValues
|
|
1384
|
+
};
|
|
1385
|
+
const serialized = JSON.stringify(entry);
|
|
1386
|
+
const size = utf8ByteLength(serialized);
|
|
1387
|
+
const limit = this.provider.sizeLimit();
|
|
1388
|
+
if (size > limit) {
|
|
1389
|
+
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`);
|
|
1390
|
+
await this.removeEntry();
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
try {
|
|
1394
|
+
await this.provider.write(serialized);
|
|
1395
|
+
} catch (error) {
|
|
1396
|
+
this.warnOnce("write failed", "Could not keep the values on the device:", error);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
async removeEntry() {
|
|
1400
|
+
try {
|
|
1401
|
+
await this.provider.remove();
|
|
1402
|
+
} catch (error) {
|
|
1403
|
+
this.warnOnce("remove failed", "Could not remove the values kept on the device:", error);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
warnOnce(warning, message, ...details) {
|
|
1407
|
+
if (this.warned.has(warning)) return;
|
|
1408
|
+
this.warned.add(warning);
|
|
1409
|
+
this.logger.warn(`[ConfigDirectorClient] ${message}`, ...details);
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
//#endregion
|
|
1249
1413
|
//#region ../js-client-core/src/DefaultConfigDirectorClient.ts
|
|
1250
1414
|
const MAX_EXPONENTIAL_DELAY = 9;
|
|
1251
1415
|
var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
@@ -1263,11 +1427,15 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1263
1427
|
readyPromise;
|
|
1264
1428
|
readyResolve;
|
|
1265
1429
|
currentContext;
|
|
1430
|
+
submittedContext;
|
|
1266
1431
|
connectionPlan;
|
|
1267
1432
|
clientSession;
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1433
|
+
cachedValuesStore;
|
|
1434
|
+
servingCachedValues = false;
|
|
1435
|
+
cachedValuesAdoptionStarted = false;
|
|
1436
|
+
static from(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions, internalClientOptions) {
|
|
1437
|
+
if (typeof clientSdkKeyOrOptions === "object") return new DefaultConfigDirectorClient(liveTelemetryClient, void 0, sdkOptions, clientSdkKeyOrOptions, internalClientOptions);
|
|
1438
|
+
return new DefaultConfigDirectorClient(liveTelemetryClient, clientSdkKeyOrOptions, sdkOptions, clientOptions, internalClientOptions);
|
|
1271
1439
|
}
|
|
1272
1440
|
constructor(liveTelemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
|
|
1273
1441
|
this.logger = clientOptions?.logger ?? createDefaultLogger();
|
|
@@ -1275,10 +1443,12 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1275
1443
|
this.timeout = clientOptions?.connection?.timeout ?? 3e3;
|
|
1276
1444
|
const urlFactory = internalClientOptions?.urlFactory ?? defaultUrlFactory;
|
|
1277
1445
|
this.connectionPlan = planConnection(clientSdkKey, clientOptions?.connection, urlFactory);
|
|
1446
|
+
this.cachedValuesStore = this.buildCachedValuesStore(planCaching(this.connectionPlan, clientOptions?.caching, internalClientOptions?.platformCachingProvider));
|
|
1278
1447
|
this.telemetryClient = this.buildTelemetryClient(liveTelemetryClient, clientOptions);
|
|
1279
1448
|
this.transport = internalClientOptions?.transport ?? this.buildTransport(sdkOptions, clientOptions, urlFactory, internalClientOptions);
|
|
1280
1449
|
this.transport.on("configSetReceived", (configSet) => {
|
|
1281
1450
|
this.hasReceivedConfigSet = true;
|
|
1451
|
+
this.servingCachedValues = false;
|
|
1282
1452
|
this.readyResolve?.();
|
|
1283
1453
|
const removedKeys = this.findKeysRemovedBy(configSet);
|
|
1284
1454
|
if (!this.configSet || configSet.kind == "full") this.configSet = configSet;
|
|
@@ -1286,6 +1456,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1286
1456
|
...this.configSet.configs,
|
|
1287
1457
|
...configSet.configs
|
|
1288
1458
|
};
|
|
1459
|
+
this.keepValuesOnTheDevice(this.configSet);
|
|
1289
1460
|
const keys = Object.keys(configSet.configs);
|
|
1290
1461
|
this.emit("configsUpdated", {
|
|
1291
1462
|
keys,
|
|
@@ -1307,6 +1478,80 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1307
1478
|
this.registerHandler("configsUpdated", clientOptions?.hooks?.configsUpdated);
|
|
1308
1479
|
this.registerHandler("contextUpdated", clientOptions?.hooks?.contextUpdated);
|
|
1309
1480
|
this.registerHandler("connectionError", clientOptions?.hooks?.connectionError);
|
|
1481
|
+
this.registerHandler("cachedValuesLoaded", clientOptions?.hooks?.cachedValuesLoaded);
|
|
1482
|
+
}
|
|
1483
|
+
buildCachedValuesStore(cachingPlan) {
|
|
1484
|
+
switch (cachingPlan.kind) {
|
|
1485
|
+
case "no cache":
|
|
1486
|
+
this.logger.debug(`[ConfigDirectorClient] No values are kept on the device: ${cachingPlan.reason}`);
|
|
1487
|
+
return;
|
|
1488
|
+
case "cache": {
|
|
1489
|
+
const store = new CachedValuesStore(cachingPlan.provider, cachingPlan.clientSdkKey, this.logger);
|
|
1490
|
+
store.read();
|
|
1491
|
+
return store;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
startCachedValuesAdoption() {
|
|
1496
|
+
const store = this.cachedValuesStore;
|
|
1497
|
+
if (!store || this.cachedValuesAdoptionStarted) return;
|
|
1498
|
+
this.cachedValuesAdoptionStarted = true;
|
|
1499
|
+
this.adoptCachedValues(store);
|
|
1500
|
+
}
|
|
1501
|
+
async adoptCachedValues(store) {
|
|
1502
|
+
const read = await store.read();
|
|
1503
|
+
switch (read.outcome) {
|
|
1504
|
+
case "nothing stored":
|
|
1505
|
+
case "unreadable":
|
|
1506
|
+
case "failed": return;
|
|
1507
|
+
case "stored": break;
|
|
1508
|
+
}
|
|
1509
|
+
const { instanceId } = await this.clientSession;
|
|
1510
|
+
const launchContextTag = contextTagFor(this.submittedContext, instanceId);
|
|
1511
|
+
const adoption = decideCachedValuesAdoption(read.cachedValues, launchContextTag, this.hasReceivedConfigSet);
|
|
1512
|
+
switch (adoption) {
|
|
1513
|
+
case "adopt":
|
|
1514
|
+
this.serveCachedValues(read.cachedValues);
|
|
1515
|
+
return;
|
|
1516
|
+
case "live values already arrived":
|
|
1517
|
+
case "context mismatch":
|
|
1518
|
+
this.logger.debug(`[ConfigDirectorClient] The values kept on the device were not adopted: ${adoption}`);
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
serveCachedValues(cachedValues) {
|
|
1523
|
+
const { environmentId, projectId, configs } = cachedValues;
|
|
1524
|
+
this.configSet = {
|
|
1525
|
+
environmentId,
|
|
1526
|
+
projectId,
|
|
1527
|
+
configs,
|
|
1528
|
+
kind: "full"
|
|
1529
|
+
};
|
|
1530
|
+
this.servingCachedValues = true;
|
|
1531
|
+
const keys = Object.keys(configs);
|
|
1532
|
+
this.emit("cachedValuesLoaded", {});
|
|
1533
|
+
this.emit("configsUpdated", {
|
|
1534
|
+
keys,
|
|
1535
|
+
removedKeys: []
|
|
1536
|
+
});
|
|
1537
|
+
this.updateWatchers(keys);
|
|
1538
|
+
this.logger.debug("[ConfigDirectorClient] Serving the values kept on the device until live values arrive:", { keys });
|
|
1539
|
+
}
|
|
1540
|
+
keepValuesOnTheDevice(configSet) {
|
|
1541
|
+
const store = this.cachedValuesStore;
|
|
1542
|
+
if (!store) return;
|
|
1543
|
+
const context = this.submittedContext;
|
|
1544
|
+
const receivedAt = Date.now();
|
|
1545
|
+
this.clientSession.then(({ instanceId }) => store.replace({
|
|
1546
|
+
contextTag: contextTagFor(context, instanceId),
|
|
1547
|
+
environmentId: configSet.environmentId,
|
|
1548
|
+
projectId: configSet.projectId,
|
|
1549
|
+
configs: configSet.configs,
|
|
1550
|
+
receivedAt
|
|
1551
|
+
}));
|
|
1552
|
+
}
|
|
1553
|
+
servedReason(reason) {
|
|
1554
|
+
return this.servingCachedValues && reason === "found-match" ? "cached" : reason;
|
|
1310
1555
|
}
|
|
1311
1556
|
registerHandler(event, handler) {
|
|
1312
1557
|
if (!handler) return;
|
|
@@ -1400,6 +1645,8 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1400
1645
|
return this.currentContext;
|
|
1401
1646
|
}
|
|
1402
1647
|
async connectToTransport(context, caller) {
|
|
1648
|
+
this.submittedContext = context;
|
|
1649
|
+
if (caller === "initialization") this.startCachedValuesAdoption();
|
|
1403
1650
|
try {
|
|
1404
1651
|
this.ready = false;
|
|
1405
1652
|
this.readyPromise = new Promise((resolve) => {
|
|
@@ -1500,6 +1747,7 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1500
1747
|
return defaultValue;
|
|
1501
1748
|
}
|
|
1502
1749
|
const parseResult = parseConfigValue(configState, defaultValue);
|
|
1750
|
+
const reason = this.servedReason(parseResult.reason);
|
|
1503
1751
|
this.telemetryClient.evaluatedConfig({
|
|
1504
1752
|
contextId: this.currentContext?.id,
|
|
1505
1753
|
type: configState.type,
|
|
@@ -1509,14 +1757,14 @@ var DefaultConfigDirectorClient = class DefaultConfigDirectorClient {
|
|
|
1509
1757
|
evaluatedValue: parseResult.parsedValue,
|
|
1510
1758
|
evaluatedValueId: parseResult.parsedValueId,
|
|
1511
1759
|
usedDefault: parseResult.usedDefault,
|
|
1512
|
-
evaluationReason:
|
|
1760
|
+
evaluationReason: reason
|
|
1513
1761
|
});
|
|
1514
1762
|
this.dispatchEvaluationEvent({
|
|
1515
1763
|
key: configKey,
|
|
1516
1764
|
value: parseResult.parsedValue,
|
|
1517
1765
|
valueId: parseResult.parsedValueId ?? void 0,
|
|
1518
1766
|
isDefaultValue: parseResult.usedDefault,
|
|
1519
|
-
reason
|
|
1767
|
+
reason,
|
|
1520
1768
|
context: this.context
|
|
1521
1769
|
});
|
|
1522
1770
|
this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
|
|
@@ -1882,7 +2130,7 @@ const installTestClient = (testClient) => {
|
|
|
1882
2130
|
const createTestClient = (options) => {
|
|
1883
2131
|
return createTestClient$1({
|
|
1884
2132
|
sdkName: "nuxt-sdk",
|
|
1885
|
-
sdkVersion: "1.
|
|
2133
|
+
sdkVersion: "1.12.0"
|
|
1886
2134
|
}, options);
|
|
1887
2135
|
};
|
|
1888
2136
|
//#endregion
|
package/package.json
CHANGED