@statsig/client-core 0.0.1-beta.32 → 0.0.1-beta.34

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.32",
3
+ "version": "0.0.1-beta.34",
4
4
  "dependencies": {},
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -1,4 +1,5 @@
1
1
  import { DownloadConfigSpecsResponse } from './DownloadConfigSpecsResponse';
2
+ import { ErrorBoundary } from './ErrorBoundary';
2
3
  import { DynamicConfigEvaluationOptions, ExperimentEvaluationOptions, FeatureGateEvaluationOptions, LayerEvaluationOptions } from './EvaluationOptions';
3
4
  import { InitializeResponseWithUpdates } from './InitializeResponse';
4
5
  import { StatsigClientEventEmitterInterface } from './StatsigClientEventEmitter';
@@ -18,6 +19,7 @@ export interface StatsigClientCommonInterface extends StatsigClientEventEmitterI
18
19
  export type CommonContext = {
19
20
  sdkKey: string;
20
21
  options: AnyStatsigOptions;
22
+ errorBoundary: ErrorBoundary;
21
23
  };
22
24
  export type AsyncCommonContext = {
23
25
  sessionID: string;
@@ -12,7 +12,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.DataAdapterCore = void 0;
13
13
  const ErrorBoundary_1 = require("./ErrorBoundary");
14
14
  const Log_1 = require("./Log");
15
- const Monitoring_1 = require("./Monitoring");
16
15
  const StatsigDataAdapter_1 = require("./StatsigDataAdapter");
17
16
  const StatsigUser_1 = require("./StatsigUser");
18
17
  const StorageProvider_1 = require("./StorageProvider");
@@ -30,7 +29,6 @@ class DataAdapterCore {
30
29
  attach(sdkKey, _options) {
31
30
  this._sdkKey = sdkKey;
32
31
  this._errorBoundary = new ErrorBoundary_1.ErrorBoundary(sdkKey);
33
- (0, Monitoring_1.monitorClass)(this._errorBoundary, this);
34
32
  }
35
33
  getDataSync(user) {
36
34
  const cacheKey = this._getCacheKey(user);
@@ -1,5 +1,3 @@
1
- export declare function captureDiagnostics(func: string, task: () => unknown): unknown;
2
1
  export declare abstract class Diagnostics {
3
- static mark(tag: string, metadata?: Record<string, unknown>): void;
4
- static flush(): void;
2
+ static mark(): void;
5
3
  }
@@ -1,52 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Diagnostics = exports.captureDiagnostics = void 0;
4
- const Log_1 = require("./Log");
5
- const NetworkDefaults_1 = require("./NetworkDefaults");
6
- const SUPPORTS_PERFORMANCE_API = typeof performance !== 'undefined' && typeof performance.mark !== 'undefined';
7
- let markers = [];
8
- function captureDiagnostics(func, task) {
9
- Diagnostics.mark(`${func}:start`);
10
- const result = task();
11
- const markEnd = () => {
12
- Diagnostics.mark(`${func}:end`);
13
- maybeFlush(`${func}:end`);
14
- };
15
- if (result && result instanceof Promise) {
16
- return result.finally(() => markEnd());
17
- }
18
- else {
19
- markEnd();
20
- }
21
- return result;
22
- }
23
- exports.captureDiagnostics = captureDiagnostics;
3
+ exports.Diagnostics = void 0;
24
4
  class Diagnostics {
25
- static mark(tag, metadata) {
26
- if (!SUPPORTS_PERFORMANCE_API) {
27
- return;
28
- }
29
- const marker = performance.mark(tag, { detail: metadata });
30
- markers.push(marker);
31
- }
32
- static flush() {
33
- const resources = performance
34
- .getEntriesByType('resource')
35
- .filter((resource) => resource.name.startsWith(NetworkDefaults_1.NetworkDefault.initializeApi) ||
36
- resource.name.startsWith(NetworkDefaults_1.NetworkDefault.specsApi) ||
37
- resource.name.startsWith(NetworkDefaults_1.NetworkDefault.eventsApi));
38
- const payload = {
39
- markers,
40
- resources,
41
- };
42
- // TODO: Send as log to Statsig
43
- Log_1.Log.debug('Diagnostics', payload, JSON.stringify(payload));
44
- markers = [];
5
+ static mark() {
6
+ //
45
7
  }
46
8
  }
47
9
  exports.Diagnostics = Diagnostics;
48
- function maybeFlush(tag) {
49
- if (tag.startsWith('initialize:')) {
50
- Diagnostics.flush();
51
- }
52
- }
@@ -2,9 +2,11 @@ import { StatsigClientEmitEventFunc } from './StatsigClientBase';
2
2
  export declare const EXCEPTION_ENDPOINT = "https://statsigapi.net/v1/sdk_exception";
3
3
  export declare class ErrorBoundary {
4
4
  private _sdkKey;
5
+ private _emitter?;
5
6
  private _seen;
6
- constructor(_sdkKey: string);
7
- capture(tag: string, task: () => unknown, emitter?: StatsigClientEmitEventFunc): unknown;
7
+ constructor(_sdkKey: string, _emitter?: StatsigClientEmitEventFunc | undefined);
8
+ wrap(instance: unknown): void;
9
+ capture(tag: string, task: () => unknown): unknown;
8
10
  logError(tag: string, error: unknown): void;
9
11
  private _onError;
10
12
  }
@@ -15,30 +15,50 @@ const SDKType_1 = require("./SDKType");
15
15
  const StatsigMetadata_1 = require("./StatsigMetadata");
16
16
  exports.EXCEPTION_ENDPOINT = 'https://statsigapi.net/v1/sdk_exception';
17
17
  class ErrorBoundary {
18
- constructor(_sdkKey) {
18
+ constructor(_sdkKey, _emitter) {
19
19
  this._sdkKey = _sdkKey;
20
+ this._emitter = _emitter;
20
21
  this._seen = new Set();
21
22
  }
22
- capture(tag, task, emitter) {
23
+ wrap(instance) {
24
+ try {
25
+ const obj = instance;
26
+ _getAllInstanceMethodNames(obj).forEach((name) => {
27
+ const original = obj[name];
28
+ if ('$EB' in original) {
29
+ return;
30
+ }
31
+ obj[name] = (...args) => {
32
+ return this.capture(name, () => original.apply(instance, args));
33
+ };
34
+ obj[name].$EB = true;
35
+ });
36
+ }
37
+ catch (err) {
38
+ this._onError('eb:wrap', err);
39
+ }
40
+ }
41
+ capture(tag, task) {
23
42
  try {
24
43
  const res = task();
25
44
  if (res && res instanceof Promise) {
26
- return res.catch((err) => this._onError(tag, err, emitter));
45
+ return res.catch((err) => this._onError(tag, err));
27
46
  }
28
47
  return res;
29
48
  }
30
49
  catch (error) {
31
- this._onError(tag, error, emitter);
50
+ this._onError(tag, error);
32
51
  return null;
33
52
  }
34
53
  }
35
54
  logError(tag, error) {
36
55
  this._onError(tag, error);
37
56
  }
38
- _onError(tag, error, emitter) {
57
+ _onError(tag, error) {
39
58
  try {
40
59
  Log_1.Log.warn(`Caught error in ${tag}`, { error });
41
60
  const impl = () => __awaiter(this, void 0, void 0, function* () {
61
+ var _a;
42
62
  const unwrapped = (error !== null && error !== void 0 ? error : Error('[Statsig] Error was empty'));
43
63
  const isError = unwrapped instanceof Error;
44
64
  const name = isError ? unwrapped.name : 'No Name';
@@ -60,7 +80,7 @@ class ErrorBoundary {
60
80
  },
61
81
  body,
62
82
  });
63
- emitter === null || emitter === void 0 ? void 0 : emitter({ name: 'error', error });
83
+ (_a = this._emitter) === null || _a === void 0 ? void 0 : _a.call(this, { name: 'error', error });
64
84
  });
65
85
  impl()
66
86
  .then(() => {
@@ -84,3 +104,14 @@ function _getDescription(obj) {
84
104
  return '[Statsig] Failed to get string for error.';
85
105
  }
86
106
  }
107
+ function _getAllInstanceMethodNames(instance) {
108
+ const names = new Set();
109
+ let proto = Object.getPrototypeOf(instance);
110
+ while (proto && proto !== Object.prototype) {
111
+ Object.getOwnPropertyNames(proto)
112
+ .filter((prop) => typeof (proto === null || proto === void 0 ? void 0 : proto[prop]) === 'function')
113
+ .forEach((name) => names.add(name));
114
+ proto = Object.getPrototypeOf(proto);
115
+ }
116
+ return Array.from(names);
117
+ }
@@ -24,14 +24,12 @@ export declare class EventLogger {
24
24
  reset(): void;
25
25
  shutdown(): Promise<void>;
26
26
  flush(): Promise<void>;
27
- private _onVisibilityChanged;
28
27
  /**
29
28
  * We 'Quick Flush' following the very first event enqueued
30
29
  * within the quick flush window
31
30
  */
32
31
  private _quickFlushIfNeeded;
33
32
  private _shouldLogEvent;
34
- private _flushAndForget;
35
33
  private _sendEvents;
36
34
  private _sendEventsViaPost;
37
35
  private _sendEventsViaBeacon;
@@ -26,6 +26,13 @@ const MAX_DEDUPER_KEYS = 1000;
26
26
  const DEDUPER_WINDOW_DURATION_MS = 60000;
27
27
  const MAX_FAILED_LOGS = 500;
28
28
  const QUICK_FLUSH_WINDOW_MS = 200;
29
+ const EVENT_LOGGER_MAP = {};
30
+ const _safeFlushAndForget = (sdkKey) => {
31
+ var _a;
32
+ (_a = EVENT_LOGGER_MAP[sdkKey]) === null || _a === void 0 ? void 0 : _a.flush().catch(() => {
33
+ // noop
34
+ });
35
+ };
29
36
  class EventLogger {
30
37
  constructor(_sdkKey, _emitter, _network, _options) {
31
38
  var _a, _b;
@@ -38,14 +45,27 @@ class EventLogger {
38
45
  this._nonExposedChecks = {};
39
46
  this._hasRunQuickFlush = false;
40
47
  this._creationTime = Date.now();
48
+ EVENT_LOGGER_MAP[_sdkKey] = this;
41
49
  this._isLoggingDisabled = (_options === null || _options === void 0 ? void 0 : _options.disableLogging) === true;
42
50
  this._maxQueueSize = (_a = _options === null || _options === void 0 ? void 0 : _options.loggingBufferMaxSize) !== null && _a !== void 0 ? _a : DEFAULT_QUEUE_SIZE;
43
51
  const flushInterval = (_b = _options === null || _options === void 0 ? void 0 : _options.loggingIntervalMs) !== null && _b !== void 0 ? _b : DEFAULT_FLUSH_INTERVAL_MS;
44
- this._flushTimer = setInterval(() => this._flushAndForget(), flushInterval);
52
+ const intervalId = setInterval(() => {
53
+ const logger = EVENT_LOGGER_MAP[_sdkKey];
54
+ if (!logger) {
55
+ clearInterval(intervalId);
56
+ return;
57
+ }
58
+ _safeFlushAndForget(_sdkKey);
59
+ }, flushInterval);
60
+ this._flushTimer = intervalId;
45
61
  const config = _options === null || _options === void 0 ? void 0 : _options.networkConfig;
46
62
  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', NetworkDefaults_1.NetworkDefault.eventsApi);
47
63
  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', NetworkDefaults_1.NetworkDefault.eventsApi);
48
- (0, VisibilityObserving_1._subscribeToVisiblityChanged)(this._onVisibilityChanged.bind(this));
64
+ (0, VisibilityObserving_1._subscribeToVisiblityChanged)((visibility) => {
65
+ if (visibility === 'background') {
66
+ _safeFlushAndForget(_sdkKey);
67
+ }
68
+ });
49
69
  this._retryFailedLogs();
50
70
  }
51
71
  setLoggingDisabled(isDisabled) {
@@ -58,7 +78,7 @@ class EventLogger {
58
78
  this._normalizeAndAppendEvent(event);
59
79
  this._quickFlushIfNeeded();
60
80
  if (this._queue.length > this._maxQueueSize) {
61
- this._flushAndForget();
81
+ _safeFlushAndForget(this._sdkKey);
62
82
  }
63
83
  }
64
84
  incrementNonExposureCount(name) {
@@ -89,11 +109,6 @@ class EventLogger {
89
109
  yield this._sendEvents(events);
90
110
  });
91
111
  }
92
- _onVisibilityChanged(visibility) {
93
- if (visibility === 'background') {
94
- this._flushAndForget();
95
- }
96
- }
97
112
  /**
98
113
  * We 'Quick Flush' following the very first event enqueued
99
114
  * within the quick flush window
@@ -106,7 +121,7 @@ class EventLogger {
106
121
  if (Date.now() - this._creationTime > QUICK_FLUSH_WINDOW_MS) {
107
122
  return;
108
123
  }
109
- setTimeout(() => this._flushAndForget(), QUICK_FLUSH_WINDOW_MS);
124
+ setTimeout(() => _safeFlushAndForget(this._sdkKey), QUICK_FLUSH_WINDOW_MS);
110
125
  }
111
126
  _shouldLogEvent(event) {
112
127
  var _a, _b, _c, _d;
@@ -131,11 +146,6 @@ class EventLogger {
131
146
  this._lastExposureTimeMap[key] = now;
132
147
  return true;
133
148
  }
134
- _flushAndForget() {
135
- this.flush().catch(() => {
136
- // noop
137
- });
138
- }
139
149
  _sendEvents(events) {
140
150
  return __awaiter(this, void 0, void 0, function* () {
141
151
  if (this._isLoggingDisabled) {
@@ -76,10 +76,7 @@ class NetworkCore {
76
76
  throw err;
77
77
  }
78
78
  const text = yield response.text();
79
- Diagnostics_1.Diagnostics.mark('_sendRequest:response-received', {
80
- status: response.status,
81
- contentLength: response.headers.get('content-length'),
82
- });
79
+ Diagnostics_1.Diagnostics.mark();
83
80
  return {
84
81
  body: text,
85
82
  code: response.status,
@@ -87,11 +84,7 @@ class NetworkCore {
87
84
  }
88
85
  catch (error) {
89
86
  const errorMessage = _getErrorMessage(controller, error);
90
- Diagnostics_1.Diagnostics.mark('_sendRequest:error', {
91
- error: errorMessage,
92
- status: response === null || response === void 0 ? void 0 : response.status,
93
- contentLength: response === null || response === void 0 ? void 0 : response.headers.get('content-length'),
94
- });
87
+ Diagnostics_1.Diagnostics.mark();
95
88
  if (!retries || retries <= 0) {
96
89
  (_a = this._emitter) === null || _a === void 0 ? void 0 : _a.call(this, { name: 'error', error });
97
90
  Log_1.Log.error(`A networking error occured during ${method} request to ${url}.`, errorMessage, error);
@@ -21,27 +21,31 @@ const StableID_1 = require("./StableID");
21
21
  const StorageProvider_1 = require("./StorageProvider");
22
22
  class StatsigClientBase {
23
23
  constructor(sdkKey, adapter, network, options) {
24
- var _a, _b, _c;
24
+ var _a, _b;
25
25
  this.loadingStatus = 'Uninitialized';
26
26
  this._listeners = {};
27
- this._sdkKey = sdkKey;
28
- this._options = options !== null && options !== void 0 ? options : {};
27
+ const emitter = this._emit.bind(this);
28
+ const statsigGlobal = (0, __StatsigGlobal_1._getStatsigGlobal)();
29
+ const instances = (_a = statsigGlobal.instances) !== null && _a !== void 0 ? _a : {};
30
+ const inst = this;
31
+ (options === null || options === void 0 ? void 0 : options.logLevel) && (Log_1.Log.level = options === null || options === void 0 ? void 0 : options.logLevel);
29
32
  (options === null || options === void 0 ? void 0 : options.disableStorage) && StorageProvider_1.Storage._setDisabled(true);
30
33
  (options === null || options === void 0 ? void 0 : options.overrideStableID) &&
31
34
  StableID_1.StableID.setOverride(options.overrideStableID, sdkKey);
32
- Log_1.Log.level = (_a = options === null || options === void 0 ? void 0 : options.logLevel) !== null && _a !== void 0 ? _a : Log_1.LogLevel.Warn;
35
+ this._errorBoundary = new ErrorBoundary_1.ErrorBoundary(sdkKey, emitter);
36
+ this._errorBoundary.wrap(this);
37
+ this._errorBoundary.wrap(network);
38
+ this._errorBoundary.wrap(adapter);
39
+ this._sdkKey = sdkKey;
40
+ this._options = options !== null && options !== void 0 ? options : {};
33
41
  this._overrideAdapter = (_b = options === null || options === void 0 ? void 0 : options.overrideAdapter) !== null && _b !== void 0 ? _b : null;
34
- this._logger = new EventLogger_1.EventLogger(sdkKey, this._emit.bind(this), network, options);
42
+ this._logger = new EventLogger_1.EventLogger(sdkKey, emitter, network, options);
35
43
  SessionID_1.SessionID._setEmitFunction(() => {
36
44
  this._emit({ name: 'session_expired' });
37
45
  }, sdkKey);
38
- this._errorBoundary = new ErrorBoundary_1.ErrorBoundary(sdkKey);
39
- const statsigGlobal = (0, __StatsigGlobal_1._getStatsigGlobal)();
40
- const instances = (_c = statsigGlobal.instances) !== null && _c !== void 0 ? _c : {};
41
46
  if (instances[sdkKey] != null && (0, SafeJs_1._isBrowserEnv)()) {
42
47
  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
48
  }
44
- const inst = this;
45
49
  instances[sdkKey] = inst;
46
50
  statsigGlobal.lastInstance = inst;
47
51
  statsigGlobal.instances = instances;
@@ -1,4 +1,4 @@
1
- export declare const SDK_VERSION = "0.0.1-beta.32";
1
+ export declare const SDK_VERSION = "0.0.1-beta.34";
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.32';
4
+ exports.SDK_VERSION = '0.0.1-beta.34';
5
5
  let metadata = {
6
6
  sdkVersion: exports.SDK_VERSION,
7
7
  sdkType: 'js-mono', // js-mono is overwritten by Precomp and OnDevice clients
package/src/index.d.ts CHANGED
@@ -13,7 +13,6 @@ export * from './EvaluationTypes';
13
13
  export * from './Hashing';
14
14
  export * from './InitializeResponse';
15
15
  export * from './Log';
16
- export * from './Monitoring';
17
16
  export * from './NetworkParams';
18
17
  export * from './NetworkCore';
19
18
  export * from './NetworkDefaults';
package/src/index.js CHANGED
@@ -35,7 +35,6 @@ __exportStar(require("./EvaluationTypes"), exports);
35
35
  __exportStar(require("./Hashing"), exports);
36
36
  __exportStar(require("./InitializeResponse"), exports);
37
37
  __exportStar(require("./Log"), exports);
38
- __exportStar(require("./Monitoring"), exports);
39
38
  __exportStar(require("./NetworkParams"), exports);
40
39
  __exportStar(require("./NetworkCore"), exports);
41
40
  __exportStar(require("./NetworkDefaults"), exports);
@@ -1,2 +0,0 @@
1
- import { ErrorBoundary } from './ErrorBoundary';
2
- export declare function monitorClass(errorBoundary: ErrorBoundary, instance: object): void;
package/src/Monitoring.js DELETED
@@ -1,74 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.monitorClass = void 0;
4
- const Diagnostics_1 = require("./Diagnostics");
5
- const StatsigClientBase_1 = require("./StatsigClientBase");
6
- function monitorClass(errorBoundary, instance) {
7
- try {
8
- _monitorClassImpl(errorBoundary, instance);
9
- }
10
- catch (error) {
11
- errorBoundary.logError('monitorClass', error);
12
- }
13
- }
14
- exports.monitorClass = monitorClass;
15
- function _monitorFunction(errorBoundary, tag, func, instance) {
16
- const emitFunc = instance instanceof StatsigClientBase_1.StatsigClientBase
17
- ? instance['_emit'].bind(instance)
18
- : undefined;
19
- return errorBoundary.capture(tag, () => (0, Diagnostics_1.captureDiagnostics)(tag, () => func.apply(instance)), emitFunc);
20
- }
21
- function _getProtoSafe(instance) {
22
- if (typeof instance === 'object') {
23
- const proto = Object.getPrototypeOf(instance);
24
- return proto && typeof proto === 'object'
25
- ? proto
26
- : null;
27
- }
28
- return null;
29
- }
30
- function _getAllInstanceMethodNames(instance) {
31
- const names = new Set();
32
- let proto = _getProtoSafe(instance);
33
- while (proto && proto !== Object.prototype) {
34
- Object.getOwnPropertyNames(proto)
35
- .filter((prop) => typeof (proto === null || proto === void 0 ? void 0 : proto[prop]) === 'function')
36
- .forEach((name) => names.add(name));
37
- proto = Object.getPrototypeOf(proto);
38
- }
39
- return Array.from(names);
40
- }
41
- function _getAllStaticMethodNames(instance) {
42
- const names = new Set();
43
- const proto = _getProtoSafe(instance);
44
- Object.getOwnPropertyNames((proto === null || proto === void 0 ? void 0 : proto.constructor) || {})
45
- .filter((prop) => {
46
- var _a;
47
- if (prop === 'caller' || prop === 'arguments' || prop === 'callee') {
48
- return false;
49
- }
50
- return (typeof ((_a = proto === null || proto === void 0 ? void 0 : proto.constructor) === null || _a === void 0 ? void 0 : _a[prop]) === 'function');
51
- })
52
- .forEach((name) => names.add(name));
53
- return Array.from(names);
54
- }
55
- function _monitorClassImpl(errorBoundary, instance) {
56
- var _a;
57
- const obj = instance;
58
- for (const method of _getAllInstanceMethodNames(obj)) {
59
- if (method === 'constructor') {
60
- continue;
61
- }
62
- const original = obj[method];
63
- obj[method] = function (...args) {
64
- return _monitorFunction(errorBoundary, method, () => original.apply(this, args), instance);
65
- };
66
- }
67
- for (const method of _getAllStaticMethodNames(obj)) {
68
- const original = (_a = obj === null || obj === void 0 ? void 0 : obj.constructor) === null || _a === void 0 ? void 0 : _a[method];
69
- (obj === null || obj === void 0 ? void 0 : obj.constructor)[method] =
70
- function (...args) {
71
- return _monitorFunction(errorBoundary, `${obj.constructor.name}.${method}`, () => original.apply(obj.constructor, args), instance);
72
- };
73
- }
74
- }