@coframe/sdk 1.1.23 → 1.1.24
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/README.md +157 -0
- package/dist/pkg/cf.cjs.js +1 -1
- package/dist/pkg/cf.esm.mjs +1 -1
- package/dist/pkg/feature-flags.cjs.js +623 -0
- package/dist/pkg/feature-flags.esm.mjs +609 -0
- package/dist/pkg/react.cjs.js +36 -1
- package/dist/pkg/react.esm.mjs +32 -2
- package/dist/pkg/server.cjs.js +75 -0
- package/dist/pkg/server.esm.mjs +70 -1
- package/dist/pkg/types/feature-flags.d.ts +75 -0
- package/dist/pkg/types/react.d.ts +22 -1
- package/dist/pkg/types/server.d.ts +51 -2
- package/package.json +7 -2
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
function getBrowserHostname() {
|
|
2
|
+
return window.location.hostname;
|
|
3
|
+
}
|
|
4
|
+
function getDocumentCookie() {
|
|
5
|
+
try {
|
|
6
|
+
return document.cookie;
|
|
7
|
+
}
|
|
8
|
+
catch (_error) {
|
|
9
|
+
return '';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function setDocumentCookie(cookie) {
|
|
13
|
+
document.cookie = cookie;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const USER_TOKEN_COOKIE_KEY = 'coframe.user.token';
|
|
17
|
+
const VISIT_COUNT_COOKIE_KEY = 'coframe.cvc';
|
|
18
|
+
const COOKIE_DAYS = 400;
|
|
19
|
+
function getGlobalScope() {
|
|
20
|
+
if (typeof globalThis !== 'undefined') {
|
|
21
|
+
return globalThis;
|
|
22
|
+
}
|
|
23
|
+
if (typeof window !== 'undefined') {
|
|
24
|
+
return window;
|
|
25
|
+
}
|
|
26
|
+
if (typeof self !== 'undefined') {
|
|
27
|
+
return self;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const generateUUID = () => {
|
|
31
|
+
var _a;
|
|
32
|
+
const cryptoObject = (_a = getGlobalScope()) === null || _a === void 0 ? void 0 : _a.crypto;
|
|
33
|
+
if (cryptoObject === null || cryptoObject === void 0 ? void 0 : cryptoObject.randomUUID)
|
|
34
|
+
return cryptoObject.randomUUID();
|
|
35
|
+
return `${1e7}${ -1e3}${ -4e3}${ -8e3}${ -1e11}`.replace(/[018]/g, (c) => {
|
|
36
|
+
var _a, _b;
|
|
37
|
+
const randomValue = (_b = (_a = cryptoObject === null || cryptoObject === void 0 ? void 0 : cryptoObject.getRandomValues) === null || _a === void 0 ? void 0 : _a.call(cryptoObject, new Uint8Array(1))[0]) !== null && _b !== void 0 ? _b : 0;
|
|
38
|
+
return (c ^
|
|
39
|
+
(randomValue & (15 >> (c / 4)))).toString(16);
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
const getCookie = (name) => {
|
|
43
|
+
const cookieStr = getDocumentCookie();
|
|
44
|
+
const regex = new RegExp(`(?:^| )${name}=([^;]*)`, 'g');
|
|
45
|
+
let match = null;
|
|
46
|
+
let lastValue = '';
|
|
47
|
+
while (true) {
|
|
48
|
+
match = regex.exec(cookieStr);
|
|
49
|
+
if (match === null)
|
|
50
|
+
break;
|
|
51
|
+
lastValue = match[1];
|
|
52
|
+
}
|
|
53
|
+
return lastValue;
|
|
54
|
+
};
|
|
55
|
+
let isCookiesEnabled = false;
|
|
56
|
+
const setCookiesEnabled = (allowed) => {
|
|
57
|
+
isCookiesEnabled = allowed;
|
|
58
|
+
};
|
|
59
|
+
const setCookie = (name, value, logger) => {
|
|
60
|
+
if (!isCookiesEnabled) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const expirationDate = new Date();
|
|
64
|
+
expirationDate.setTime(expirationDate.getTime() + 24 * 60 * 60 * 1000 * COOKIE_DAYS);
|
|
65
|
+
const expires = expirationDate.toUTCString();
|
|
66
|
+
const setCookieForDomain = (domain) => {
|
|
67
|
+
const cookie = domain
|
|
68
|
+
? `${name}=${value};path=/;domain=${domain};expires=${expires};secure`
|
|
69
|
+
: `${name}=${value};path=/;expires=${expires};secure`;
|
|
70
|
+
setDocumentCookie(cookie);
|
|
71
|
+
};
|
|
72
|
+
const hostname = getBrowserHostname();
|
|
73
|
+
if (hostname === 'localhost') {
|
|
74
|
+
setCookieForDomain();
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const domainParts = hostname.split('.');
|
|
78
|
+
for (let i = domainParts.length - 2; i >= 0; i--) {
|
|
79
|
+
let domain = `${domainParts.slice(i).join('.')}`;
|
|
80
|
+
if (i !== 0) {
|
|
81
|
+
domain = `.${domain}`;
|
|
82
|
+
}
|
|
83
|
+
setCookieForDomain(domain);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (getCookie(name) !== value) ;
|
|
87
|
+
};
|
|
88
|
+
const getOrGenerateUserToken = () => {
|
|
89
|
+
let uuid = getCookie(USER_TOKEN_COOKIE_KEY);
|
|
90
|
+
if (!uuid) {
|
|
91
|
+
uuid = generateUUID();
|
|
92
|
+
}
|
|
93
|
+
return uuid;
|
|
94
|
+
};
|
|
95
|
+
const setUserTokenCookie = (uuid, logger) => {
|
|
96
|
+
setCookie(USER_TOKEN_COOKIE_KEY, uuid);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const DEFAULT_TIMEOUT_MS = 3000;
|
|
100
|
+
const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
101
|
+
const CACHE_PREFIX = 'coframe.featureFlags.';
|
|
102
|
+
let config;
|
|
103
|
+
let projectId;
|
|
104
|
+
let userToken;
|
|
105
|
+
let userContext;
|
|
106
|
+
let response;
|
|
107
|
+
let responseContextKey;
|
|
108
|
+
let inFlight;
|
|
109
|
+
let inFlightContextKey;
|
|
110
|
+
let requestGeneration = 0;
|
|
111
|
+
const listeners = [];
|
|
112
|
+
function storageKey(projectId) {
|
|
113
|
+
return `${CACHE_PREFIX}${projectId}`;
|
|
114
|
+
}
|
|
115
|
+
function readCache(projectId, expectedFingerprint) {
|
|
116
|
+
if (!isCookiesEnabled || typeof window === 'undefined')
|
|
117
|
+
return undefined;
|
|
118
|
+
try {
|
|
119
|
+
const value = window.localStorage.getItem(storageKey(projectId));
|
|
120
|
+
if (!value)
|
|
121
|
+
return undefined;
|
|
122
|
+
const parsed = JSON.parse(value);
|
|
123
|
+
if (!Object.prototype.hasOwnProperty.call(parsed, 'fingerprint')) {
|
|
124
|
+
window.localStorage.removeItem(storageKey(projectId));
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
if (typeof parsed.loadedAt !== 'number' ||
|
|
128
|
+
parsed.fingerprint !== expectedFingerprint ||
|
|
129
|
+
!parsed.response ||
|
|
130
|
+
typeof parsed.response.flags !== 'object') {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
return parsed;
|
|
134
|
+
}
|
|
135
|
+
catch (_a) {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function getEvaluationContext() {
|
|
140
|
+
var _a, _b;
|
|
141
|
+
const visitCountCookie = getCookie(VISIT_COUNT_COOKIE_KEY);
|
|
142
|
+
const visitCount = /^[1-9]\d{0,14}$/.test(visitCountCookie)
|
|
143
|
+
? Number(visitCountCookie)
|
|
144
|
+
: undefined;
|
|
145
|
+
return {
|
|
146
|
+
currentUrl: window.location.href,
|
|
147
|
+
languages: Array.from((_a = navigator.languages) !== null && _a !== void 0 ? _a : [navigator.language]),
|
|
148
|
+
browserPlatform: navigator.platform ||
|
|
149
|
+
((_b = navigator.userAgentData) === null || _b === void 0 ? void 0 : _b.platform) ||
|
|
150
|
+
'',
|
|
151
|
+
...(visitCount !== undefined ? { userVisitCount: visitCount } : {}),
|
|
152
|
+
...(userContext ? { userContext } : {}),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function getContextKey(context) {
|
|
156
|
+
return JSON.stringify({
|
|
157
|
+
selector: (config === null || config === void 0 ? void 0 : config.selector)
|
|
158
|
+
? {
|
|
159
|
+
names: config.selector.names
|
|
160
|
+
? [...config.selector.names].sort()
|
|
161
|
+
: null,
|
|
162
|
+
groups: config.selector.groups
|
|
163
|
+
? [...config.selector.groups].sort()
|
|
164
|
+
: null,
|
|
165
|
+
}
|
|
166
|
+
: null,
|
|
167
|
+
context,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async function getCacheFingerprint(currentUserToken, contextKey) {
|
|
171
|
+
var _a;
|
|
172
|
+
if (!isCookiesEnabled || !((_a = globalThis.crypto) === null || _a === void 0 ? void 0 : _a.subtle))
|
|
173
|
+
return undefined;
|
|
174
|
+
try {
|
|
175
|
+
const bytes = new TextEncoder().encode(JSON.stringify([currentUserToken, contextKey]));
|
|
176
|
+
const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes);
|
|
177
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
178
|
+
}
|
|
179
|
+
catch (_b) {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function writeCache(projectId, value) {
|
|
184
|
+
if (!isCookiesEnabled || typeof window === 'undefined')
|
|
185
|
+
return;
|
|
186
|
+
try {
|
|
187
|
+
window.localStorage.setItem(storageKey(projectId), JSON.stringify(value));
|
|
188
|
+
}
|
|
189
|
+
catch (_a) {
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function notify() {
|
|
193
|
+
if (!response)
|
|
194
|
+
return;
|
|
195
|
+
const metadata = {
|
|
196
|
+
errorsWhileComputingFlags: response.errorsWhileComputingFlags,
|
|
197
|
+
snapshotVersion: response.snapshotVersion,
|
|
198
|
+
};
|
|
199
|
+
for (const listener of listeners.slice()) {
|
|
200
|
+
try {
|
|
201
|
+
listener(response.flags, metadata);
|
|
202
|
+
}
|
|
203
|
+
catch (_a) {
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function notifyCleared() {
|
|
208
|
+
for (const listener of listeners.slice()) {
|
|
209
|
+
try {
|
|
210
|
+
listener({}, { errorsWhileComputingFlags: false, snapshotVersion: 0 });
|
|
211
|
+
}
|
|
212
|
+
catch (_a) {
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function fetchFeatureFlags(context) {
|
|
217
|
+
var _a;
|
|
218
|
+
if (!config || !projectId)
|
|
219
|
+
throw new Error('Feature Flags are not configured');
|
|
220
|
+
const controller = new AbortController();
|
|
221
|
+
const timeout = window.setTimeout(() => controller.abort(), (_a = config.requestTimeoutMs) !== null && _a !== void 0 ? _a : DEFAULT_TIMEOUT_MS);
|
|
222
|
+
try {
|
|
223
|
+
const result = await fetch(`${"https://edge.cofra.me"}/api/v1/flags?v=1`, {
|
|
224
|
+
method: 'POST',
|
|
225
|
+
headers: { 'Content-Type': 'application/json' },
|
|
226
|
+
body: JSON.stringify({
|
|
227
|
+
projectId,
|
|
228
|
+
userToken,
|
|
229
|
+
...(config.selector ? { selector: config.selector } : {}),
|
|
230
|
+
...context,
|
|
231
|
+
}),
|
|
232
|
+
signal: controller.signal,
|
|
233
|
+
});
|
|
234
|
+
if (!result.ok)
|
|
235
|
+
throw new Error(`Feature Flags request failed (${result.status})`);
|
|
236
|
+
return (await result.json());
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
window.clearTimeout(timeout);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async function initializeFeatureFlags(nextConfig, nextProjectId, nextUserToken, nextUserContext) {
|
|
243
|
+
var _a;
|
|
244
|
+
if (!nextConfig)
|
|
245
|
+
return;
|
|
246
|
+
config = nextConfig;
|
|
247
|
+
projectId = nextProjectId;
|
|
248
|
+
userToken = nextUserToken;
|
|
249
|
+
userContext = nextUserContext;
|
|
250
|
+
requestGeneration += 1;
|
|
251
|
+
inFlight = undefined;
|
|
252
|
+
inFlightContextKey = undefined;
|
|
253
|
+
response = undefined;
|
|
254
|
+
responseContextKey = undefined;
|
|
255
|
+
const initializationGeneration = requestGeneration;
|
|
256
|
+
const initialContextKey = getContextKey(getEvaluationContext());
|
|
257
|
+
const loading = reloadFeatureFlags();
|
|
258
|
+
const initialFingerprint = await getCacheFingerprint(nextUserToken, initialContextKey);
|
|
259
|
+
if (initializationGeneration === requestGeneration && !response) {
|
|
260
|
+
const initialCache = initialFingerprint
|
|
261
|
+
? readCache(nextProjectId, initialFingerprint)
|
|
262
|
+
: undefined;
|
|
263
|
+
const ttl = (_a = nextConfig.cacheTtlMs) !== null && _a !== void 0 ? _a : DEFAULT_CACHE_TTL_MS;
|
|
264
|
+
if (initialCache && Date.now() - initialCache.loadedAt <= ttl) {
|
|
265
|
+
response = initialCache.response;
|
|
266
|
+
responseContextKey = initialContextKey;
|
|
267
|
+
notify();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
await loading;
|
|
271
|
+
}
|
|
272
|
+
function reloadFeatureFlags() {
|
|
273
|
+
if (!config)
|
|
274
|
+
return Promise.resolve();
|
|
275
|
+
const currentProjectId = projectId;
|
|
276
|
+
if (!currentProjectId)
|
|
277
|
+
return Promise.resolve();
|
|
278
|
+
const requestUserToken = userToken !== null && userToken !== void 0 ? userToken : '';
|
|
279
|
+
const requestContext = getEvaluationContext();
|
|
280
|
+
const requestContextKey = getContextKey(requestContext);
|
|
281
|
+
if (inFlight && inFlightContextKey === requestContextKey)
|
|
282
|
+
return inFlight;
|
|
283
|
+
if (response && responseContextKey !== requestContextKey) {
|
|
284
|
+
response = undefined;
|
|
285
|
+
responseContextKey = undefined;
|
|
286
|
+
notifyCleared();
|
|
287
|
+
}
|
|
288
|
+
if (inFlight)
|
|
289
|
+
requestGeneration += 1;
|
|
290
|
+
const generation = requestGeneration;
|
|
291
|
+
const fingerprintPromise = getCacheFingerprint(requestUserToken, requestContextKey);
|
|
292
|
+
let trackedRequest;
|
|
293
|
+
trackedRequest = fetchFeatureFlags(requestContext)
|
|
294
|
+
.then(async (nextResponse) => {
|
|
295
|
+
if (generation !== requestGeneration)
|
|
296
|
+
return;
|
|
297
|
+
response = nextResponse;
|
|
298
|
+
responseContextKey = requestContextKey;
|
|
299
|
+
notify();
|
|
300
|
+
const fingerprint = await fingerprintPromise;
|
|
301
|
+
if (generation !== requestGeneration || !fingerprint)
|
|
302
|
+
return;
|
|
303
|
+
const cache = {
|
|
304
|
+
fingerprint,
|
|
305
|
+
loadedAt: Date.now(),
|
|
306
|
+
response: nextResponse,
|
|
307
|
+
};
|
|
308
|
+
writeCache(currentProjectId, cache);
|
|
309
|
+
})
|
|
310
|
+
.catch(async () => {
|
|
311
|
+
if (generation !== requestGeneration)
|
|
312
|
+
return;
|
|
313
|
+
const fingerprint = await fingerprintPromise;
|
|
314
|
+
if (generation !== requestGeneration || !fingerprint)
|
|
315
|
+
return;
|
|
316
|
+
const fallbackCache = readCache(currentProjectId, fingerprint);
|
|
317
|
+
if (!response && fallbackCache) {
|
|
318
|
+
response = fallbackCache.response;
|
|
319
|
+
responseContextKey = requestContextKey;
|
|
320
|
+
notify();
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
.finally(() => {
|
|
324
|
+
if (inFlight === trackedRequest) {
|
|
325
|
+
inFlight = undefined;
|
|
326
|
+
inFlightContextKey = undefined;
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
inFlight = trackedRequest;
|
|
330
|
+
inFlightContextKey = requestContextKey;
|
|
331
|
+
return trackedRequest;
|
|
332
|
+
}
|
|
333
|
+
function updateFeatureFlagUserToken(nextUserToken) {
|
|
334
|
+
if (nextUserToken === userToken)
|
|
335
|
+
return;
|
|
336
|
+
userToken = nextUserToken;
|
|
337
|
+
requestGeneration += 1;
|
|
338
|
+
inFlight = undefined;
|
|
339
|
+
inFlightContextKey = undefined;
|
|
340
|
+
response = undefined;
|
|
341
|
+
responseContextKey = undefined;
|
|
342
|
+
notifyCleared();
|
|
343
|
+
void reloadFeatureFlags();
|
|
344
|
+
}
|
|
345
|
+
function onFeatureFlags(callback) {
|
|
346
|
+
listeners.push(callback);
|
|
347
|
+
if (response) {
|
|
348
|
+
try {
|
|
349
|
+
callback(response.flags, {
|
|
350
|
+
errorsWhileComputingFlags: response.errorsWhileComputingFlags,
|
|
351
|
+
snapshotVersion: response.snapshotVersion,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
catch (_a) {
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return () => {
|
|
358
|
+
const index = listeners.indexOf(callback);
|
|
359
|
+
if (index !== -1)
|
|
360
|
+
listeners.splice(index, 1);
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function getFeatureFlagResult(key) {
|
|
364
|
+
return response === null || response === void 0 ? void 0 : response.flags[key];
|
|
365
|
+
}
|
|
366
|
+
function getFeatureFlag(key) {
|
|
367
|
+
var _a;
|
|
368
|
+
const result = getFeatureFlagResult(key);
|
|
369
|
+
if (!result)
|
|
370
|
+
return undefined;
|
|
371
|
+
return (_a = result.variant) !== null && _a !== void 0 ? _a : result.enabled;
|
|
372
|
+
}
|
|
373
|
+
function isFeatureEnabled(key, options) {
|
|
374
|
+
var _a, _b;
|
|
375
|
+
return (_b = (_a = getFeatureFlagResult(key)) === null || _a === void 0 ? void 0 : _a.enabled) !== null && _b !== void 0 ? _b : options === null || options === void 0 ? void 0 : options.defaultValue;
|
|
376
|
+
}
|
|
377
|
+
function getFeatureFlagPayload(key) {
|
|
378
|
+
var _a;
|
|
379
|
+
const result = getFeatureFlagResult(key);
|
|
380
|
+
if (!result)
|
|
381
|
+
return undefined;
|
|
382
|
+
return 'payload' in result ? ((_a = result.payload) !== null && _a !== void 0 ? _a : null) : null;
|
|
383
|
+
}
|
|
384
|
+
const getRemoteConfig = getFeatureFlagPayload;
|
|
385
|
+
|
|
386
|
+
function observeFeatureFlagUrlChanges(onUrlChange) {
|
|
387
|
+
let lastUrl = window.location.href;
|
|
388
|
+
const tryReload = () => {
|
|
389
|
+
const currentUrl = window.location.href;
|
|
390
|
+
if (currentUrl === lastUrl)
|
|
391
|
+
return;
|
|
392
|
+
const previousUrl = lastUrl;
|
|
393
|
+
lastUrl = currentUrl;
|
|
394
|
+
onUrlChange(currentUrl, previousUrl);
|
|
395
|
+
};
|
|
396
|
+
window.addEventListener('popstate', tryReload);
|
|
397
|
+
window.addEventListener('hashchange', tryReload);
|
|
398
|
+
const patchHistoryMethod = (method) => {
|
|
399
|
+
const original = window.history[method];
|
|
400
|
+
window.history[method] = function (...args) {
|
|
401
|
+
const result = original.apply(this, args);
|
|
402
|
+
setTimeout(tryReload, 0);
|
|
403
|
+
return result;
|
|
404
|
+
};
|
|
405
|
+
};
|
|
406
|
+
patchHistoryMethod('pushState');
|
|
407
|
+
patchHistoryMethod('replaceState');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const QueueName = {
|
|
411
|
+
CFQ: 'CFQ'};
|
|
412
|
+
|
|
413
|
+
function ensureQueueExists(queueName = QueueName.CFQ) {
|
|
414
|
+
var _a;
|
|
415
|
+
const descriptor = Object.getOwnPropertyDescriptor(window, queueName);
|
|
416
|
+
if (descriptor === null || descriptor === void 0 ? void 0 : descriptor.get) {
|
|
417
|
+
return window[queueName];
|
|
418
|
+
}
|
|
419
|
+
const queue = (_a = window[queueName]) !== null && _a !== void 0 ? _a : [];
|
|
420
|
+
Object.defineProperty(window, queueName, {
|
|
421
|
+
get() {
|
|
422
|
+
return queue;
|
|
423
|
+
},
|
|
424
|
+
set(val) {
|
|
425
|
+
if (val !== queue) {
|
|
426
|
+
console.warn(`[Coframe] Attempt to reassign ${queueName} queue — assignment ignored. Use .push() instead.`);
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
configurable: false,
|
|
430
|
+
});
|
|
431
|
+
return queue;
|
|
432
|
+
}
|
|
433
|
+
function __ensureQueuePushListenerHooked(queueName = QueueName.CFQ) {
|
|
434
|
+
const queue = ensureQueueExists(queueName);
|
|
435
|
+
if (queue.__cfqHooked) {
|
|
436
|
+
return queue;
|
|
437
|
+
}
|
|
438
|
+
queue.__cfqHooked = true;
|
|
439
|
+
queue.__cfqListeners = [];
|
|
440
|
+
const originalPush = queue.push.bind(queue);
|
|
441
|
+
queue.push = (...items) => {
|
|
442
|
+
const len = originalPush(...items);
|
|
443
|
+
const listeners = queue.__cfqListeners;
|
|
444
|
+
for (let i = 0; i < items.length; i++) {
|
|
445
|
+
const cmd = items[i];
|
|
446
|
+
const snapshot = listeners.slice();
|
|
447
|
+
for (let j = 0; j < snapshot.length; j++) {
|
|
448
|
+
const l = snapshot[j];
|
|
449
|
+
if (l.predicate(cmd))
|
|
450
|
+
l.callback(cmd);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return len;
|
|
454
|
+
};
|
|
455
|
+
return queue;
|
|
456
|
+
}
|
|
457
|
+
function __addListener(predicate, callback, queueName = QueueName.CFQ) {
|
|
458
|
+
const queue = __ensureQueuePushListenerHooked(queueName);
|
|
459
|
+
const entry = { predicate, callback };
|
|
460
|
+
queue.__cfqListeners.push(entry);
|
|
461
|
+
let active = true;
|
|
462
|
+
return () => {
|
|
463
|
+
if (!active)
|
|
464
|
+
return;
|
|
465
|
+
active = false;
|
|
466
|
+
const arr = queue.__cfqListeners;
|
|
467
|
+
const idx = arr.indexOf(entry);
|
|
468
|
+
if (idx !== -1)
|
|
469
|
+
arr.splice(idx, 1);
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
function findLastCmd(predicate, queueName = QueueName.CFQ) {
|
|
473
|
+
const queue = ensureQueueExists(queueName);
|
|
474
|
+
for (let i = queue.length - 1; i >= 0; i--) {
|
|
475
|
+
if (predicate(queue[i])) {
|
|
476
|
+
return queue[i];
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return undefined;
|
|
480
|
+
}
|
|
481
|
+
function subscribeToCmd(predicate, callback, queueName = QueueName.CFQ) {
|
|
482
|
+
return __addListener(predicate, callback, queueName);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function push(cmd) {
|
|
486
|
+
if (typeof window === 'undefined')
|
|
487
|
+
return;
|
|
488
|
+
window.CFQ = window.CFQ || [];
|
|
489
|
+
window.CFQ.push(cmd);
|
|
490
|
+
}
|
|
491
|
+
function setConsent$1(enabled) {
|
|
492
|
+
push({ cookiesEnabled: enabled });
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
let currentUserToken;
|
|
496
|
+
let spaNavigationInstalled = false;
|
|
497
|
+
let identitySubscriptionInstalled = false;
|
|
498
|
+
let consentSubscriptionInstalled = false;
|
|
499
|
+
function requireIdentity(userToken) {
|
|
500
|
+
const normalized = userToken.trim();
|
|
501
|
+
if (!normalized)
|
|
502
|
+
throw new Error('Feature Flag userToken must not be empty');
|
|
503
|
+
return normalized;
|
|
504
|
+
}
|
|
505
|
+
function persistIdentity() {
|
|
506
|
+
if (isCookiesEnabled && currentUserToken) {
|
|
507
|
+
setUserTokenCookie(currentUserToken);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
function applyConsent(enabled) {
|
|
511
|
+
setCookiesEnabled(enabled);
|
|
512
|
+
if (enabled)
|
|
513
|
+
persistIdentity();
|
|
514
|
+
}
|
|
515
|
+
function installSpaNavigationReload() {
|
|
516
|
+
if (spaNavigationInstalled || typeof window === 'undefined')
|
|
517
|
+
return;
|
|
518
|
+
spaNavigationInstalled = true;
|
|
519
|
+
observeFeatureFlagUrlChanges(() => {
|
|
520
|
+
void reloadFeatureFlags();
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
function getQueuedUserToken() {
|
|
524
|
+
const command = findLastCmd((cmd) => typeof cmd.setUserToken === 'string' && !!cmd.setUserToken);
|
|
525
|
+
return command === null || command === void 0 ? void 0 : command.setUserToken;
|
|
526
|
+
}
|
|
527
|
+
function getQueuedConsent() {
|
|
528
|
+
const command = findLastCmd((cmd) => typeof cmd.cookiesEnabled === 'boolean');
|
|
529
|
+
return command === null || command === void 0 ? void 0 : command.cookiesEnabled;
|
|
530
|
+
}
|
|
531
|
+
function installIdentitySync() {
|
|
532
|
+
if (identitySubscriptionInstalled || typeof window === 'undefined')
|
|
533
|
+
return;
|
|
534
|
+
identitySubscriptionInstalled = true;
|
|
535
|
+
subscribeToCmd((cmd) => typeof cmd.setUserToken === 'string' && !!cmd.setUserToken, (cmd) => {
|
|
536
|
+
if (!cmd.setUserToken)
|
|
537
|
+
return;
|
|
538
|
+
const nextUserToken = requireIdentity(cmd.setUserToken);
|
|
539
|
+
if (nextUserToken === currentUserToken)
|
|
540
|
+
return;
|
|
541
|
+
currentUserToken = nextUserToken;
|
|
542
|
+
persistIdentity();
|
|
543
|
+
updateFeatureFlagUserToken(nextUserToken);
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
function installConsentSync() {
|
|
547
|
+
if (consentSubscriptionInstalled || typeof window === 'undefined')
|
|
548
|
+
return;
|
|
549
|
+
consentSubscriptionInstalled = true;
|
|
550
|
+
subscribeToCmd((cmd) => typeof cmd.cookiesEnabled === 'boolean', (cmd) => applyConsent(!!cmd.cookiesEnabled));
|
|
551
|
+
}
|
|
552
|
+
function initFeatureFlags(options) {
|
|
553
|
+
const queuedConsent = getQueuedConsent();
|
|
554
|
+
installConsentSync();
|
|
555
|
+
const queuedUserToken = options.userToken ? undefined : getQueuedUserToken();
|
|
556
|
+
currentUserToken = options.userToken
|
|
557
|
+
? requireIdentity(options.userToken)
|
|
558
|
+
: queuedUserToken
|
|
559
|
+
? requireIdentity(queuedUserToken)
|
|
560
|
+
: getOrGenerateUserToken();
|
|
561
|
+
installIdentitySync();
|
|
562
|
+
if (options.consent !== undefined) {
|
|
563
|
+
setConsent$1(options.consent);
|
|
564
|
+
}
|
|
565
|
+
else if (queuedConsent !== undefined) {
|
|
566
|
+
applyConsent(queuedConsent);
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
persistIdentity();
|
|
570
|
+
}
|
|
571
|
+
if (options.reloadOnSpaNavigation !== false) {
|
|
572
|
+
installSpaNavigationReload();
|
|
573
|
+
}
|
|
574
|
+
const { selector, requestTimeoutMs, cacheTtlMs } = options;
|
|
575
|
+
return initializeFeatureFlags({ selector, requestTimeoutMs, cacheTtlMs }, options.projectId, currentUserToken, options.userContext);
|
|
576
|
+
}
|
|
577
|
+
function setUserToken(userToken) {
|
|
578
|
+
currentUserToken = requireIdentity(userToken);
|
|
579
|
+
persistIdentity();
|
|
580
|
+
updateFeatureFlagUserToken(currentUserToken);
|
|
581
|
+
}
|
|
582
|
+
function reset() {
|
|
583
|
+
currentUserToken = generateUUID();
|
|
584
|
+
persistIdentity();
|
|
585
|
+
updateFeatureFlagUserToken(currentUserToken);
|
|
586
|
+
return currentUserToken;
|
|
587
|
+
}
|
|
588
|
+
function setConsent(enabled) {
|
|
589
|
+
setConsent$1(enabled);
|
|
590
|
+
}
|
|
591
|
+
function getUserToken() {
|
|
592
|
+
return currentUserToken;
|
|
593
|
+
}
|
|
594
|
+
const featureFlags = {
|
|
595
|
+
init: initFeatureFlags,
|
|
596
|
+
setUserToken,
|
|
597
|
+
reset,
|
|
598
|
+
setConsent,
|
|
599
|
+
getUserToken,
|
|
600
|
+
getFeatureFlag,
|
|
601
|
+
getFeatureFlagPayload,
|
|
602
|
+
getFeatureFlagResult,
|
|
603
|
+
getRemoteConfig,
|
|
604
|
+
isFeatureEnabled,
|
|
605
|
+
onFeatureFlags,
|
|
606
|
+
reloadFeatureFlags,
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
export { featureFlags, getFeatureFlag, getFeatureFlagPayload, getFeatureFlagResult, getRemoteConfig, getUserToken, initFeatureFlags, isFeatureEnabled, onFeatureFlags, reloadFeatureFlags, reset, setConsent, setUserToken };
|
package/dist/pkg/react.cjs.js
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var sdk = require('@coframe/sdk');
|
|
5
|
+
var featureFlags = require('@coframe/sdk/feature-flags');
|
|
5
6
|
var React = require('react');
|
|
6
7
|
|
|
7
|
-
const CoframeContext = React.createContext({
|
|
8
|
+
const CoframeContext = React.createContext({
|
|
9
|
+
treatments: null,
|
|
10
|
+
});
|
|
8
11
|
function CoframeProvider({ projectConfig, projectData, onTreatmentApplied: onTreatmentAppliedProp, onTreatmentAppliedBulk: onTreatmentAppliedBulkProp, onTreatmentAppliedBulkV2, onTreatmentAppliedBulkV2WithoutConsent, onLog: onLogProp, children, }) {
|
|
9
12
|
const [treatments, setTreatments] = React.useState(null);
|
|
10
13
|
React.useEffect(() => {
|
|
@@ -59,6 +62,33 @@ function useCoframeVariant(experimentId) {
|
|
|
59
62
|
var _a;
|
|
60
63
|
return (_a = useCoframeTreatment(experimentId)) === null || _a === void 0 ? void 0 : _a.variantId;
|
|
61
64
|
}
|
|
65
|
+
function useFeatureFlagResult(key) {
|
|
66
|
+
const [result, setResult] = React.useState(() => featureFlags.getFeatureFlagResult(key));
|
|
67
|
+
React.useEffect(() => {
|
|
68
|
+
setResult(featureFlags.getFeatureFlagResult(key));
|
|
69
|
+
return featureFlags.onFeatureFlags(() => setResult(featureFlags.getFeatureFlagResult(key)));
|
|
70
|
+
}, [key]);
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
function useFeatureFlagVariantKey(key) {
|
|
74
|
+
var _a;
|
|
75
|
+
const result = useFeatureFlagResult(key);
|
|
76
|
+
if (!result)
|
|
77
|
+
return undefined;
|
|
78
|
+
return (_a = result.variant) !== null && _a !== void 0 ? _a : result.enabled;
|
|
79
|
+
}
|
|
80
|
+
const useFeatureFlag = useFeatureFlagVariantKey;
|
|
81
|
+
function useFeatureFlagEnabled(key) {
|
|
82
|
+
var _a;
|
|
83
|
+
return (_a = useFeatureFlagResult(key)) === null || _a === void 0 ? void 0 : _a.enabled;
|
|
84
|
+
}
|
|
85
|
+
function useFeatureFlagPayload(key) {
|
|
86
|
+
var _a;
|
|
87
|
+
const result = useFeatureFlagResult(key);
|
|
88
|
+
if (!result)
|
|
89
|
+
return undefined;
|
|
90
|
+
return 'payload' in result ? ((_a = result.payload) !== null && _a !== void 0 ? _a : null) : null;
|
|
91
|
+
}
|
|
62
92
|
|
|
63
93
|
Object.defineProperty(exports, "init", {
|
|
64
94
|
enumerable: true,
|
|
@@ -96,3 +126,8 @@ exports.CoframeProvider = CoframeProvider;
|
|
|
96
126
|
exports.useCoframeTreatment = useCoframeTreatment;
|
|
97
127
|
exports.useCoframeTreatments = useCoframeTreatments;
|
|
98
128
|
exports.useCoframeVariant = useCoframeVariant;
|
|
129
|
+
exports.useFeatureFlag = useFeatureFlag;
|
|
130
|
+
exports.useFeatureFlagEnabled = useFeatureFlagEnabled;
|
|
131
|
+
exports.useFeatureFlagPayload = useFeatureFlagPayload;
|
|
132
|
+
exports.useFeatureFlagResult = useFeatureFlagResult;
|
|
133
|
+
exports.useFeatureFlagVariantKey = useFeatureFlagVariantKey;
|
package/dist/pkg/react.esm.mjs
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { init, onTreatmentApplied, onLog } from '@coframe/sdk';
|
|
3
3
|
export { init, onLog, onTreatmentApplied, setConsent, setFeatureFlag, setPageHydrated, setUserToken, trackConversion } from '@coframe/sdk';
|
|
4
|
+
import { getFeatureFlagResult, onFeatureFlags } from '@coframe/sdk/feature-flags';
|
|
4
5
|
import React, { createContext, useState, useEffect, useContext } from 'react';
|
|
5
6
|
|
|
6
|
-
const CoframeContext = createContext({
|
|
7
|
+
const CoframeContext = createContext({
|
|
8
|
+
treatments: null,
|
|
9
|
+
});
|
|
7
10
|
function CoframeProvider({ projectConfig, projectData, onTreatmentApplied: onTreatmentAppliedProp, onTreatmentAppliedBulk: onTreatmentAppliedBulkProp, onTreatmentAppliedBulkV2, onTreatmentAppliedBulkV2WithoutConsent, onLog: onLogProp, children, }) {
|
|
8
11
|
const [treatments, setTreatments] = useState(null);
|
|
9
12
|
useEffect(() => {
|
|
@@ -58,5 +61,32 @@ function useCoframeVariant(experimentId) {
|
|
|
58
61
|
var _a;
|
|
59
62
|
return (_a = useCoframeTreatment(experimentId)) === null || _a === void 0 ? void 0 : _a.variantId;
|
|
60
63
|
}
|
|
64
|
+
function useFeatureFlagResult(key) {
|
|
65
|
+
const [result, setResult] = useState(() => getFeatureFlagResult(key));
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
setResult(getFeatureFlagResult(key));
|
|
68
|
+
return onFeatureFlags(() => setResult(getFeatureFlagResult(key)));
|
|
69
|
+
}, [key]);
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
function useFeatureFlagVariantKey(key) {
|
|
73
|
+
var _a;
|
|
74
|
+
const result = useFeatureFlagResult(key);
|
|
75
|
+
if (!result)
|
|
76
|
+
return undefined;
|
|
77
|
+
return (_a = result.variant) !== null && _a !== void 0 ? _a : result.enabled;
|
|
78
|
+
}
|
|
79
|
+
const useFeatureFlag = useFeatureFlagVariantKey;
|
|
80
|
+
function useFeatureFlagEnabled(key) {
|
|
81
|
+
var _a;
|
|
82
|
+
return (_a = useFeatureFlagResult(key)) === null || _a === void 0 ? void 0 : _a.enabled;
|
|
83
|
+
}
|
|
84
|
+
function useFeatureFlagPayload(key) {
|
|
85
|
+
var _a;
|
|
86
|
+
const result = useFeatureFlagResult(key);
|
|
87
|
+
if (!result)
|
|
88
|
+
return undefined;
|
|
89
|
+
return 'payload' in result ? ((_a = result.payload) !== null && _a !== void 0 ? _a : null) : null;
|
|
90
|
+
}
|
|
61
91
|
|
|
62
|
-
export { CoframeProvider, useCoframeTreatment, useCoframeTreatments, useCoframeVariant };
|
|
92
|
+
export { CoframeProvider, useCoframeTreatment, useCoframeTreatments, useCoframeVariant, useFeatureFlag, useFeatureFlagEnabled, useFeatureFlagPayload, useFeatureFlagResult, useFeatureFlagVariantKey };
|