@databuddy/sdk 2.3.21 → 2.3.23

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,4 +1,4 @@
1
- export { c as createScript, i as isScriptInjected } from '../shared/@databuddy/sdk.C8vEu9Y4.mjs';
1
+ export { c as createScript, i as isScriptInjected } from '../shared/@databuddy/sdk.DQLu36uX.mjs';
2
2
 
3
3
  function detectClientId(providedClientId) {
4
4
  if (providedClientId) {
@@ -2,9 +2,9 @@
2
2
 
3
3
  import { detectClientId } from '../core/index.mjs';
4
4
  export { clear, flush, getAnonymousId, getSessionId, getTracker, getTrackingIds, getTrackingParams, isTrackerAvailable, track, trackError } from '../core/index.mjs';
5
- import { i as isScriptInjected, c as createScript } from '../shared/@databuddy/sdk.C8vEu9Y4.mjs';
5
+ import { i as isScriptInjected, c as createScript } from '../shared/@databuddy/sdk.DQLu36uX.mjs';
6
6
  import React, { useRef, useMemo, useEffect, useSyncExternalStore, createContext, useContext } from 'react';
7
- import { B as BrowserFlagStorage, C as CoreFlagsManager } from '../shared/@databuddy/sdk.D0dyEsAb.mjs';
7
+ import { B as BrowserFlagStorage, C as CoreFlagsManager } from '../shared/@databuddy/sdk.csCQ7WQR.mjs';
8
8
  import { l as logger } from '../shared/@databuddy/sdk.DCKr2Zpd.mjs';
9
9
 
10
10
  function Databuddy(props) {
@@ -1,4 +1,4 @@
1
- const version = "2.3.21";
1
+ const version = "2.3.22";
2
2
 
3
3
  const INJECTED_SCRIPT_ATTRIBUTE = "data-databuddy-injected";
4
4
  function isScriptInjected() {
@@ -1,15 +1,25 @@
1
1
  import { l as logger, c as createCacheEntry, i as isCacheValid, b as buildQueryParams, R as RequestBatcher, a as isCacheStale, D as DEFAULT_RESULT, g as getCacheKey, f as fetchAllFlags } from './sdk.DCKr2Zpd.mjs';
2
2
 
3
+ const isBrowser = typeof window !== "undefined" && typeof localStorage !== "undefined";
3
4
  class BrowserFlagStorage {
4
5
  ttl = 24 * 60 * 60 * 1e3;
5
6
  // 24 hours in milliseconds
6
7
  get(key) {
8
+ if (!isBrowser) {
9
+ return null;
10
+ }
7
11
  return this.getFromLocalStorage(key);
8
12
  }
9
13
  set(key, value) {
14
+ if (!isBrowser) {
15
+ return;
16
+ }
10
17
  this.setToLocalStorage(key, value);
11
18
  }
12
19
  getAll() {
20
+ if (!isBrowser) {
21
+ return {};
22
+ }
13
23
  const result = {};
14
24
  const now = Date.now();
15
25
  const keys = Object.keys(localStorage).filter(
@@ -33,6 +43,9 @@ class BrowserFlagStorage {
33
43
  return result;
34
44
  }
35
45
  clear() {
46
+ if (!isBrowser) {
47
+ return;
48
+ }
36
49
  const keys = Object.keys(localStorage).filter(
37
50
  (key) => key.startsWith("db-flag-")
38
51
  );
@@ -77,14 +90,23 @@ class BrowserFlagStorage {
77
90
  return Date.now() > expiresAt;
78
91
  }
79
92
  delete(key) {
93
+ if (!isBrowser) {
94
+ return;
95
+ }
80
96
  localStorage.removeItem(`db-flag-${key}`);
81
97
  }
82
98
  deleteMultiple(keys) {
99
+ if (!isBrowser) {
100
+ return;
101
+ }
83
102
  for (const key of keys) {
84
103
  localStorage.removeItem(`db-flag-${key}`);
85
104
  }
86
105
  }
87
106
  setAll(flags) {
107
+ if (!isBrowser) {
108
+ return;
109
+ }
88
110
  const currentFlags = this.getAll();
89
111
  const currentKeys = Object.keys(currentFlags);
90
112
  const newKeys = Object.keys(flags);
@@ -97,6 +119,9 @@ class BrowserFlagStorage {
97
119
  }
98
120
  }
99
121
  cleanupExpired() {
122
+ if (!isBrowser) {
123
+ return;
124
+ }
100
125
  const now = Date.now();
101
126
  const keys = Object.keys(localStorage).filter(
102
127
  (key) => key.startsWith("db-flag-")
@@ -117,6 +142,7 @@ class BrowserFlagStorage {
117
142
  }
118
143
  }
119
144
 
145
+ const ANONYMOUS_ID_KEY = "did";
120
146
  class CoreFlagsManager {
121
147
  config;
122
148
  storage;
@@ -149,11 +175,45 @@ class CoreFlagsManager {
149
175
  this.setupVisibilityListener();
150
176
  this.initialize();
151
177
  }
178
+ /**
179
+ * Get or create anonymous ID for deterministic rollouts.
180
+ * Uses the same storage key as the tracker ("did") for consistency.
181
+ */
182
+ getOrCreateAnonymousId() {
183
+ if (typeof localStorage === "undefined") {
184
+ return null;
185
+ }
186
+ try {
187
+ let id = localStorage.getItem(ANONYMOUS_ID_KEY);
188
+ if (id) {
189
+ return id;
190
+ }
191
+ id = `anon_${crypto.randomUUID()}`;
192
+ localStorage.setItem(ANONYMOUS_ID_KEY, id);
193
+ return id;
194
+ } catch {
195
+ return null;
196
+ }
197
+ }
198
+ /**
199
+ * Ensure user context has an identifier for deterministic flag evaluation.
200
+ * If no userId/email provided, inject anonymous ID as userId.
201
+ */
202
+ ensureUserIdentity(user) {
203
+ if (user?.userId || user?.email) {
204
+ return user;
205
+ }
206
+ const anonymousId = this.getOrCreateAnonymousId();
207
+ if (!anonymousId) {
208
+ return user;
209
+ }
210
+ return { ...user, userId: anonymousId };
211
+ }
152
212
  withDefaults(config) {
153
213
  return {
154
214
  clientId: config.clientId,
155
215
  apiUrl: config.apiUrl ?? "https://api.databuddy.cc",
156
- user: config.user,
216
+ user: this.ensureUserIdentity(config.user),
157
217
  disabled: config.disabled ?? false,
158
218
  debug: config.debug ?? false,
159
219
  skipStorage: config.skipStorage ?? false,
@@ -181,6 +241,16 @@ class CoreFlagsManager {
181
241
  document.removeEventListener("visibilitychange", handleVisibility);
182
242
  };
183
243
  }
244
+ removeStaleKeys(validKeys, user) {
245
+ const ctx = user ?? this.config.user;
246
+ const suffix = ctx?.userId || ctx?.email ? `:${ctx.userId ?? ""}:${ctx.email ?? ""}` : "";
247
+ for (const key of this.cache.keys()) {
248
+ const belongsToUser = suffix ? key.endsWith(suffix) : !key.includes(":");
249
+ if (belongsToUser && !validKeys.has(key)) {
250
+ this.cache.delete(key);
251
+ }
252
+ }
253
+ }
184
254
  async initialize() {
185
255
  if (!this.config.skipStorage && this.storage) {
186
256
  this.loadFromStorage();
@@ -334,13 +404,20 @@ class CoreFlagsManager {
334
404
  }
335
405
  const apiUrl = this.config.apiUrl ?? "https://api.databuddy.cc";
336
406
  const params = buildQueryParams(this.config, user);
407
+ const ttl = this.config.cacheTtl ?? 6e4;
408
+ const staleTime = this.config.staleTime ?? ttl / 2;
337
409
  try {
338
410
  const flags = await fetchAllFlags(apiUrl, params);
339
- const ttl = this.config.cacheTtl ?? 6e4;
340
- const staleTime = this.config.staleTime ?? ttl / 2;
341
- for (const [key, result] of Object.entries(flags)) {
342
- const cacheKey = getCacheKey(key, user ?? this.config.user);
343
- this.cache.set(cacheKey, createCacheEntry(result, ttl, staleTime));
411
+ const flagCacheEntries = Object.entries(flags).map(([key, result]) => ({
412
+ cacheKey: getCacheKey(key, user ?? this.config.user),
413
+ cacheEntry: createCacheEntry(result, ttl, staleTime)
414
+ }));
415
+ this.removeStaleKeys(
416
+ new Set(flagCacheEntries.map(({ cacheKey }) => cacheKey)),
417
+ user
418
+ );
419
+ for (const { cacheKey, cacheEntry } of flagCacheEntries) {
420
+ this.cache.set(cacheKey, cacheEntry);
344
421
  }
345
422
  this.ready = true;
346
423
  this.notifyUpdate();
@@ -417,7 +494,7 @@ class CoreFlagsManager {
417
494
  * Update user context and refresh flags
418
495
  */
419
496
  updateUser(user) {
420
- this.config = { ...this.config, user };
497
+ this.config = { ...this.config, user: this.ensureUserIdentity(user) };
421
498
  this.batcher?.destroy();
422
499
  this.batcher = null;
423
500
  this.onConfigUpdate?.(this.config);
@@ -1,6 +1,6 @@
1
1
  import { defineComponent, ref, onMounted, onUnmounted, watch, reactive, watchEffect, computed } from 'vue';
2
- import { i as isScriptInjected, c as createScript } from '../shared/@databuddy/sdk.C8vEu9Y4.mjs';
3
- import { B as BrowserFlagStorage, C as CoreFlagsManager } from '../shared/@databuddy/sdk.D0dyEsAb.mjs';
2
+ import { i as isScriptInjected, c as createScript } from '../shared/@databuddy/sdk.DQLu36uX.mjs';
3
+ import { B as BrowserFlagStorage, C as CoreFlagsManager } from '../shared/@databuddy/sdk.csCQ7WQR.mjs';
4
4
  import '../shared/@databuddy/sdk.DCKr2Zpd.mjs';
5
5
 
6
6
  const Databuddy = defineComponent({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@databuddy/sdk",
3
- "version": "2.3.21",
3
+ "version": "2.3.23",
4
4
  "description": "Official Databuddy Analytics SDK",
5
5
  "main": "./dist/core/index.mjs",
6
6
  "types": "./dist/core/index.d.ts",