wolves-js-client 1.0.5 → 1.0.7

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.
@@ -5,10 +5,29 @@ export declare class EventLogger {
5
5
  private flushIntervalId;
6
6
  private network;
7
7
  private maxQueueSize;
8
- constructor(network: Network);
8
+ private sdkKey;
9
+ constructor(sdkKey: string, network: Network);
10
+ /**
11
+ * Safely flush events for a specific sdkKey without throwing.
12
+ * This is used internally and can be called even after client destruction.
13
+ */
14
+ private static safeFlushAndForget;
9
15
  enqueue(event: WolvesEvent): void;
10
16
  flush(): Promise<void>;
17
+ /**
18
+ * Start the event logger and register it in the global map.
19
+ * This should be called when the client is initialized.
20
+ * If an existing logger with the same sdkKey exists, it will be flushed first.
21
+ */
11
22
  start(): void;
23
+ /**
24
+ * Reset the event queue. Any pending events will be lost.
25
+ * Called when switching users to clear any user-specific events.
26
+ */
12
27
  reset(): void;
28
+ /**
29
+ * Stop the event logger, flush all pending events, and remove from global map.
30
+ * This ensures all events are sent before the client is destroyed.
31
+ */
13
32
  stop(): Promise<void>;
14
33
  }
@@ -13,20 +13,34 @@ exports.EventLogger = void 0;
13
13
  const Log_1 = require("./Log");
14
14
  const DEFAULT_QUEUE_SIZE = 100;
15
15
  const DEFAULT_FLUSH_INTERVAL_MS = 10000;
16
+ /**
17
+ * Global map of EventLoggers by sdkKey.
18
+ * This ensures events can still be sent even after a client is destroyed,
19
+ * as long as there are pending events in the queue.
20
+ */
21
+ const EVENT_LOGGER_MAP = {};
16
22
  class EventLogger {
17
- constructor(network) {
23
+ constructor(sdkKey, network) {
18
24
  this.queue = [];
19
25
  this.flushIntervalId = null;
20
26
  this.maxQueueSize = DEFAULT_QUEUE_SIZE;
27
+ this.sdkKey = sdkKey;
21
28
  this.network = network;
22
- this.start();
29
+ }
30
+ /**
31
+ * Safely flush events for a specific sdkKey without throwing.
32
+ * This is used internally and can be called even after client destruction.
33
+ */
34
+ static safeFlushAndForget(sdkKey) {
35
+ var _a;
36
+ (_a = EVENT_LOGGER_MAP[sdkKey]) === null || _a === void 0 ? void 0 : _a.flush().catch(() => {
37
+ // noop - errors already logged in flush
38
+ });
23
39
  }
24
40
  enqueue(event) {
25
41
  this.queue.push(event);
26
42
  if (this.queue.length >= this.maxQueueSize) {
27
- this.flush().catch((e) => {
28
- Log_1.Log.error('Failed to flush events', e);
29
- });
43
+ EventLogger.safeFlushAndForget(this.sdkKey);
30
44
  }
31
45
  }
32
46
  flush() {
@@ -50,24 +64,59 @@ class EventLogger {
50
64
  }
51
65
  });
52
66
  }
67
+ /**
68
+ * Start the event logger and register it in the global map.
69
+ * This should be called when the client is initialized.
70
+ * If an existing logger with the same sdkKey exists, it will be flushed first.
71
+ */
53
72
  start() {
54
73
  if (this.flushIntervalId) {
55
74
  return;
56
75
  }
76
+ // If there's an existing logger with the same sdkKey, flush it first
77
+ const existingLogger = EVENT_LOGGER_MAP[this.sdkKey];
78
+ if (existingLogger && existingLogger !== this) {
79
+ // Stop the old logger's interval to prevent it from continuing to flush
80
+ if (existingLogger.flushIntervalId) {
81
+ clearInterval(existingLogger.flushIntervalId);
82
+ existingLogger.flushIntervalId = null;
83
+ }
84
+ // Flush remaining events from the old logger
85
+ existingLogger.flush().catch(() => {
86
+ // noop - errors already logged in flush
87
+ });
88
+ }
89
+ // Register this logger in the global map (replaces any existing)
90
+ EVENT_LOGGER_MAP[this.sdkKey] = this;
57
91
  this.flushIntervalId = setInterval(() => {
58
- this.flush();
92
+ EventLogger.safeFlushAndForget(this.sdkKey);
59
93
  }, DEFAULT_FLUSH_INTERVAL_MS);
60
94
  }
95
+ /**
96
+ * Reset the event queue. Any pending events will be lost.
97
+ * Called when switching users to clear any user-specific events.
98
+ */
61
99
  reset() {
100
+ // Attempt to flush any remaining events before resetting
101
+ this.flush().catch(() => {
102
+ // noop - we're resetting anyway
103
+ });
62
104
  this.queue = [];
63
105
  }
106
+ /**
107
+ * Stop the event logger, flush all pending events, and remove from global map.
108
+ * This ensures all events are sent before the client is destroyed.
109
+ */
64
110
  stop() {
65
111
  return __awaiter(this, void 0, void 0, function* () {
66
112
  if (this.flushIntervalId) {
67
113
  clearInterval(this.flushIntervalId);
68
114
  this.flushIntervalId = null;
69
115
  }
116
+ // Flush all pending events before stopping
70
117
  yield this.flush();
118
+ // Remove from global map after ensuring all events are sent
119
+ delete EVENT_LOGGER_MAP[this.sdkKey];
71
120
  });
72
121
  }
73
122
  }
package/dist/Network.d.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import { WolvesUser } from './WolvesUser';
2
2
  export declare const API_LOCAL = "http://localhost:8000/api";
3
3
  export declare const API_DEV = "https://wolves-nova-dev.azurewebsites.net/api";
4
+ export declare const API_PROD = "https://wolves-nova.azurewebsites.net/api";
5
+ export type ApiEnvironment = 'local' | 'dev' | 'prod';
4
6
  export declare class Network {
5
7
  private sdkKey;
6
8
  private api;
7
- constructor(sdkKey: string, api?: string);
9
+ constructor(sdkKey: string, apiEnv?: ApiEnvironment);
8
10
  fetchConfig(user: WolvesUser, sinceTime?: number, retries?: number, backoff?: number): Promise<any>;
9
11
  sendEvents(events: any[], retries?: number, backoff?: number): Promise<void>;
10
12
  }
package/dist/Network.js CHANGED
@@ -9,17 +9,30 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.Network = exports.API_DEV = exports.API_LOCAL = void 0;
12
+ exports.Network = exports.API_PROD = exports.API_DEV = exports.API_LOCAL = void 0;
13
13
  const Log_1 = require("./Log");
14
14
  const WolvesMetadata_1 = require("./WolvesMetadata");
15
15
  const RETRYABLE_CODES = [408, 500, 502, 503, 504, 522, 524, 599];
16
- // API endpoints - switch between local and production
16
+ // API endpoints
17
17
  exports.API_LOCAL = 'http://localhost:8000/api';
18
18
  exports.API_DEV = 'https://wolves-nova-dev.azurewebsites.net/api';
19
+ exports.API_PROD = 'https://wolves-nova.azurewebsites.net/api';
20
+ const API_ENDPOINTS = {
21
+ local: exports.API_LOCAL,
22
+ dev: exports.API_DEV,
23
+ prod: exports.API_PROD,
24
+ };
25
+ const VALID_ENVIRONMENTS = ['local', 'dev', 'prod'];
26
+ function validateAndGetEndpoint(apiEnv) {
27
+ if (!VALID_ENVIRONMENTS.includes(apiEnv)) {
28
+ throw new Error(`Invalid apiEnv: "${apiEnv}". Must be one of: ${VALID_ENVIRONMENTS.join(', ')}`);
29
+ }
30
+ return API_ENDPOINTS[apiEnv];
31
+ }
19
32
  class Network {
20
- constructor(sdkKey, api = exports.API_DEV) {
33
+ constructor(sdkKey, apiEnv = 'prod') {
21
34
  this.sdkKey = sdkKey;
22
- this.api = api;
35
+ this.api = validateAndGetEndpoint(apiEnv);
23
36
  }
24
37
  fetchConfig(user_1, sinceTime_1) {
25
38
  return __awaiter(this, arguments, void 0, function* (user, sinceTime, retries = 2, backoff = 1000) {
package/dist/Store.d.ts CHANGED
@@ -3,6 +3,9 @@ export type ExperimentConfig = {
3
3
  value: Record<string, any>;
4
4
  experiment_id?: string;
5
5
  group?: string;
6
+ group_name?: string;
7
+ is_user_in_experiment?: boolean;
8
+ is_experiment_active?: boolean;
6
9
  };
7
10
  export type InitializeResponse = {
8
11
  dynamic_configs: Record<string, ExperimentConfig>;
package/dist/Types.d.ts CHANGED
@@ -17,5 +17,7 @@ export type Experiment = {
17
17
  value: Record<string, unknown>;
18
18
  experimentID: string;
19
19
  groupName: string | null;
20
+ isExperimentActive: boolean;
21
+ isUserInExperiment: boolean;
20
22
  get: <T>(key: string, defaultValue: T) => T;
21
23
  };
@@ -23,7 +23,7 @@ export declare class WolvesClient {
23
23
  private logger;
24
24
  private loadingStatus;
25
25
  private initializePromise;
26
- constructor(sdkKey: string, user: WolvesUser);
26
+ constructor(sdkKey: string, user: WolvesUser, apiEnv?: string);
27
27
  /**
28
28
  * Returns the current loading status of the client.
29
29
  */
@@ -105,6 +105,14 @@ export declare class WolvesClient {
105
105
  */
106
106
  getExperimentForTest(experimentName: string, groupName: string): Experiment;
107
107
  logEvent(eventName: string, value?: string | number, metadata?: Record<string, string>): void;
108
+ /**
109
+ * Manually flush all pending events to the server.
110
+ * This is useful when you want to ensure events are sent immediately,
111
+ * such as before navigating away from a page.
112
+ *
113
+ * @returns Promise that resolves when all events have been sent
114
+ */
115
+ flush(): Promise<void>;
108
116
  shutdown(): Promise<void>;
109
117
  private logExposure;
110
118
  }
@@ -22,14 +22,14 @@ function createUpdateDetails(success, source, duration, error, warnings = []) {
22
22
  return { success, source, duration, error, warnings };
23
23
  }
24
24
  class WolvesClient {
25
- constructor(sdkKey, user) {
25
+ constructor(sdkKey, user, apiEnv = 'prod') {
26
26
  this.loadingStatus = 'Uninitialized';
27
27
  this.initializePromise = null;
28
28
  this.sdkKey = sdkKey;
29
29
  this.user = user;
30
- this.network = new Network_1.Network(sdkKey);
30
+ this.network = new Network_1.Network(sdkKey, apiEnv);
31
31
  this.store = new Store_1.Store(sdkKey);
32
- this.logger = new EventLogger_1.EventLogger(this.network);
32
+ this.logger = new EventLogger_1.EventLogger(sdkKey, this.network);
33
33
  }
34
34
  /**
35
35
  * Returns the current loading status of the client.
@@ -267,13 +267,23 @@ class WolvesClient {
267
267
  this.user = user;
268
268
  }
269
269
  getExperiment(experimentName) {
270
- var _a, _b, _c;
270
+ var _a, _b, _c, _d, _e;
271
271
  const { config, details } = this.store.getExperiment(experimentName);
272
+ // Check if experiment is in prestart state (setup status on server)
273
+ const isExperimentActive = (_a = config === null || config === void 0 ? void 0 : config.is_experiment_active) !== null && _a !== void 0 ? _a : true;
274
+ const isUserInExperiment = (_b = config === null || config === void 0 ? void 0 : config.is_user_in_experiment) !== null && _b !== void 0 ? _b : true;
275
+ const isPrestart = !isExperimentActive && (config === null || config === void 0 ? void 0 : config.group) === 'prestart';
276
+ // For prestart experiments, log the prestart state
277
+ if (isPrestart) {
278
+ Log_1.Log.info(`Experiment "${experimentName}" is in prestart state`);
279
+ }
272
280
  const experiment = {
273
281
  name: experimentName,
274
- value: (_a = config === null || config === void 0 ? void 0 : config.value) !== null && _a !== void 0 ? _a : {},
275
- experimentID: (_b = config === null || config === void 0 ? void 0 : config.experiment_id) !== null && _b !== void 0 ? _b : '',
276
- groupName: (_c = config === null || config === void 0 ? void 0 : config.group) !== null && _c !== void 0 ? _c : null,
282
+ value: (_c = config === null || config === void 0 ? void 0 : config.value) !== null && _c !== void 0 ? _c : {},
283
+ experimentID: (_d = config === null || config === void 0 ? void 0 : config.experiment_id) !== null && _d !== void 0 ? _d : '',
284
+ groupName: (_e = config === null || config === void 0 ? void 0 : config.group) !== null && _e !== void 0 ? _e : null,
285
+ isExperimentActive,
286
+ isUserInExperiment,
277
287
  get: (key, defaultValue) => {
278
288
  var _a;
279
289
  const val = (_a = config === null || config === void 0 ? void 0 : config.value) === null || _a === void 0 ? void 0 : _a[key];
@@ -293,20 +303,28 @@ class WolvesClient {
293
303
  * The experiment ID is retrieved from the store config.
294
304
  */
295
305
  getExperimentForTest(experimentName, groupName) {
296
- var _a;
306
+ var _a, _b, _c;
297
307
  const { config, details } = this.store.getExperiment(experimentName);
298
308
  const experimentId = (_a = config === null || config === void 0 ? void 0 : config.experiment_id) !== null && _a !== void 0 ? _a : '';
309
+ const isExperimentActive = (_b = config === null || config === void 0 ? void 0 : config.is_experiment_active) !== null && _b !== void 0 ? _b : true;
310
+ const isUserInExperiment = (_c = config === null || config === void 0 ? void 0 : config.is_user_in_experiment) !== null && _c !== void 0 ? _c : true;
299
311
  const experiment = {
300
312
  name: experimentName,
301
313
  value: {},
302
314
  experimentID: experimentId,
303
315
  groupName: groupName,
316
+ isExperimentActive,
317
+ isUserInExperiment,
304
318
  get: (_key, defaultValue) => {
305
319
  return defaultValue;
306
320
  }
307
321
  };
308
322
  // Log exposure with the specified group and experiment ID from store
309
- this.logExposure(experimentName, { group: groupName, experiment_id: experimentId, value: {} }, details);
323
+ this.logExposure(experimentName, {
324
+ group: groupName,
325
+ experiment_id: experimentId,
326
+ value: {}
327
+ }, details);
310
328
  return experiment;
311
329
  }
312
330
  logEvent(eventName, value, metadata) {
@@ -319,6 +337,18 @@ class WolvesClient {
319
337
  };
320
338
  this.logger.enqueue(event);
321
339
  }
340
+ /**
341
+ * Manually flush all pending events to the server.
342
+ * This is useful when you want to ensure events are sent immediately,
343
+ * such as before navigating away from a page.
344
+ *
345
+ * @returns Promise that resolves when all events have been sent
346
+ */
347
+ flush() {
348
+ return __awaiter(this, void 0, void 0, function* () {
349
+ yield this.logger.flush();
350
+ });
351
+ }
322
352
  shutdown() {
323
353
  return __awaiter(this, void 0, void 0, function* () {
324
354
  yield this.logger.stop();
@@ -1,4 +1,4 @@
1
- export declare const SDK_VERSION = "1.0.5";
1
+ export declare const SDK_VERSION = "1.0.7";
2
2
  export declare const SDK_TYPE = "wolves-js-client";
3
3
  export type WolvesMetadata = {
4
4
  readonly [key: string]: string | undefined | null;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WolvesMetadataProvider = exports.SDK_TYPE = exports.SDK_VERSION = void 0;
4
- exports.SDK_VERSION = '1.0.5';
4
+ exports.SDK_VERSION = '1.0.7';
5
5
  exports.SDK_TYPE = 'wolves-js-client';
6
6
  let metadata = {
7
7
  sdk_version: exports.SDK_VERSION,
package/dist/index.d.ts CHANGED
@@ -8,3 +8,4 @@ export type { WolvesMetadata } from './WolvesMetadata';
8
8
  export { Storage } from './StorageProvider';
9
9
  export type { StorageProvider } from './StorageProvider';
10
10
  export type { DataSource, DataAdapterResult } from './Store';
11
+ export type { ApiEnvironment } from './Network';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolves-js-client",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "A Wolves JavaScript Client SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",