@databuddy/sdk 2.3.1 → 2.3.2

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.
@@ -1,471 +0,0 @@
1
- class BrowserFlagStorage {
2
- ttl = 24 * 60 * 60 * 1e3;
3
- // 24 hours in milliseconds
4
- get(key) {
5
- return this.getFromLocalStorage(key);
6
- }
7
- set(key, value) {
8
- this.setToLocalStorage(key, value);
9
- }
10
- getAll() {
11
- const result = {};
12
- const now = Date.now();
13
- const keys = Object.keys(localStorage).filter(
14
- (key) => key.startsWith("db-flag-")
15
- );
16
- for (const key of keys) {
17
- const flagKey = key.replace("db-flag-", "");
18
- try {
19
- const item = localStorage.getItem(key);
20
- if (item) {
21
- const parsed = JSON.parse(item);
22
- if (parsed.expiresAt && now > parsed.expiresAt) {
23
- localStorage.removeItem(key);
24
- } else {
25
- result[flagKey] = parsed.value || parsed;
26
- }
27
- }
28
- } catch {
29
- }
30
- }
31
- return result;
32
- }
33
- clear() {
34
- const keys = Object.keys(localStorage).filter(
35
- (key) => key.startsWith("db-flag-")
36
- );
37
- for (const key of keys) {
38
- localStorage.removeItem(key);
39
- }
40
- }
41
- getFromLocalStorage(key) {
42
- try {
43
- const item = localStorage.getItem(`db-flag-${key}`);
44
- if (!item) {
45
- return null;
46
- }
47
- const parsed = JSON.parse(item);
48
- if (parsed.expiresAt) {
49
- if (this.isExpired(parsed.expiresAt)) {
50
- localStorage.removeItem(`db-flag-${key}`);
51
- return null;
52
- }
53
- return parsed.value;
54
- }
55
- return parsed;
56
- } catch {
57
- return null;
58
- }
59
- }
60
- setToLocalStorage(key, value) {
61
- try {
62
- const item = {
63
- value,
64
- timestamp: Date.now(),
65
- expiresAt: Date.now() + this.ttl
66
- };
67
- localStorage.setItem(`db-flag-${key}`, JSON.stringify(item));
68
- } catch {
69
- }
70
- }
71
- isExpired(expiresAt) {
72
- if (!expiresAt) {
73
- return false;
74
- }
75
- return Date.now() > expiresAt;
76
- }
77
- delete(key) {
78
- localStorage.removeItem(`db-flag-${key}`);
79
- }
80
- deleteMultiple(keys) {
81
- for (const key of keys) {
82
- localStorage.removeItem(`db-flag-${key}`);
83
- }
84
- }
85
- setAll(flags) {
86
- const currentFlags = this.getAll();
87
- const currentKeys = Object.keys(currentFlags);
88
- const newKeys = Object.keys(flags);
89
- const removedKeys = currentKeys.filter((key) => !newKeys.includes(key));
90
- if (removedKeys.length > 0) {
91
- this.deleteMultiple(removedKeys);
92
- }
93
- for (const [key, value] of Object.entries(flags)) {
94
- this.set(key, value);
95
- }
96
- }
97
- cleanupExpired() {
98
- const now = Date.now();
99
- const keys = Object.keys(localStorage).filter(
100
- (key) => key.startsWith("db-flag-")
101
- );
102
- for (const key of keys) {
103
- try {
104
- const item = localStorage.getItem(key);
105
- if (item) {
106
- const parsed = JSON.parse(item);
107
- if (parsed.expiresAt && now > parsed.expiresAt) {
108
- localStorage.removeItem(key);
109
- }
110
- }
111
- } catch {
112
- localStorage.removeItem(key);
113
- }
114
- }
115
- }
116
- }
117
-
118
- class Logger {
119
- debugEnabled = false;
120
- /**
121
- * Enable or disable debug logging
122
- */
123
- setDebug(enabled) {
124
- this.debugEnabled = enabled;
125
- }
126
- /**
127
- * Log debug messages (only when debug is enabled)
128
- */
129
- debug(...args) {
130
- if (this.debugEnabled) {
131
- console.log("[Databuddy]", ...args);
132
- }
133
- }
134
- /**
135
- * Log info messages (always enabled)
136
- */
137
- info(...args) {
138
- console.info("[Databuddy]", ...args);
139
- }
140
- /**
141
- * Log warning messages (always enabled)
142
- */
143
- warn(...args) {
144
- console.warn("[Databuddy]", ...args);
145
- }
146
- /**
147
- * Log error messages (always enabled)
148
- */
149
- error(...args) {
150
- console.error("[Databuddy]", ...args);
151
- }
152
- /**
153
- * Log with table format (only when debug is enabled)
154
- */
155
- table(data) {
156
- if (this.debugEnabled) {
157
- console.table(data);
158
- }
159
- }
160
- /**
161
- * Time a function execution (only when debug is enabled)
162
- */
163
- time(label) {
164
- if (this.debugEnabled) {
165
- console.time(`[Databuddy] ${label}`);
166
- }
167
- }
168
- /**
169
- * End timing a function execution (only when debug is enabled)
170
- */
171
- timeEnd(label) {
172
- if (this.debugEnabled) {
173
- console.timeEnd(`[Databuddy] ${label}`);
174
- }
175
- }
176
- /**
177
- * Log JSON data (only when debug is enabled)
178
- */
179
- json(data) {
180
- if (this.debugEnabled) {
181
- console.log("[Databuddy]", JSON.stringify(data, null, 2));
182
- }
183
- }
184
- }
185
- const logger = new Logger();
186
-
187
- class CoreFlagsManager {
188
- config;
189
- storage;
190
- onFlagsUpdate;
191
- onConfigUpdate;
192
- memoryFlags = {};
193
- pendingFlags = /* @__PURE__ */ new Set();
194
- constructor(options) {
195
- this.config = this.withDefaults(options.config);
196
- this.storage = options.storage;
197
- this.onFlagsUpdate = options.onFlagsUpdate;
198
- this.onConfigUpdate = options.onConfigUpdate;
199
- logger.setDebug(this.config.debug ?? false);
200
- logger.debug("CoreFlagsManager initialized with config:", {
201
- clientId: this.config.clientId,
202
- debug: this.config.debug,
203
- isPending: this.config.isPending,
204
- hasUser: !!this.config.user
205
- });
206
- this.initialize();
207
- }
208
- withDefaults(config) {
209
- return {
210
- clientId: config.clientId,
211
- apiUrl: config.apiUrl ?? "https://api.databuddy.cc",
212
- user: config.user,
213
- disabled: config.disabled ?? false,
214
- debug: config.debug ?? false,
215
- skipStorage: config.skipStorage ?? false,
216
- isPending: config.isPending,
217
- autoFetch: config.autoFetch !== false
218
- };
219
- }
220
- async initialize() {
221
- if (!this.config.skipStorage && this.storage) {
222
- this.loadCachedFlags();
223
- this.storage.cleanupExpired();
224
- }
225
- if (this.config.autoFetch && !this.config.isPending) {
226
- await this.fetchAllFlags();
227
- }
228
- }
229
- loadCachedFlags() {
230
- if (!this.storage || this.config.skipStorage) {
231
- return;
232
- }
233
- try {
234
- const cachedFlags = this.storage.getAll();
235
- if (Object.keys(cachedFlags).length > 0) {
236
- this.memoryFlags = cachedFlags;
237
- this.notifyFlagsUpdate();
238
- logger.debug("Loaded cached flags:", Object.keys(cachedFlags));
239
- }
240
- } catch (err) {
241
- logger.warn("Error loading cached flags:", err);
242
- }
243
- }
244
- async fetchAllFlags() {
245
- if (this.config.isPending) {
246
- logger.debug("Session pending, skipping bulk fetch");
247
- return;
248
- }
249
- const params = new URLSearchParams();
250
- params.set("clientId", this.config.clientId);
251
- if (this.config.user?.userId) {
252
- params.set("userId", this.config.user.userId);
253
- }
254
- if (this.config.user?.email) {
255
- params.set("email", this.config.user.email);
256
- }
257
- if (this.config.user?.properties) {
258
- params.set("properties", JSON.stringify(this.config.user.properties));
259
- }
260
- const url = `${this.config.apiUrl}/public/v1/flags/bulk?${params.toString()}`;
261
- try {
262
- const response = await fetch(url);
263
- if (!response.ok) {
264
- throw new Error(`HTTP ${response.status}`);
265
- }
266
- const result = await response.json();
267
- logger.debug("Bulk fetch response:", result);
268
- if (result.flags) {
269
- this.memoryFlags = result.flags;
270
- this.notifyFlagsUpdate();
271
- if (!this.config.skipStorage && this.storage) {
272
- try {
273
- this.storage.setAll(result.flags);
274
- logger.debug("Bulk flags synced to cache");
275
- } catch (err) {
276
- logger.warn("Bulk storage error:", err);
277
- }
278
- }
279
- }
280
- } catch (err) {
281
- logger.error("Bulk fetch error:", err);
282
- }
283
- }
284
- async getFlag(key) {
285
- logger.debug(`Getting: ${key}`);
286
- if (this.config.isPending) {
287
- logger.debug(`Session pending for: ${key}`);
288
- return {
289
- enabled: false,
290
- value: false,
291
- payload: null,
292
- reason: "SESSION_PENDING"
293
- };
294
- }
295
- if (this.memoryFlags[key]) {
296
- logger.debug(`Memory: ${key}`);
297
- return this.memoryFlags[key];
298
- }
299
- if (this.pendingFlags.has(key)) {
300
- logger.debug(`Pending: ${key}`);
301
- return {
302
- enabled: false,
303
- value: false,
304
- payload: null,
305
- reason: "FETCHING"
306
- };
307
- }
308
- if (!this.config.skipStorage && this.storage) {
309
- try {
310
- const cached = await this.storage.get(key);
311
- if (cached) {
312
- logger.debug(`Cache: ${key}`);
313
- this.memoryFlags[key] = cached;
314
- this.notifyFlagsUpdate();
315
- return cached;
316
- }
317
- } catch (err) {
318
- logger.warn(`Storage error: ${key}`, err);
319
- }
320
- }
321
- return this.fetchFlag(key);
322
- }
323
- async fetchFlag(key) {
324
- this.pendingFlags.add(key);
325
- const params = new URLSearchParams();
326
- params.set("key", key);
327
- params.set("clientId", this.config.clientId);
328
- if (this.config.user?.userId) {
329
- params.set("userId", this.config.user.userId);
330
- }
331
- if (this.config.user?.email) {
332
- params.set("email", this.config.user.email);
333
- }
334
- if (this.config.user?.properties) {
335
- params.set("properties", JSON.stringify(this.config.user.properties));
336
- }
337
- const url = `${this.config.apiUrl}/public/v1/flags/evaluate?${params.toString()}`;
338
- logger.debug(`Fetching: ${key}`);
339
- try {
340
- const response = await fetch(url);
341
- if (!response.ok) {
342
- throw new Error(`HTTP ${response.status}`);
343
- }
344
- const result = await response.json();
345
- logger.debug(`Response for ${key}:`, result);
346
- this.memoryFlags[key] = result;
347
- this.notifyFlagsUpdate();
348
- if (!this.config.skipStorage && this.storage) {
349
- try {
350
- this.storage.set(key, result);
351
- logger.debug(`Cached: ${key}`);
352
- } catch (err) {
353
- logger.warn(`Cache error: ${key}`, err);
354
- }
355
- }
356
- return result;
357
- } catch (err) {
358
- logger.error(`Fetch error: ${key}`, err);
359
- const fallback = {
360
- enabled: false,
361
- value: false,
362
- payload: null,
363
- reason: "ERROR"
364
- };
365
- this.memoryFlags[key] = fallback;
366
- this.notifyFlagsUpdate();
367
- return fallback;
368
- } finally {
369
- this.pendingFlags.delete(key);
370
- }
371
- }
372
- isEnabled(key) {
373
- if (this.memoryFlags[key]) {
374
- return {
375
- enabled: this.memoryFlags[key].enabled,
376
- isLoading: false,
377
- isReady: true
378
- };
379
- }
380
- if (this.pendingFlags.has(key)) {
381
- return {
382
- enabled: false,
383
- isLoading: true,
384
- isReady: false
385
- };
386
- }
387
- this.getFlag(key);
388
- return {
389
- enabled: false,
390
- isLoading: true,
391
- isReady: false
392
- };
393
- }
394
- refresh(forceClear = false) {
395
- logger.debug("Refreshing", { forceClear });
396
- if (forceClear) {
397
- this.memoryFlags = {};
398
- this.notifyFlagsUpdate();
399
- if (!this.config.skipStorage && this.storage) {
400
- try {
401
- this.storage.clear();
402
- logger.debug("Storage cleared");
403
- } catch (err) {
404
- logger.warn("Storage clear error:", err);
405
- }
406
- }
407
- }
408
- this.fetchAllFlags();
409
- }
410
- updateUser(user) {
411
- this.config = { ...this.config, user };
412
- this.onConfigUpdate?.(this.config);
413
- this.refresh();
414
- }
415
- updateConfig(config) {
416
- this.config = this.withDefaults(config);
417
- this.onConfigUpdate?.(this.config);
418
- if (!this.config.skipStorage && this.storage) {
419
- this.loadCachedFlags();
420
- this.storage.cleanupExpired();
421
- }
422
- if (this.config.autoFetch && !this.config.isPending) {
423
- this.fetchAllFlags();
424
- }
425
- }
426
- getMemoryFlags() {
427
- return { ...this.memoryFlags };
428
- }
429
- getPendingFlags() {
430
- return new Set(this.pendingFlags);
431
- }
432
- notifyFlagsUpdate() {
433
- this.onFlagsUpdate?.(this.getMemoryFlags());
434
- }
435
- }
436
-
437
- const version = "2.3.0";
438
-
439
- const INJECTED_SCRIPT_ATTRIBUTE = "data-databuddy-injected";
440
- function isScriptInjected() {
441
- return !!document.querySelector(`script[${INJECTED_SCRIPT_ATTRIBUTE}]`);
442
- }
443
- function createScript({
444
- scriptUrl,
445
- sdkVersion,
446
- clientSecret,
447
- filter,
448
- debug,
449
- ...props
450
- }) {
451
- const script = document.createElement("script");
452
- script.src = scriptUrl || "https://cdn.databuddy.cc/databuddy.js";
453
- script.async = true;
454
- script.crossOrigin = "anonymous";
455
- script.setAttribute(INJECTED_SCRIPT_ATTRIBUTE, "true");
456
- script.setAttribute("data-sdk-version", sdkVersion || version);
457
- for (const [key, value] of Object.entries(props)) {
458
- if (value === void 0 || value === null) {
459
- continue;
460
- }
461
- const dataKey = `data-${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`;
462
- if (Array.isArray(value) || value && typeof value === "object") {
463
- script.setAttribute(dataKey, JSON.stringify(value));
464
- } else {
465
- script.setAttribute(dataKey, String(value));
466
- }
467
- }
468
- return script;
469
- }
470
-
471
- export { BrowserFlagStorage as B, CoreFlagsManager as C, createScript as c, isScriptInjected as i, logger as l };
@@ -1,64 +0,0 @@
1
- interface FlagResult {
2
- enabled: boolean;
3
- value: boolean;
4
- payload: any;
5
- reason: string;
6
- flagId?: string;
7
- flagType?: "boolean" | "rollout";
8
- }
9
- interface FlagsConfig {
10
- /** Client ID for flag evaluation */
11
- clientId: string;
12
- apiUrl?: string;
13
- user?: {
14
- userId?: string;
15
- email?: string;
16
- properties?: Record<string, any>;
17
- };
18
- disabled?: boolean;
19
- /** Enable debug logging */
20
- debug?: boolean;
21
- /** Skip persistent storage */
22
- skipStorage?: boolean;
23
- /** Whether session is loading */
24
- isPending?: boolean;
25
- /** Automatically fetch all flags on initialization (default: true) */
26
- autoFetch?: boolean;
27
- }
28
- interface FlagState {
29
- enabled: boolean;
30
- isLoading: boolean;
31
- isReady: boolean;
32
- }
33
- interface FlagsContext {
34
- isEnabled: (key: string) => FlagState;
35
- fetchAllFlags: () => Promise<void>;
36
- updateUser: (user: FlagsConfig["user"]) => void;
37
- refresh: (forceClear?: boolean) => Promise<void>;
38
- }
39
- interface StorageInterface {
40
- get(key: string): any;
41
- set(key: string, value: unknown): void;
42
- getAll(): Record<string, unknown>;
43
- clear(): void;
44
- setAll(flags: Record<string, unknown>): void;
45
- cleanupExpired(): void;
46
- }
47
- interface FlagsManagerOptions {
48
- config: FlagsConfig;
49
- storage?: StorageInterface;
50
- onFlagsUpdate?: (flags: Record<string, FlagResult>) => void;
51
- onConfigUpdate?: (config: FlagsConfig) => void;
52
- }
53
- interface FlagsManager {
54
- getFlag: (key: string) => Promise<FlagResult>;
55
- isEnabled: (key: string) => FlagState;
56
- fetchAllFlags: () => Promise<void>;
57
- updateUser: (user: FlagsConfig["user"]) => void;
58
- refresh: (forceClear?: boolean) => void;
59
- updateConfig: (config: FlagsConfig) => void;
60
- getMemoryFlags: () => Record<string, FlagResult>;
61
- getPendingFlags: () => Set<string>;
62
- }
63
-
64
- export type { FlagsManager as F, StorageInterface as S, FlagsManagerOptions as a, FlagResult as b, FlagState as c, FlagsConfig as d, FlagsContext as e };
@@ -1,64 +0,0 @@
1
- interface FlagResult {
2
- enabled: boolean;
3
- value: boolean;
4
- payload: any;
5
- reason: string;
6
- flagId?: string;
7
- flagType?: "boolean" | "rollout";
8
- }
9
- interface FlagsConfig {
10
- /** Client ID for flag evaluation */
11
- clientId: string;
12
- apiUrl?: string;
13
- user?: {
14
- userId?: string;
15
- email?: string;
16
- properties?: Record<string, any>;
17
- };
18
- disabled?: boolean;
19
- /** Enable debug logging */
20
- debug?: boolean;
21
- /** Skip persistent storage */
22
- skipStorage?: boolean;
23
- /** Whether session is loading */
24
- isPending?: boolean;
25
- /** Automatically fetch all flags on initialization (default: true) */
26
- autoFetch?: boolean;
27
- }
28
- interface FlagState {
29
- enabled: boolean;
30
- isLoading: boolean;
31
- isReady: boolean;
32
- }
33
- interface FlagsContext {
34
- isEnabled: (key: string) => FlagState;
35
- fetchAllFlags: () => Promise<void>;
36
- updateUser: (user: FlagsConfig["user"]) => void;
37
- refresh: (forceClear?: boolean) => Promise<void>;
38
- }
39
- interface StorageInterface {
40
- get(key: string): any;
41
- set(key: string, value: unknown): void;
42
- getAll(): Record<string, unknown>;
43
- clear(): void;
44
- setAll(flags: Record<string, unknown>): void;
45
- cleanupExpired(): void;
46
- }
47
- interface FlagsManagerOptions {
48
- config: FlagsConfig;
49
- storage?: StorageInterface;
50
- onFlagsUpdate?: (flags: Record<string, FlagResult>) => void;
51
- onConfigUpdate?: (config: FlagsConfig) => void;
52
- }
53
- interface FlagsManager {
54
- getFlag: (key: string) => Promise<FlagResult>;
55
- isEnabled: (key: string) => FlagState;
56
- fetchAllFlags: () => Promise<void>;
57
- updateUser: (user: FlagsConfig["user"]) => void;
58
- refresh: (forceClear?: boolean) => void;
59
- updateConfig: (config: FlagsConfig) => void;
60
- getMemoryFlags: () => Record<string, FlagResult>;
61
- getPendingFlags: () => Set<string>;
62
- }
63
-
64
- export type { FlagsManager as F, StorageInterface as S, FlagsManagerOptions as a, FlagResult as b, FlagState as c, FlagsConfig as d, FlagsContext as e };