@statsig/client-core 0.0.1-beta.40 → 0.0.1-beta.42

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statsig/client-core",
3
- "version": "0.0.1-beta.40",
3
+ "version": "0.0.1-beta.42",
4
4
  "dependencies": {},
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -4,12 +4,15 @@ var _a, _b, _c;
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports._getInstance = exports._getStatsigGlobal = void 0;
6
6
  const _getStatsigGlobal = () => {
7
- return __STATSIG__ !== null && __STATSIG__ !== void 0 ? __STATSIG__ : statsigGlobal;
7
+ return __STATSIG__ ? __STATSIG__ : statsigGlobal;
8
8
  };
9
9
  exports._getStatsigGlobal = _getStatsigGlobal;
10
10
  const _getInstance = (sdkKey) => {
11
- var _a;
12
- return sdkKey ? (_a = __STATSIG__ === null || __STATSIG__ === void 0 ? void 0 : __STATSIG__.instances) === null || _a === void 0 ? void 0 : _a[sdkKey] : __STATSIG__ === null || __STATSIG__ === void 0 ? void 0 : __STATSIG__.lastInstance;
11
+ const gbl = (0, exports._getStatsigGlobal)();
12
+ if (!sdkKey) {
13
+ return gbl.lastInstance;
14
+ }
15
+ return gbl.instances && gbl.instances[sdkKey];
13
16
  };
14
17
  exports._getInstance = _getInstance;
15
18
  const GLOBAL_KEY = '__STATSIG__';
@@ -0,0 +1,4 @@
1
+ import { StatsigUser } from './StatsigUser';
2
+ export type CustomCacheKeyGenerator = (sdkKey: string, user: StatsigUser) => string;
3
+ export declare function _getUserStorageKey(sdkKey: string, user: StatsigUser, customKeyGenerator?: CustomCacheKeyGenerator): string;
4
+ export declare function _getStorageKey(sdkKey: string, user?: StatsigUser, customKeyGenerator?: CustomCacheKeyGenerator): string;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports._getStorageKey = exports._getUserStorageKey = void 0;
4
+ const Hashing_1 = require("./Hashing");
5
+ function _getUserStorageKey(sdkKey, user, customKeyGenerator) {
6
+ var _a, _b;
7
+ if (customKeyGenerator) {
8
+ return customKeyGenerator(sdkKey, user);
9
+ }
10
+ const parts = [
11
+ `uid:${(_a = user === null || user === void 0 ? void 0 : user.userID) !== null && _a !== void 0 ? _a : ''}`,
12
+ `cids:${Object.entries((_b = user === null || user === void 0 ? void 0 : user.customIDs) !== null && _b !== void 0 ? _b : {})
13
+ .sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey))
14
+ .map(([key, value]) => `${key}-${value}`)
15
+ .join(',')}`,
16
+ `k:${sdkKey}`,
17
+ ];
18
+ return (0, Hashing_1._DJB2)(parts.join('|'));
19
+ }
20
+ exports._getUserStorageKey = _getUserStorageKey;
21
+ function _getStorageKey(sdkKey, user, customKeyGenerator) {
22
+ if (user) {
23
+ return _getUserStorageKey(sdkKey, user, customKeyGenerator);
24
+ }
25
+ return (0, Hashing_1._DJB2)(`k:${sdkKey}`);
26
+ }
27
+ exports._getStorageKey = _getStorageKey;
@@ -3,12 +3,13 @@ import { AnyStatsigOptions } from './StatsigOptionsCommon';
3
3
  import { StatsigUser } from './StatsigUser';
4
4
  export declare abstract class DataAdapterCore {
5
5
  private _adapterName;
6
- private _cacheSuffix;
6
+ protected _cacheSuffix: string;
7
+ protected _options: AnyStatsigOptions | null;
7
8
  private _sdkKey;
8
9
  private _inMemoryCache;
9
10
  private _lastModifiedStoreKey;
10
11
  protected constructor(_adapterName: string, _cacheSuffix: string);
11
- attach(sdkKey: string, _options: AnyStatsigOptions | null): void;
12
+ attach(sdkKey: string, options: AnyStatsigOptions | null): void;
12
13
  getDataSync(user?: StatsigUser | undefined): DataAdapterResult | null;
13
14
  setData(data: string, user?: StatsigUser): void;
14
15
  /**
@@ -20,10 +21,9 @@ export declare abstract class DataAdapterCore {
20
21
  protected _getDataAsyncImpl(current: DataAdapterResult | null, user?: StatsigUser, options?: DataAdapterAsyncOptions): Promise<DataAdapterResult | null>;
21
22
  protected _prefetchDataImpl(user?: StatsigUser, options?: DataAdapterAsyncOptions): Promise<void>;
22
23
  protected abstract _fetchFromNetwork(current: string | null, user?: StatsigUser, options?: DataAdapterAsyncOptions): Promise<string | null>;
24
+ protected abstract _getCacheKey(user?: StatsigUser): string;
23
25
  private _fetchAndPrepFromNetwork;
24
26
  protected _getSdkKey(): string;
25
- protected _getCacheKey(user?: StatsigUser): string;
26
- protected _addToInMemoryCache(cacheKey: string, result: DataAdapterResult): void;
27
27
  private _loadFromCache;
28
28
  private _writeToCache;
29
29
  private _runLocalStorageCacheEviction;
@@ -11,7 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.DataAdapterCore = void 0;
13
13
  const Log_1 = require("./Log");
14
- const StatsigDataAdapter_1 = require("./StatsigDataAdapter");
14
+ const StableID_1 = require("./StableID");
15
15
  const StatsigUser_1 = require("./StatsigUser");
16
16
  const StorageProvider_1 = require("./StorageProvider");
17
17
  const TypedJsonParse_1 = require("./TypedJsonParse");
@@ -20,33 +20,33 @@ class DataAdapterCore {
20
20
  constructor(_adapterName, _cacheSuffix) {
21
21
  this._adapterName = _adapterName;
22
22
  this._cacheSuffix = _cacheSuffix;
23
+ this._options = null;
23
24
  this._sdkKey = null;
24
- this._inMemoryCache = {};
25
25
  this._lastModifiedStoreKey = `statsig.last_modified_time.${_cacheSuffix}`;
26
+ this._inMemoryCache = new InMemoryCache();
26
27
  }
27
- attach(sdkKey, _options) {
28
+ attach(sdkKey, options) {
28
29
  this._sdkKey = sdkKey;
30
+ this._options = options;
29
31
  }
30
32
  getDataSync(user) {
31
33
  const cacheKey = this._getCacheKey(user);
32
- const result = this._inMemoryCache[cacheKey];
33
- if (result) {
34
- return result;
34
+ const inMem = this._inMemoryCache.get(cacheKey, user);
35
+ if (inMem) {
36
+ return inMem;
35
37
  }
36
38
  const cache = this._loadFromCache(cacheKey);
37
39
  if (cache) {
38
- this._addToInMemoryCache(cacheKey, cache);
39
- return this._inMemoryCache[cacheKey];
40
+ this._inMemoryCache.add(cacheKey, cache);
41
+ return this._inMemoryCache.get(cacheKey, user);
40
42
  }
41
43
  return null;
42
44
  }
43
45
  setData(data, user) {
44
- const cacheKey = this._getCacheKey(user);
45
- this._addToInMemoryCache(cacheKey, {
46
- source: 'Bootstrap',
47
- data,
48
- receivedAt: Date.now(),
49
- });
46
+ var _a;
47
+ const normalized = user && (0, StatsigUser_1._normalizeUser)(user, (_a = this._options) === null || _a === void 0 ? void 0 : _a.environment);
48
+ const cacheKey = this._getCacheKey(normalized);
49
+ this._inMemoryCache.add(cacheKey, _makeDataAdapterResult('Bootstrap', data, null));
50
50
  }
51
51
  /**
52
52
  * (Internal Use Only) - Used by \@statsig/react-native-bindings to prime the cache from AsyncStorage
@@ -54,7 +54,7 @@ class DataAdapterCore {
54
54
  * @param {Record<string, DataAdapterResult>} cache The values to merge into _inMemoryCache
55
55
  */
56
56
  __primeInMemoryCache(cache) {
57
- this._inMemoryCache = Object.assign(Object.assign({}, this._inMemoryCache), cache);
57
+ this._inMemoryCache.merge(cache);
58
58
  }
59
59
  _getDataAsyncImpl(current, user, options) {
60
60
  var _a;
@@ -77,7 +77,7 @@ class DataAdapterCore {
77
77
  const cacheKey = this._getCacheKey(user);
78
78
  const result = yield this._getDataAsyncImpl(null, user, options);
79
79
  if (result) {
80
- this._addToInMemoryCache(cacheKey, Object.assign(Object.assign({}, result), { source: 'Prefetch' }));
80
+ this._inMemoryCache.add(cacheKey, Object.assign(Object.assign({}, result), { source: 'Prefetch' }));
81
81
  }
82
82
  });
83
83
  }
@@ -88,23 +88,21 @@ class DataAdapterCore {
88
88
  Log_1.Log.debug('No response returned for latest value');
89
89
  return null;
90
90
  }
91
- const response = (0, TypedJsonParse_1.typedJsonParse)(latest, 'has_updates', 'Failure while attempting to persist latest value');
91
+ const response = (0, TypedJsonParse_1._typedJsonParse)(latest, 'has_updates', 'Initialize Response');
92
+ const sdkKey = this._getSdkKey();
93
+ const stableID = yield StableID_1.StableID.get(sdkKey);
92
94
  let result = null;
93
95
  if ((response === null || response === void 0 ? void 0 : response.has_updates) === true) {
94
- result = { source: 'Network', data: latest, receivedAt: Date.now() };
96
+ result = _makeDataAdapterResult('Network', latest, stableID);
95
97
  }
96
98
  else if (current && (response === null || response === void 0 ? void 0 : response.has_updates) === false) {
97
- result = {
98
- source: 'NetworkNotModified',
99
- data: current,
100
- receivedAt: Date.now(),
101
- };
99
+ result = _makeDataAdapterResult('NetworkNotModified', current, stableID);
102
100
  }
103
- if (!result) {
101
+ else {
104
102
  return null;
105
103
  }
106
104
  const cacheKey = this._getCacheKey(user);
107
- this._addToInMemoryCache(cacheKey, result);
105
+ this._inMemoryCache.add(cacheKey, result);
108
106
  yield this._writeToCache(cacheKey, result);
109
107
  return result;
110
108
  });
@@ -116,29 +114,13 @@ class DataAdapterCore {
116
114
  Log_1.Log.error(`${this._adapterName} is not attached to a Client`);
117
115
  return '';
118
116
  }
119
- _getCacheKey(user) {
120
- const key = (0, StatsigUser_1.getUserStorageKey)(this._getSdkKey(), user);
121
- return `${StatsigDataAdapter_1.DataAdapterCachePrefix}.${this._cacheSuffix}.${key}`;
122
- }
123
- _addToInMemoryCache(cacheKey, result) {
124
- const entries = Object.entries(this._inMemoryCache);
125
- if (entries.length < CACHE_LIMIT) {
126
- this._inMemoryCache[cacheKey] = result;
127
- return;
128
- }
129
- const [oldest] = entries.reduce((acc, curr) => {
130
- return curr[1] < acc[1] ? curr : acc;
131
- });
132
- delete this._inMemoryCache[oldest];
133
- this._inMemoryCache[cacheKey] = result;
134
- }
135
117
  _loadFromCache(cacheKey) {
136
118
  var _a;
137
119
  const cache = (_a = StorageProvider_1.Storage._getItemSync) === null || _a === void 0 ? void 0 : _a.call(StorageProvider_1.Storage, cacheKey);
138
120
  if (cache == null) {
139
121
  return null;
140
122
  }
141
- const result = (0, TypedJsonParse_1.typedJsonParse)(cache, 'source', 'Failed to parse cached result');
123
+ const result = (0, TypedJsonParse_1._typedJsonParse)(cache, 'source', 'Cached Result');
142
124
  return result ? Object.assign(Object.assign({}, result), { source: 'Cache' }) : null;
143
125
  }
144
126
  _writeToCache(cacheKey, result) {
@@ -167,3 +149,42 @@ class DataAdapterCore {
167
149
  }
168
150
  }
169
151
  exports.DataAdapterCore = DataAdapterCore;
152
+ function _makeDataAdapterResult(source, data, stableID) {
153
+ return {
154
+ source,
155
+ data,
156
+ receivedAt: Date.now(),
157
+ stableID,
158
+ };
159
+ }
160
+ class InMemoryCache {
161
+ constructor() {
162
+ this._data = {};
163
+ }
164
+ get(cacheKey, user) {
165
+ var _a;
166
+ const result = this._data[cacheKey];
167
+ const cached = result === null || result === void 0 ? void 0 : result.stableID;
168
+ const provided = (_a = user === null || user === void 0 ? void 0 : user.customIDs) === null || _a === void 0 ? void 0 : _a.stableID;
169
+ if (provided && cached && provided !== cached) {
170
+ Log_1.Log.warn("'StatsigUser.customIDs.stableID' mismatch");
171
+ return null;
172
+ }
173
+ return result;
174
+ }
175
+ add(cacheKey, value) {
176
+ const entries = Object.entries(this._data);
177
+ if (entries.length < CACHE_LIMIT) {
178
+ this._data[cacheKey] = value;
179
+ return;
180
+ }
181
+ const [oldest] = entries.reduce((acc, curr) => {
182
+ return curr[1] < acc[1] ? curr : acc;
183
+ });
184
+ delete this._data[oldest];
185
+ this._data[cacheKey] = value;
186
+ }
187
+ merge(values) {
188
+ this._data = Object.assign(Object.assign({}, this._data), values);
189
+ }
190
+ }
@@ -8,7 +8,7 @@ export declare class ErrorBoundary {
8
8
  private _seen;
9
9
  constructor(_sdkKey: string, _options: AnyStatsigOptions | null, _emitter?: StatsigClientEmitEventFunc | undefined);
10
10
  wrap(instance: unknown): void;
11
- capture(tag: string, task: () => unknown): unknown;
12
11
  logError(tag: string, error: unknown): void;
12
+ private _capture;
13
13
  private _onError;
14
14
  }
@@ -14,6 +14,7 @@ const Log_1 = require("./Log");
14
14
  const SDKType_1 = require("./SDKType");
15
15
  const StatsigMetadata_1 = require("./StatsigMetadata");
16
16
  exports.EXCEPTION_ENDPOINT = 'https://statsigapi.net/v1/sdk_exception';
17
+ const UNKNOWN_ERROR = '[Statsig] UnknownError';
17
18
  class ErrorBoundary {
18
19
  constructor(_sdkKey, _options, _emitter) {
19
20
  this._sdkKey = _sdkKey;
@@ -30,7 +31,7 @@ class ErrorBoundary {
30
31
  return;
31
32
  }
32
33
  obj[name] = (...args) => {
33
- return this.capture(name, () => original.apply(instance, args));
34
+ return this._capture(name, () => original.apply(instance, args));
34
35
  };
35
36
  obj[name].$EB = true;
36
37
  });
@@ -39,7 +40,10 @@ class ErrorBoundary {
39
40
  this._onError('eb:wrap', err);
40
41
  }
41
42
  }
42
- capture(tag, task) {
43
+ logError(tag, error) {
44
+ this._onError(tag, error);
45
+ }
46
+ _capture(tag, task) {
43
47
  try {
44
48
  const res = task();
45
49
  if (res && res instanceof Promise) {
@@ -52,15 +56,12 @@ class ErrorBoundary {
52
56
  return null;
53
57
  }
54
58
  }
55
- logError(tag, error) {
56
- this._onError(tag, error);
57
- }
58
59
  _onError(tag, error) {
59
60
  try {
60
61
  Log_1.Log.warn(`Caught error in ${tag}`, { error });
61
62
  const impl = () => __awaiter(this, void 0, void 0, function* () {
62
63
  var _a, _b, _c, _d, _e, _f, _g;
63
- const unwrapped = (error !== null && error !== void 0 ? error : Error('[Statsig] Error was empty'));
64
+ const unwrapped = (error ? error : Error(UNKNOWN_ERROR));
64
65
  const isError = unwrapped instanceof Error;
65
66
  const name = isError ? unwrapped.name : 'No Name';
66
67
  if (this._seen.has(name)) {
@@ -107,7 +108,7 @@ function _getDescription(obj) {
107
108
  return JSON.stringify(obj);
108
109
  }
109
110
  catch (_a) {
110
- return '[Statsig] Failed to get string for error.';
111
+ return UNKNOWN_ERROR;
111
112
  }
112
113
  }
113
114
  function _getAllInstanceMethodNames(instance) {
@@ -8,7 +8,7 @@ export declare class EventLogger {
8
8
  private _network;
9
9
  private _options;
10
10
  private _queue;
11
- private _flushTimer;
11
+ private _flushIntervalId;
12
12
  private _lastExposureTimeMap;
13
13
  private _nonExposedChecks;
14
14
  private _maxQueueSize;
@@ -16,7 +16,6 @@ export declare class EventLogger {
16
16
  private _creationTime;
17
17
  private _isLoggingDisabled;
18
18
  private _logEventUrl;
19
- private _logEventBeaconUrl;
20
19
  constructor(_sdkKey: string, _emitter: StatsigClientEmitEventFunc, _network: NetworkCore, _options: StatsigOptionsCommon<NetworkConfigCommon> | null);
21
20
  setLoggingDisabled(isDisabled: boolean): void;
22
21
  enqueue(event: StatsigEventInternal): void;
@@ -33,10 +32,12 @@ export declare class EventLogger {
33
32
  private _sendEvents;
34
33
  private _sendEventsViaPost;
35
34
  private _sendEventsViaBeacon;
35
+ private _getRequestData;
36
36
  private _saveFailedLogsToStorage;
37
37
  private _retryFailedLogs;
38
38
  private _getStorageKey;
39
39
  private _normalizeAndAppendEvent;
40
40
  private _appendAndResetNonExposedChecks;
41
41
  private _getCurrentPageUrl;
42
+ private _startBackgroundFlushInterval;
42
43
  }
@@ -16,7 +16,6 @@ const NetworkConfig_1 = require("./NetworkConfig");
16
16
  const SafeJs_1 = require("./SafeJs");
17
17
  const StatsigEvent_1 = require("./StatsigEvent");
18
18
  const StorageProvider_1 = require("./StorageProvider");
19
- const TypedJsonParse_1 = require("./TypedJsonParse");
20
19
  const UrlOverrides_1 = require("./UrlOverrides");
21
20
  const VisibilityObserving_1 = require("./VisibilityObserving");
22
21
  const DEFAULT_QUEUE_SIZE = 50;
@@ -34,7 +33,7 @@ const _safeFlushAndForget = (sdkKey) => {
34
33
  };
35
34
  class EventLogger {
36
35
  constructor(_sdkKey, _emitter, _network, _options) {
37
- var _a, _b;
36
+ var _a;
38
37
  this._sdkKey = _sdkKey;
39
38
  this._emitter = _emitter;
40
39
  this._network = _network;
@@ -47,25 +46,15 @@ class EventLogger {
47
46
  EVENT_LOGGER_MAP[_sdkKey] = this;
48
47
  this._isLoggingDisabled = (_options === null || _options === void 0 ? void 0 : _options.disableLogging) === true;
49
48
  this._maxQueueSize = (_a = _options === null || _options === void 0 ? void 0 : _options.loggingBufferMaxSize) !== null && _a !== void 0 ? _a : DEFAULT_QUEUE_SIZE;
50
- const flushInterval = (_b = _options === null || _options === void 0 ? void 0 : _options.loggingIntervalMs) !== null && _b !== void 0 ? _b : DEFAULT_FLUSH_INTERVAL_MS;
51
- const intervalId = setInterval(() => {
52
- const logger = EVENT_LOGGER_MAP[_sdkKey];
53
- if (!logger) {
54
- clearInterval(intervalId);
55
- return;
56
- }
57
- _safeFlushAndForget(_sdkKey);
58
- }, flushInterval);
59
- this._flushTimer = intervalId;
60
49
  const config = _options === null || _options === void 0 ? void 0 : _options.networkConfig;
61
50
  this._logEventUrl = (0, UrlOverrides_1._getOverridableUrl)(config === null || config === void 0 ? void 0 : config.logEventUrl, config === null || config === void 0 ? void 0 : config.api, '/rgstr', NetworkConfig_1.NetworkDefault.eventsApi);
62
- this._logEventBeaconUrl = (0, UrlOverrides_1._getOverridableUrl)(config === null || config === void 0 ? void 0 : config.logEventBeaconUrl, config === null || config === void 0 ? void 0 : config.api, '/log_event_beacon', NetworkConfig_1.NetworkDefault.eventsApi);
63
51
  (0, VisibilityObserving_1._subscribeToVisiblityChanged)((visibility) => {
64
52
  if (visibility === 'background') {
65
53
  _safeFlushAndForget(_sdkKey);
66
54
  }
67
55
  });
68
56
  this._retryFailedLogs();
57
+ this._startBackgroundFlushInterval();
69
58
  }
70
59
  setLoggingDisabled(isDisabled) {
71
60
  this._isLoggingDisabled = isDisabled;
@@ -90,9 +79,9 @@ class EventLogger {
90
79
  }
91
80
  shutdown() {
92
81
  return __awaiter(this, void 0, void 0, function* () {
93
- if (this._flushTimer) {
94
- clearInterval(this._flushTimer);
95
- this._flushTimer = null;
82
+ if (this._flushIntervalId) {
83
+ clearInterval(this._flushIntervalId);
84
+ this._flushIntervalId = null;
96
85
  }
97
86
  yield this.flush();
98
87
  });
@@ -105,7 +94,7 @@ class EventLogger {
105
94
  }
106
95
  const events = this._queue;
107
96
  this._queue = [];
108
- yield this._sendEvents(events);
97
+ return this._sendEvents(events);
109
98
  });
110
99
  }
111
100
  /**
@@ -123,16 +112,17 @@ class EventLogger {
123
112
  setTimeout(() => _safeFlushAndForget(this._sdkKey), QUICK_FLUSH_WINDOW_MS);
124
113
  }
125
114
  _shouldLogEvent(event) {
126
- var _a, _b, _c, _d;
127
115
  if (!(0, StatsigEvent_1._isExposureEvent)(event)) {
128
116
  return true;
129
117
  }
118
+ const user = event.user ? event.user : {};
119
+ const metadata = event.metadata ? event.metadata : {};
130
120
  const key = [
131
121
  event.eventName,
132
- (_a = event.user) === null || _a === void 0 ? void 0 : _a.userID,
133
- (_b = event.metadata) === null || _b === void 0 ? void 0 : _b['gate'],
134
- (_c = event.metadata) === null || _c === void 0 ? void 0 : _c['config'],
135
- (_d = event.metadata) === null || _d === void 0 ? void 0 : _d['ruleID'],
122
+ user.userID,
123
+ metadata['gate'],
124
+ metadata['config'],
125
+ metadata['ruleID'],
136
126
  ].join('|');
137
127
  const previous = this._lastExposureTimeMap[key];
138
128
  const now = Date.now();
@@ -176,37 +166,34 @@ class EventLogger {
176
166
  });
177
167
  }
178
168
  _sendEventsViaPost(events) {
169
+ var _a;
179
170
  return __awaiter(this, void 0, void 0, function* () {
180
- const result = yield this._network.post({
181
- sdkKey: this._sdkKey,
182
- data: {
183
- events,
184
- },
185
- url: this._logEventUrl,
186
- retries: 3,
187
- params: {
188
- [NetworkConfig_1.NetworkParam.EventCount]: String(events.length),
189
- },
190
- });
191
- const response = (result === null || result === void 0 ? void 0 : result.body)
192
- ? (0, TypedJsonParse_1.typedJsonParse)(result.body, 'success', 'Failed to parse SendEventsResponse')
193
- : null;
194
- return { success: (response === null || response === void 0 ? void 0 : response.success) === true };
171
+ const result = yield this._network.post(this._getRequestData(events));
172
+ const code = (_a = result === null || result === void 0 ? void 0 : result.code) !== null && _a !== void 0 ? _a : -1;
173
+ return { success: code >= 200 && code < 300 };
195
174
  });
196
175
  }
197
176
  _sendEventsViaBeacon(events) {
198
177
  return __awaiter(this, void 0, void 0, function* () {
199
178
  return {
200
- success: yield this._network.beacon({
201
- sdkKey: this._sdkKey,
202
- data: {
203
- events,
204
- },
205
- url: this._logEventBeaconUrl,
206
- }),
179
+ success: yield this._network.beacon(this._getRequestData(events)),
207
180
  };
208
181
  });
209
182
  }
183
+ _getRequestData(events) {
184
+ return {
185
+ sdkKey: this._sdkKey,
186
+ data: {
187
+ events,
188
+ },
189
+ url: this._logEventUrl,
190
+ retries: 3,
191
+ isCompressable: true,
192
+ params: {
193
+ [NetworkConfig_1.NetworkParam.EventCount]: String(events.length),
194
+ },
195
+ };
196
+ }
210
197
  _saveFailedLogsToStorage(events) {
211
198
  while (events.length > MAX_FAILED_LOGS) {
212
199
  events.shift();
@@ -230,7 +217,7 @@ class EventLogger {
230
217
  });
231
218
  }
232
219
  _getStorageKey() {
233
- return `statsig.failed_logs.${(0, Hashing_1.DJB2)(this._sdkKey)}`;
220
+ return `statsig.failed_logs.${(0, Hashing_1._DJB2)(this._sdkKey)}`;
234
221
  }
235
222
  _normalizeAndAppendEvent(event) {
236
223
  if (event.user) {
@@ -242,7 +229,9 @@ class EventLogger {
242
229
  if (currentPage) {
243
230
  extras.statsigMetadata = { currentPage };
244
231
  }
245
- this._queue.push(Object.assign(Object.assign({}, event), extras));
232
+ const final = Object.assign(Object.assign({}, event), extras);
233
+ Log_1.Log.debug('Enqueued Event:', final);
234
+ this._queue.push(final);
246
235
  }
247
236
  _appendAndResetNonExposedChecks() {
248
237
  if (Object.keys(this._nonExposedChecks).length === 0) {
@@ -265,5 +254,22 @@ class EventLogger {
265
254
  }
266
255
  return (0, SafeJs_1._getCurrentPageUrlSafe)();
267
256
  }
257
+ _startBackgroundFlushInterval() {
258
+ var _a, _b;
259
+ if (!(0, SafeJs_1._isBrowserEnv)()) {
260
+ return; // do not run in server environments
261
+ }
262
+ const flushInterval = (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.loggingIntervalMs) !== null && _b !== void 0 ? _b : DEFAULT_FLUSH_INTERVAL_MS;
263
+ const intervalId = setInterval(() => {
264
+ const logger = EVENT_LOGGER_MAP[this._sdkKey];
265
+ if (logger._flushIntervalId !== intervalId) {
266
+ clearInterval(intervalId);
267
+ }
268
+ else {
269
+ _safeFlushAndForget(this._sdkKey);
270
+ }
271
+ }, flushInterval);
272
+ this._flushIntervalId = intervalId;
273
+ }
268
274
  }
269
275
  exports.EventLogger = EventLogger;
package/src/Hashing.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const DJB2: (value: string) => string;
2
- export declare const DJB2Object: (value: Record<string, unknown> | null) => string;
1
+ export declare const _DJB2: (value: string) => string;
2
+ export declare const _DJB2Object: (value: Record<string, unknown> | null) => string;
package/src/Hashing.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DJB2Object = exports.DJB2 = void 0;
4
- const DJB2 = (value) => {
3
+ exports._DJB2Object = exports._DJB2 = void 0;
4
+ const _DJB2 = (value) => {
5
5
  let hash = 0;
6
6
  for (let i = 0; i < value.length; i++) {
7
7
  const character = value.charCodeAt(i);
@@ -10,11 +10,11 @@ const DJB2 = (value) => {
10
10
  }
11
11
  return String(hash >>> 0);
12
12
  };
13
- exports.DJB2 = DJB2;
14
- const DJB2Object = (value) => {
15
- return (0, exports.DJB2)(JSON.stringify(_getSortedObject(value)));
13
+ exports._DJB2 = _DJB2;
14
+ const _DJB2Object = (value) => {
15
+ return (0, exports._DJB2)(JSON.stringify(_getSortedObject(value)));
16
16
  };
17
- exports.DJB2Object = DJB2Object;
17
+ exports._DJB2Object = _DJB2Object;
18
18
  const _getSortedObject = (object) => {
19
19
  if (object == null) {
20
20
  return null;
@@ -14,5 +14,6 @@ export declare enum NetworkParam {
14
14
  SdkVersion = "sv",
15
15
  Time = "t",
16
16
  SessionID = "sid",
17
- StatsigEncoded = "se"
17
+ StatsigEncoded = "se",
18
+ IsGzipped = "gz"
18
19
  }
@@ -15,4 +15,5 @@ var NetworkParam;
15
15
  NetworkParam["Time"] = "t";
16
16
  NetworkParam["SessionID"] = "sid";
17
17
  NetworkParam["StatsigEncoded"] = "se";
18
+ NetworkParam["IsGzipped"] = "gz";
18
19
  })(NetworkParam || (exports.NetworkParam = NetworkParam = {}));
@@ -2,6 +2,7 @@ import './$_StatsigGlobal';
2
2
  import { NetworkPriority } from './NetworkConfig';
3
3
  import { StatsigClientEmitEventFunc } from './StatsigClientBase';
4
4
  import { AnyStatsigOptions } from './StatsigOptionsCommon';
5
+ import { Flatten } from './UtitlityTypes';
5
6
  type RequestArgs = {
6
7
  sdkKey: string;
7
8
  url: string;
@@ -10,10 +11,12 @@ type RequestArgs = {
10
11
  params?: Record<string, string>;
11
12
  headers?: Record<string, string>;
12
13
  };
13
- type RequestArgsWithData = RequestArgs & {
14
+ export type RequestArgsWithData = Flatten<RequestArgs & {
14
15
  data: Record<string, unknown>;
15
16
  isStatsigEncodable?: boolean;
16
- };
17
+ isCompressable?: boolean;
18
+ }>;
19
+ type BeaconRequestArgs = Pick<RequestArgsWithData, 'data' | 'sdkKey' | 'url' | 'params' | 'isCompressable'>;
17
20
  type NetworkResponse = {
18
21
  body: string | null;
19
22
  code: number;
@@ -22,11 +25,12 @@ export declare class NetworkCore {
22
25
  private _options;
23
26
  private _emitter?;
24
27
  private readonly _timeout;
28
+ private readonly _netConfig;
25
29
  constructor(_options: AnyStatsigOptions | null, _emitter?: StatsigClientEmitEventFunc | undefined);
26
30
  post(args: RequestArgsWithData): Promise<NetworkResponse | null>;
27
31
  get(args: RequestArgs): Promise<NetworkResponse | null>;
28
32
  isBeaconSupported(): boolean;
29
- beacon(args: RequestArgsWithData): Promise<boolean>;
33
+ beacon(args: BeaconRequestArgs): Promise<boolean>;
30
34
  private _sendRequest;
31
35
  private _getPopulatedURL;
32
36
  private _getPopulatedBody;
@@ -19,19 +19,26 @@ const SafeJs_1 = require("./SafeJs");
19
19
  const SessionID_1 = require("./SessionID");
20
20
  const StableID_1 = require("./StableID");
21
21
  const StatsigMetadata_1 = require("./StatsigMetadata");
22
+ const VisibilityObserving_1 = require("./VisibilityObserving");
22
23
  const DEFAULT_TIMEOUT_MS = 10000;
23
24
  class NetworkCore {
24
25
  constructor(_options, _emitter) {
25
- var _a, _b;
26
+ var _a, _b, _c;
26
27
  this._options = _options;
27
28
  this._emitter = _emitter;
28
- this._timeout =
29
- (_b = (_a = _options === null || _options === void 0 ? void 0 : _options.networkConfig) === null || _a === void 0 ? void 0 : _a.networkTimeoutMs) !== null && _b !== void 0 ? _b : DEFAULT_TIMEOUT_MS;
29
+ this._netConfig = (_a = _options === null || _options === void 0 ? void 0 : _options.networkConfig) !== null && _a !== void 0 ? _a : null;
30
+ this._timeout = (_c = (_b = this._netConfig) === null || _b === void 0 ? void 0 : _b.networkTimeoutMs) !== null && _c !== void 0 ? _c : DEFAULT_TIMEOUT_MS;
30
31
  }
31
32
  post(args) {
32
33
  return __awaiter(this, void 0, void 0, function* () {
33
- const body = yield this._getPopulatedBody(args);
34
- return this._sendRequest(Object.assign({ method: 'POST', body: this._attemptToEncodeString(args, body) }, args));
34
+ let body = yield this._getPopulatedBody(args);
35
+ if (args.isStatsigEncodable) {
36
+ body = this._attemptToEncodeString(args, body);
37
+ }
38
+ else if (args.isCompressable) {
39
+ body = yield _attemptToCompressBody(args, body);
40
+ }
41
+ return this._sendRequest(Object.assign({ method: 'POST', body }, args));
35
42
  });
36
43
  }
37
44
  get(args) {
@@ -39,47 +46,49 @@ class NetworkCore {
39
46
  }
40
47
  isBeaconSupported() {
41
48
  return (typeof navigator !== 'undefined' &&
42
- typeof (navigator === null || navigator === void 0 ? void 0 : navigator.sendBeacon) === 'function');
49
+ typeof navigator.sendBeacon === 'function');
43
50
  }
44
51
  beacon(args) {
45
52
  return __awaiter(this, void 0, void 0, function* () {
46
53
  if (!_ensureValidSdkKey(args)) {
47
54
  return false;
48
55
  }
56
+ let body = yield this._getPopulatedBody(args);
57
+ body = yield _attemptToCompressBody(args, body);
49
58
  const url = yield this._getPopulatedURL(args);
50
- const body = yield this._getPopulatedBody(args);
51
59
  return navigator.sendBeacon(url, body);
52
60
  });
53
61
  }
54
62
  _sendRequest(args) {
55
- var _a, _b, _c, _d, _e, _f;
63
+ var _a, _b, _c, _d;
56
64
  return __awaiter(this, void 0, void 0, function* () {
57
65
  if (!_ensureValidSdkKey(args)) {
58
66
  return null;
59
67
  }
60
- if ((_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.networkConfig) === null || _b === void 0 ? void 0 : _b.preventAllNetworkTraffic) {
68
+ if ((_a = this._netConfig) === null || _a === void 0 ? void 0 : _a.preventAllNetworkTraffic) {
61
69
  return null;
62
70
  }
63
71
  const { method, body, retries } = args;
64
- const controller = new AbortController();
65
- const handle = setTimeout(() => controller.abort(`Timeout of ${this._timeout}ms expired.`), this._timeout);
72
+ const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
73
+ const handle = setTimeout(() => controller === null || controller === void 0 ? void 0 : controller.abort(`Timeout of ${this._timeout}ms expired.`), this._timeout);
66
74
  const url = yield this._getPopulatedURL(args);
67
75
  let response = null;
76
+ const keepalive = (0, VisibilityObserving_1._isUnloading)();
68
77
  try {
69
78
  const config = {
70
79
  method,
71
80
  body,
72
81
  headers: Object.assign({}, args.headers),
73
- signal: controller.signal,
82
+ signal: controller === null || controller === void 0 ? void 0 : controller.signal,
74
83
  priority: args.priority,
75
- keepalive: true,
84
+ keepalive,
76
85
  };
77
- const func = (_e = (_d = (_c = this._options) === null || _c === void 0 ? void 0 : _c.networkConfig) === null || _d === void 0 ? void 0 : _d.networkOverrideFunc) !== null && _e !== void 0 ? _e : fetch;
86
+ const func = (_c = (_b = this._netConfig) === null || _b === void 0 ? void 0 : _b.networkOverrideFunc) !== null && _c !== void 0 ? _c : fetch;
78
87
  response = yield func(url, config);
79
88
  clearTimeout(handle);
80
89
  if (!response.ok) {
81
90
  const text = yield response.text().catch(() => 'No Text');
82
- const err = new Error(`Failed to fetch: ${url} ${text}`);
91
+ const err = new Error(`NetworkError: ${url} ${text}`);
83
92
  err.name = 'NetworkError';
84
93
  throw err;
85
94
  }
@@ -94,7 +103,7 @@ class NetworkCore {
94
103
  const errorMessage = _getErrorMessage(controller, error);
95
104
  Diagnostics_1.Diagnostics.mark();
96
105
  if (!retries || retries <= 0) {
97
- (_f = this._emitter) === null || _f === void 0 ? void 0 : _f.call(this, { name: 'error', error });
106
+ (_d = this._emitter) === null || _d === void 0 ? void 0 : _d.call(this, { name: 'error', error });
98
107
  Log_1.Log.error(`A networking error occured during ${method} request to ${url}.`, errorMessage, error);
99
108
  return null;
100
109
  }
@@ -153,7 +162,7 @@ const _ensureValidSdkKey = (args) => {
153
162
  return true;
154
163
  };
155
164
  function _getErrorMessage(controller, error) {
156
- if (controller.signal.aborted &&
165
+ if ((controller === null || controller === void 0 ? void 0 : controller.signal.aborted) &&
157
166
  typeof controller.signal.reason === 'string') {
158
167
  return controller.signal.reason;
159
168
  }
@@ -165,3 +174,21 @@ function _getErrorMessage(controller, error) {
165
174
  }
166
175
  return 'Unknown Error';
167
176
  }
177
+ function _attemptToCompressBody(args, body) {
178
+ var _a;
179
+ return __awaiter(this, void 0, void 0, function* () {
180
+ if (!args.isCompressable ||
181
+ typeof CompressionStream === 'undefined' ||
182
+ typeof TextEncoder === 'undefined' ||
183
+ (__STATSIG__ === null || __STATSIG__ === void 0 ? void 0 : __STATSIG__['no-compress']) != null) {
184
+ return body;
185
+ }
186
+ const bytes = new TextEncoder().encode(body);
187
+ const stream = new CompressionStream('gzip');
188
+ const writer = stream.writable.getWriter();
189
+ writer.write(bytes).catch(Log_1.Log.error);
190
+ writer.close().catch(Log_1.Log.error);
191
+ args.params = Object.assign(Object.assign({}, ((_a = args.params) !== null && _a !== void 0 ? _a : {})), { [NetworkConfig_1.NetworkParam.IsGzipped]: '1' });
192
+ return yield new Response(stream.readable).arrayBuffer();
193
+ });
194
+ }
package/src/SessionID.js CHANGED
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.StatsigSession = exports.SessionID = void 0;
13
- const Hashing_1 = require("./Hashing");
13
+ const CacheKey_1 = require("./CacheKey");
14
14
  const Log_1 = require("./Log");
15
15
  const StorageProvider_1 = require("./StorageProvider");
16
16
  const UUID_1 = require("./UUID");
@@ -80,7 +80,7 @@ function _hasRunTooLong({ startTime }) {
80
80
  return Date.now() - startTime > MAX_SESSION_AGE;
81
81
  }
82
82
  function _getSessionIDStorageKey(sdkKey) {
83
- return `statsig.session_id.${(0, Hashing_1.DJB2)(sdkKey)}`;
83
+ return `statsig.session_id.${(0, CacheKey_1._getStorageKey)(sdkKey)}`;
84
84
  }
85
85
  function _persistToStorage(session, sdkKey) {
86
86
  const storageKey = _getSessionIDStorageKey(sdkKey);
package/src/StableID.js CHANGED
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.StableID = void 0;
13
- const Hashing_1 = require("./Hashing");
13
+ const CacheKey_1 = require("./CacheKey");
14
14
  const Log_1 = require("./Log");
15
15
  const StorageProvider_1 = require("./StorageProvider");
16
16
  const UUID_1 = require("./UUID");
@@ -35,7 +35,7 @@ exports.StableID = {
35
35
  },
36
36
  };
37
37
  function _getStableIDStorageKey(sdkKey) {
38
- return `statsig.stable_id.${(0, Hashing_1.DJB2)(sdkKey)}`;
38
+ return `statsig.stable_id.${(0, CacheKey_1._getStorageKey)(sdkKey)}`;
39
39
  }
40
40
  function _persistToStorage(stableID, sdkKey) {
41
41
  const storageKey = _getStableIDStorageKey(sdkKey);
@@ -63,4 +63,5 @@ export declare abstract class StatsigClientBase<TAdapter extends EvaluationsData
63
63
  $emt(event: AnyStatsigClientEvent): void;
64
64
  protected _setStatus(newStatus: StatsigLoadingStatus, values: DataAdapterResult | null): void;
65
65
  protected _enqueueExposure(name: string, exposure: StatsigEventInternal, options?: EvaluationOptionsCommon): void;
66
+ protected abstract _primeReadyRipcord(): void;
66
67
  }
@@ -16,7 +16,6 @@ const ErrorBoundary_1 = require("./ErrorBoundary");
16
16
  const EventLogger_1 = require("./EventLogger");
17
17
  const Log_1 = require("./Log");
18
18
  const SafeJs_1 = require("./SafeJs");
19
- const StableID_1 = require("./StableID");
20
19
  const StorageProvider_1 = require("./StorageProvider");
21
20
  class StatsigClientBase {
22
21
  constructor(sdkKey, adapter, network, options) {
@@ -24,30 +23,32 @@ class StatsigClientBase {
24
23
  this.loadingStatus = 'Uninitialized';
25
24
  this._listeners = {};
26
25
  const emitter = this.$emt.bind(this);
27
- const statsigGlobal = (0, __StatsigGlobal_1._getStatsigGlobal)();
28
- const instances = (_a = statsigGlobal.instances) !== null && _a !== void 0 ? _a : {};
29
- const inst = this;
30
- (options === null || options === void 0 ? void 0 : options.logLevel) && (Log_1.Log.level = options === null || options === void 0 ? void 0 : options.logLevel);
26
+ (options === null || options === void 0 ? void 0 : options.logLevel) != null && (Log_1.Log.level = options.logLevel);
31
27
  (options === null || options === void 0 ? void 0 : options.disableStorage) && StorageProvider_1.Storage._setDisabled(true);
32
- (options === null || options === void 0 ? void 0 : options.overrideStableID) &&
33
- StableID_1.StableID.setOverride(options.overrideStableID, sdkKey);
28
+ this._sdkKey = sdkKey;
29
+ this._options = options !== null && options !== void 0 ? options : {};
30
+ this._overrideAdapter = (_a = options === null || options === void 0 ? void 0 : options.overrideAdapter) !== null && _a !== void 0 ? _a : null;
31
+ this._logger = new EventLogger_1.EventLogger(sdkKey, emitter, network, options);
34
32
  this._errorBoundary = new ErrorBoundary_1.ErrorBoundary(sdkKey, options, emitter);
35
33
  this._errorBoundary.wrap(this);
36
34
  this._errorBoundary.wrap(network);
37
35
  this._errorBoundary.wrap(adapter);
38
- this._sdkKey = sdkKey;
39
- this._options = options !== null && options !== void 0 ? options : {};
40
- this._overrideAdapter = (_b = options === null || options === void 0 ? void 0 : options.overrideAdapter) !== null && _b !== void 0 ? _b : null;
41
- this._logger = new EventLogger_1.EventLogger(sdkKey, emitter, network, options);
42
- if (instances[sdkKey] != null && (0, SafeJs_1._isBrowserEnv)()) {
43
- Log_1.Log.warn('Creating multiple Statsig clients with the same SDK key can lead to unexpected behavior. Multi-instance support requires different SDK keys.');
36
+ this._errorBoundary.wrap(this._logger);
37
+ if ((0, SafeJs_1._isBrowserEnv)()) {
38
+ const statsigGlobal = (0, __StatsigGlobal_1._getStatsigGlobal)();
39
+ const instances = (_b = statsigGlobal.instances) !== null && _b !== void 0 ? _b : {};
40
+ const inst = this;
41
+ if (instances[sdkKey] != null) {
42
+ Log_1.Log.warn('Creating multiple Statsig clients with the same SDK key can lead to unexpected behavior. Multi-instance support requires different SDK keys.');
43
+ }
44
+ instances[sdkKey] = inst;
45
+ statsigGlobal.lastInstance = inst;
46
+ statsigGlobal.instances = instances;
47
+ __STATSIG__ = statsigGlobal;
44
48
  }
45
- instances[sdkKey] = inst;
46
- statsigGlobal.lastInstance = inst;
47
- statsigGlobal.instances = instances;
48
- __STATSIG__ = statsigGlobal;
49
49
  this.dataAdapter = adapter;
50
50
  this.dataAdapter.attach(sdkKey, options);
51
+ this._primeReadyRipcord();
51
52
  }
52
53
  /**
53
54
  * Updates runtime configuration options for the SDK, allowing toggling of certain behaviors such as logging and storage to comply with user preferences or regulations such as GDPR.
@@ -6,6 +6,7 @@ export type DataAdapterResult = {
6
6
  readonly source: DataSource;
7
7
  readonly data: string;
8
8
  readonly receivedAt: number;
9
+ readonly stableID: string | null;
9
10
  };
10
11
  export type DataAdapterAsyncOptions = {
11
12
  /**
@@ -1,4 +1,4 @@
1
- export declare const SDK_VERSION = "0.0.1-beta.40";
1
+ export declare const SDK_VERSION = "0.0.1-beta.42";
2
2
  export type StatsigMetadata = {
3
3
  readonly [key: string]: string | undefined;
4
4
  readonly appVersion?: string;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StatsigMetadataProvider = exports.SDK_VERSION = void 0;
4
- exports.SDK_VERSION = '0.0.1-beta.40';
4
+ exports.SDK_VERSION = '0.0.1-beta.42';
5
5
  let metadata = {
6
6
  sdkVersion: exports.SDK_VERSION,
7
7
  sdkType: 'js-mono', // js-mono is overwritten by Precomp and OnDevice clients
@@ -26,12 +26,6 @@ export type NetworkConfigCommon = {
26
26
  * default: `https://featuregates.org/v1/initialize`
27
27
  */
28
28
  logEventUrl?: string;
29
- /**
30
- * The URL used to flush queued events via {@link window.navigator.sendBeacon} (web only). Takes precedence over {@link StatsigOptionsCommon.api}.
31
- *
32
- * default: `https://featuregates.org/v1/initialize`
33
- */
34
- logEventBeaconUrl?: string;
35
29
  /**
36
30
  * The maximum amount of time (in milliseconds) that any network request can take
37
31
  * before timing out.
@@ -69,10 +63,6 @@ export type StatsigOptionsCommon<NetworkConfig extends NetworkConfigCommon> = St
69
63
  * in the same session.
70
64
  */
71
65
  environment?: StatsigEnvironment;
72
- /**
73
- * Overrides the auto-generated StableID that is set for the device.
74
- */
75
- overrideStableID?: string;
76
66
  /**
77
67
  * How much information is allowed to be printed to the console.
78
68
  *
@@ -2,7 +2,10 @@ import type { StatsigEnvironment } from './StatsigOptionsCommon';
2
2
  type StatsigUserPrimitives = string | number | boolean | Array<string> | undefined;
3
3
  export type StatsigUser = {
4
4
  userID?: string;
5
- customIDs?: Record<string, string>;
5
+ customIDs?: {
6
+ [key: string]: string | undefined;
7
+ stableID?: string;
8
+ };
6
9
  email?: string;
7
10
  ip?: string;
8
11
  userAgent?: string;
@@ -15,7 +18,6 @@ export type StatsigUser = {
15
18
  export type StatsigUserInternal = StatsigUser & {
16
19
  statsigEnvironment?: StatsigEnvironment;
17
20
  };
18
- export declare function normalizeUser(original: StatsigUser, environment?: StatsigEnvironment): StatsigUser;
19
- export declare function getUserStorageKey(sdkKey: string, user?: StatsigUser): string;
21
+ export declare function _normalizeUser(original: StatsigUser, environment?: StatsigEnvironment): StatsigUser;
20
22
  export declare function getUnitIDFromUser(user: StatsigUser, idType: string): string | undefined;
21
23
  export {};
@@ -1,8 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getUnitIDFromUser = exports.getUserStorageKey = exports.normalizeUser = void 0;
4
- const Hashing_1 = require("./Hashing");
5
- function normalizeUser(original, environment) {
3
+ exports.getUnitIDFromUser = exports._normalizeUser = void 0;
4
+ function _normalizeUser(original, environment) {
6
5
  try {
7
6
  const copy = JSON.parse(JSON.stringify(original));
8
7
  if (environment != null) {
@@ -14,11 +13,7 @@ function normalizeUser(original, environment) {
14
13
  throw new Error('User object must be convertable to JSON string.');
15
14
  }
16
15
  }
17
- exports.normalizeUser = normalizeUser;
18
- function getUserStorageKey(sdkKey, user) {
19
- return (0, Hashing_1.DJB2Object)({ sdkKey, user });
20
- }
21
- exports.getUserStorageKey = getUserStorageKey;
16
+ exports._normalizeUser = _normalizeUser;
22
17
  function getUnitIDFromUser(user, idType) {
23
18
  var _a, _b, _c;
24
19
  if (typeof idType === 'string' && idType.toLowerCase() !== 'userid') {
@@ -11,53 +11,30 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports._setObjectInStorage = exports._getObjectFromStorage = exports.Storage = void 0;
13
13
  const Log_1 = require("./Log");
14
+ const SafeJs_1 = require("./SafeJs");
14
15
  const inMemoryStore = {};
15
16
  const _resolve = (input) => Promise.resolve(input);
16
17
  const _inMemoryProvider = {
17
18
  _getProviderName: () => 'InMemory',
18
- _getItemSync(key) {
19
- var _a;
20
- return (_a = inMemoryStore[key]) !== null && _a !== void 0 ? _a : null;
21
- },
22
- _getItem(key) {
23
- var _a;
24
- return _resolve((_a = inMemoryStore[key]) !== null && _a !== void 0 ? _a : null);
25
- },
26
- _setItem(key, value) {
27
- inMemoryStore[key] = value;
28
- return _resolve();
29
- },
30
- _removeItem(key) {
31
- delete inMemoryStore[key];
32
- return _resolve();
33
- },
34
- _getAllKeys() {
35
- return _resolve(Object.keys(inMemoryStore));
36
- },
19
+ _getItemSync: (key) => inMemoryStore[key] ? inMemoryStore[key] : null,
20
+ _getItem: (key) => _resolve(inMemoryStore[key] ? inMemoryStore[key] : null),
21
+ _setItem: (key, value) => ((inMemoryStore[key] = value), _resolve()),
22
+ _removeItem: (key) => (delete inMemoryStore[key], _resolve()),
23
+ _getAllKeys: () => _resolve(Object.keys(inMemoryStore)),
37
24
  };
38
25
  let _localStorageProvider = null;
39
26
  try {
40
- if (typeof window !== 'undefined' && 'localStorage' in window) {
27
+ const win = (0, SafeJs_1._getWindowSafe)();
28
+ if (win &&
29
+ win.localStorage &&
30
+ typeof win.localStorage.getItem === 'function') {
41
31
  _localStorageProvider = {
42
32
  _getProviderName: () => 'LocalStorage',
43
- _getItemSync(key) {
44
- return localStorage.getItem(key);
45
- },
46
- _getItem(key) {
47
- return _resolve(localStorage.getItem(key));
48
- },
49
- _setItem(key, value) {
50
- localStorage.setItem(key, value);
51
- return _resolve();
52
- },
53
- _removeItem(key) {
54
- localStorage.removeItem(key);
55
- return _resolve();
56
- },
57
- _getAllKeys() {
58
- const keys = Object.keys(localStorage);
59
- return _resolve(keys);
60
- },
33
+ _getItemSync: (key) => win.localStorage.getItem(key),
34
+ _getItem: (key) => _resolve(win.localStorage.getItem(key)),
35
+ _setItem: (key, value) => (win.localStorage.setItem(key, value), _resolve()),
36
+ _removeItem: (key) => (win.localStorage.removeItem(key), _resolve()),
37
+ _getAllKeys: () => _resolve(Object.keys(win.localStorage)),
61
38
  };
62
39
  }
63
40
  }
@@ -81,7 +58,7 @@ function _inMemoryBreaker(get) {
81
58
  exports.Storage = {
82
59
  _getProviderName: () => _current._getProviderName(),
83
60
  _getItem: (key) => __awaiter(void 0, void 0, void 0, function* () { return _inMemoryBreaker(() => _current._getItem(key)); }),
84
- _getItemSync: (key) => _inMemoryBreaker(() => { var _a, _b; return (_b = (_a = _current._getItemSync) === null || _a === void 0 ? void 0 : _a.call(_current, key)) !== null && _b !== void 0 ? _b : null; }),
61
+ _getItemSync: (key) => _inMemoryBreaker(() => _current._getItemSync ? _current._getItemSync(key) : null),
85
62
  _setItem: (key, value) => _current._setItem(key, value),
86
63
  _removeItem: (key) => _current._removeItem(key),
87
64
  _getAllKeys: () => _current._getAllKeys(),
@@ -5,4 +5,4 @@
5
5
  * @param {string} error An error to print via Log.error() when parsing fails
6
6
  * @returns {T | null} The parse object T or null if it failed
7
7
  */
8
- export declare function typedJsonParse<T>(data: string, guard: string, error: string): T | null;
8
+ export declare function _typedJsonParse<T>(data: string, guard: string, typeName: string): T | null;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.typedJsonParse = void 0;
3
+ exports._typedJsonParse = void 0;
4
4
  const Log_1 = require("./Log");
5
5
  /**
6
6
  *
@@ -9,7 +9,7 @@ const Log_1 = require("./Log");
9
9
  * @param {string} error An error to print via Log.error() when parsing fails
10
10
  * @returns {T | null} The parse object T or null if it failed
11
11
  */
12
- function typedJsonParse(data, guard, error) {
12
+ function _typedJsonParse(data, guard, typeName) {
13
13
  try {
14
14
  const result = JSON.parse(data);
15
15
  if (typeof result === 'object' &&
@@ -21,7 +21,7 @@ function typedJsonParse(data, guard, error) {
21
21
  catch (_a) {
22
22
  // noop
23
23
  }
24
- Log_1.Log.error(error);
24
+ Log_1.Log.error(`Failed to parse ${typeName}`);
25
25
  return null;
26
26
  }
27
- exports.typedJsonParse = typedJsonParse;
27
+ exports._typedJsonParse = _typedJsonParse;
@@ -3,6 +3,7 @@ declare const BACKGROUND = "background";
3
3
  export type Visibility = typeof FOREGROUND | typeof BACKGROUND;
4
4
  type VisibilityChangedCallback = (visibility: Visibility) => void;
5
5
  export declare const _isCurrentlyVisible: () => boolean;
6
+ export declare const _isUnloading: () => boolean;
6
7
  export declare const _subscribeToVisiblityChanged: (listener: VisibilityChangedCallback) => void;
7
8
  export declare const _notifyVisibilityChanged: (visibility: Visibility) => void;
8
9
  export {};
@@ -1,15 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._notifyVisibilityChanged = exports._subscribeToVisiblityChanged = exports._isCurrentlyVisible = void 0;
3
+ exports._notifyVisibilityChanged = exports._subscribeToVisiblityChanged = exports._isUnloading = exports._isCurrentlyVisible = void 0;
4
4
  const SafeJs_1 = require("./SafeJs");
5
5
  const FOREGROUND = 'foreground';
6
6
  const BACKGROUND = 'background';
7
7
  const LISTENERS = [];
8
8
  let current = FOREGROUND;
9
+ let isUnloading = false;
9
10
  const _isCurrentlyVisible = () => {
10
11
  return current === FOREGROUND;
11
12
  };
12
13
  exports._isCurrentlyVisible = _isCurrentlyVisible;
14
+ const _isUnloading = () => isUnloading;
15
+ exports._isUnloading = _isUnloading;
13
16
  const _subscribeToVisiblityChanged = (listener) => {
14
17
  LISTENERS.unshift(listener);
15
18
  };
@@ -22,9 +25,15 @@ const _notifyVisibilityChanged = (visibility) => {
22
25
  LISTENERS.forEach((l) => l(visibility));
23
26
  };
24
27
  exports._notifyVisibilityChanged = _notifyVisibilityChanged;
25
- (0, SafeJs_1._addWindowEventListenerSafe)('focus', () => (0, exports._notifyVisibilityChanged)(FOREGROUND));
28
+ (0, SafeJs_1._addWindowEventListenerSafe)('focus', () => {
29
+ isUnloading = false;
30
+ (0, exports._notifyVisibilityChanged)(FOREGROUND);
31
+ });
26
32
  (0, SafeJs_1._addWindowEventListenerSafe)('blur', () => (0, exports._notifyVisibilityChanged)(BACKGROUND));
27
- (0, SafeJs_1._addWindowEventListenerSafe)('beforeunload', () => (0, exports._notifyVisibilityChanged)(BACKGROUND));
33
+ (0, SafeJs_1._addWindowEventListenerSafe)('beforeunload', () => {
34
+ isUnloading = true;
35
+ (0, exports._notifyVisibilityChanged)(BACKGROUND);
36
+ });
28
37
  (0, SafeJs_1._addDocumentEventListenerSafe)('visibilitychange', () => {
29
38
  (0, exports._notifyVisibilityChanged)(document.visibilityState === 'visible' ? FOREGROUND : BACKGROUND);
30
39
  });
package/src/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import { EventLogger } from './EventLogger';
4
4
  import { Log } from './Log';
5
5
  import { Storage } from './StorageProvider';
6
6
  export * from './$_StatsigGlobal';
7
+ export * from './CacheKey';
7
8
  export * from './ClientInterfaces';
8
9
  export * from './DataAdapterCore';
9
10
  export * from './DownloadConfigSpecsResponse';
package/src/index.js CHANGED
@@ -24,8 +24,8 @@ Object.defineProperty(exports, "Log", { enumerable: true, get: function () { ret
24
24
  const StatsigMetadata_1 = require("./StatsigMetadata");
25
25
  const StorageProvider_1 = require("./StorageProvider");
26
26
  Object.defineProperty(exports, "Storage", { enumerable: true, get: function () { return StorageProvider_1.Storage; } });
27
- const UUID_1 = require("./UUID");
28
27
  __exportStar(require("./$_StatsigGlobal"), exports);
28
+ __exportStar(require("./CacheKey"), exports);
29
29
  __exportStar(require("./ClientInterfaces"), exports);
30
30
  __exportStar(require("./DataAdapterCore"), exports);
31
31
  __exportStar(require("./DownloadConfigSpecsResponse"), exports);
@@ -57,8 +57,5 @@ __exportStar(require("./UrlOverrides"), exports);
57
57
  __exportStar(require("./UtitlityTypes"), exports);
58
58
  __exportStar(require("./UUID"), exports);
59
59
  __exportStar(require("./VisibilityObserving"), exports);
60
- __STATSIG__ = Object.assign(Object.assign({}, (__STATSIG__ !== null && __STATSIG__ !== void 0 ? __STATSIG__ : {})), { EventLogger: EventLogger_1.EventLogger,
61
- Log: Log_1.Log,
62
- getUUID: UUID_1.getUUID,
63
- Storage: StorageProvider_1.Storage,
60
+ __STATSIG__ = Object.assign(Object.assign({}, (__STATSIG__ !== null && __STATSIG__ !== void 0 ? __STATSIG__ : {})), { Log: Log_1.Log,
64
61
  SDK_VERSION: StatsigMetadata_1.SDK_VERSION });