@launchdarkly/js-client-sdk-common 1.14.0 → 1.15.0
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/CHANGELOG.md +14 -0
- package/dist/cjs/LDClientImpl.d.ts +11 -2
- package/dist/cjs/LDClientImpl.d.ts.map +1 -1
- package/dist/cjs/api/LDClient.d.ts +37 -0
- package/dist/cjs/api/LDClient.d.ts.map +1 -1
- package/dist/cjs/api/LDIdentifyOptions.d.ts +10 -0
- package/dist/cjs/api/LDIdentifyOptions.d.ts.map +1 -1
- package/dist/cjs/api/LDIdentifyResult.d.ts +28 -0
- package/dist/cjs/api/LDIdentifyResult.d.ts.map +1 -0
- package/dist/cjs/api/index.d.ts +1 -0
- package/dist/cjs/api/index.d.ts.map +1 -1
- package/dist/cjs/api/integrations/Hooks.d.ts +10 -1
- package/dist/cjs/api/integrations/Hooks.d.ts.map +1 -1
- package/dist/cjs/async/AsyncTaskQueue.d.ts +28 -9
- package/dist/cjs/async/AsyncTaskQueue.d.ts.map +1 -1
- package/dist/cjs/index.cjs +269 -37
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/streaming/StreamingProcessor.d.ts +1 -0
- package/dist/cjs/streaming/StreamingProcessor.d.ts.map +1 -1
- package/dist/esm/LDClientImpl.d.ts +11 -2
- package/dist/esm/LDClientImpl.d.ts.map +1 -1
- package/dist/esm/api/LDClient.d.ts +37 -0
- package/dist/esm/api/LDClient.d.ts.map +1 -1
- package/dist/esm/api/LDIdentifyOptions.d.ts +10 -0
- package/dist/esm/api/LDIdentifyOptions.d.ts.map +1 -1
- package/dist/esm/api/LDIdentifyResult.d.ts +28 -0
- package/dist/esm/api/LDIdentifyResult.d.ts.map +1 -0
- package/dist/esm/api/index.d.ts +1 -0
- package/dist/esm/api/index.d.ts.map +1 -1
- package/dist/esm/api/integrations/Hooks.d.ts +10 -1
- package/dist/esm/api/integrations/Hooks.d.ts.map +1 -1
- package/dist/esm/async/AsyncTaskQueue.d.ts +28 -9
- package/dist/esm/async/AsyncTaskQueue.d.ts.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.mjs +270 -38
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/streaming/StreamingProcessor.d.ts +1 -0
- package/dist/esm/streaming/StreamingProcessor.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Hooks.d.ts","sourceRoot":"","sources":["../../../src/api/integrations/Hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAExD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAOhC;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED
|
|
1
|
+
{"version":3,"file":"Hooks.d.ts","sourceRoot":"","sources":["../../../src/api/integrations/Hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAExD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAOhC;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,oBAAoB,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;AAElE;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,oBAAoB,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB;;OAEG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,IAAI;IACnB;;OAEG;IACH,WAAW,IAAI,YAAY,CAAC;IAE5B;;;;;;;;;;;;;;OAcG;IACH,gBAAgB,CAAC,CACf,WAAW,EAAE,uBAAuB,EACpC,IAAI,EAAE,oBAAoB,GACzB,oBAAoB,CAAC;IAExB;;;;;;;;;;;;;;;;OAgBG;IACH,eAAe,CAAC,CACd,WAAW,EAAE,uBAAuB,EACpC,IAAI,EAAE,oBAAoB,EAC1B,MAAM,EAAE,kBAAkB,GACzB,oBAAoB,CAAC;IAExB;;;;;;;;;;;;;;OAcG;IACH,cAAc,CAAC,CAAC,WAAW,EAAE,qBAAqB,EAAE,IAAI,EAAE,kBAAkB,GAAG,kBAAkB,CAAC;IAElG;;;;;;;;;;;;;;;;;;;OAmBG;IACH,aAAa,CAAC,CACZ,WAAW,EAAE,qBAAqB,EAClC,IAAI,EAAE,kBAAkB,EACxB,MAAM,EAAE,oBAAoB,GAC3B,kBAAkB,CAAC;IAEtB;;;;;;OAMG;IACH,UAAU,CAAC,CAAC,WAAW,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACpD"}
|
|
@@ -24,6 +24,22 @@ export interface ErroredTask {
|
|
|
24
24
|
* Represents the result of a task.
|
|
25
25
|
*/
|
|
26
26
|
export type TaskResult<TTaskResult> = CompletedTask<TTaskResult> | ErroredTask | ShedTask;
|
|
27
|
+
export interface Task<TTaskResult, TBeforeResult> {
|
|
28
|
+
/**
|
|
29
|
+
* Method ran before the task is executed or shed.
|
|
30
|
+
*/
|
|
31
|
+
before?: () => Promise<TBeforeResult>;
|
|
32
|
+
/**
|
|
33
|
+
* Execute the task. This is not ran if the task is shed.
|
|
34
|
+
* @returns The result of the task.
|
|
35
|
+
*/
|
|
36
|
+
execute: (beforeResult?: TBeforeResult) => Promise<TTaskResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Method ran after the task is executed or shed.
|
|
39
|
+
* @param result The result of the task.
|
|
40
|
+
*/
|
|
41
|
+
after?: (result: TaskResult<TTaskResult>, beforeResult?: TBeforeResult) => void;
|
|
42
|
+
}
|
|
27
43
|
/**
|
|
28
44
|
* An asynchronous task queue with the ability to replace pending tasks.
|
|
29
45
|
*
|
|
@@ -33,7 +49,7 @@ export type TaskResult<TTaskResult> = CompletedTask<TTaskResult> | ErroredTask |
|
|
|
33
49
|
* For instance, the SDK can only have one active context at a time, if you request identification of many contexts,
|
|
34
50
|
* then the ultimate state will be based on the last request. The intermediate identifies can be discarded.
|
|
35
51
|
*
|
|
36
|
-
* This
|
|
52
|
+
* This queue will always begin execution of the first item added to the queue, at that point the item itself is not
|
|
37
53
|
* queued, but active. If another request is made while that item is still active, then it is added to the queue.
|
|
38
54
|
* A third request would then replace the second request if the second request had not yet become active, and it was
|
|
39
55
|
* sheddable.
|
|
@@ -49,11 +65,7 @@ export type TaskResult<TTaskResult> = CompletedTask<TTaskResult> | ErroredTask |
|
|
|
49
65
|
* Queue management should be done synchronously. There should not be asynchronous operations between checking the queue
|
|
50
66
|
* and acting on the results of said check.
|
|
51
67
|
*/
|
|
52
|
-
export declare
|
|
53
|
-
private readonly _logger?;
|
|
54
|
-
private _activeTask?;
|
|
55
|
-
private _queue;
|
|
56
|
-
constructor(_logger?: LDLogger | undefined);
|
|
68
|
+
export declare function createAsyncTaskQueue<TTaskResult>(logger?: LDLogger): {
|
|
57
69
|
/**
|
|
58
70
|
* Execute a task using the queue.
|
|
59
71
|
*
|
|
@@ -61,7 +73,14 @@ export declare class AsyncTaskQueue<TTaskResult> {
|
|
|
61
73
|
* @param sheddable Whether the task can be shed from the queue.
|
|
62
74
|
* @returns A promise that resolves to the result of the task.
|
|
63
75
|
*/
|
|
64
|
-
execute(task:
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
execute<TBeforeResult>(task: Task<TTaskResult, TBeforeResult>, sheddable?: boolean): Promise<TaskResult<TTaskResult>>;
|
|
77
|
+
/**
|
|
78
|
+
* Returns the number of pending tasks in the queue.
|
|
79
|
+
* Intended for testing purposes only.
|
|
80
|
+
*
|
|
81
|
+
* @internal
|
|
82
|
+
* @returns The number of pending tasks in the queue.
|
|
83
|
+
*/
|
|
84
|
+
pendingCount(): number;
|
|
85
|
+
};
|
|
67
86
|
//# sourceMappingURL=AsyncTaskQueue.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AsyncTaskQueue.d.ts","sourceRoot":"","sources":["../../src/async/AsyncTaskQueue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAEvD;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,WAAW;IACxC,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,WAAW,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;CACd;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,CAAC,WAAW,IAAI,aAAa,CAAC,WAAW,CAAC,GAAG,WAAW,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"AsyncTaskQueue.d.ts","sourceRoot":"","sources":["../../src/async/AsyncTaskQueue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAEvD;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,WAAW;IACxC,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,WAAW,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;CACd;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,CAAC,WAAW,IAAI,aAAa,CAAC,WAAW,CAAC,GAAG,WAAW,GAAG,QAAQ,CAAC;AAa1F,MAAM,WAAW,IAAI,CAAC,WAAW,EAAE,aAAa;IAC9C;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;IAEtC;;;OAGG;IAEH,OAAO,EAAE,CAAC,YAAY,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IAEhE;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,EAAE,YAAY,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC;CACjF;AAoED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,QAAQ;IAwB/D;;;;;;OAMG;+EAGU,OAAO,GACjB,QAAQ,WAAW,WAAW,CAAC,CAAC;IAoBnC;;;;;;OAMG;oBACa,MAAM;EAIzB"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import LDClientImpl from './LDClientImpl';
|
|
|
6
6
|
import LDEmitter, { EventName } from './LDEmitter';
|
|
7
7
|
export * from '@launchdarkly/js-sdk-common';
|
|
8
8
|
export * as platform from '@launchdarkly/js-sdk-common';
|
|
9
|
-
export type { LDEvaluationDetail, LDEvaluationDetailTyped, LDClient, LDOptions, ConnectionMode, LDIdentifyOptions, Hook, HookMetadata, EvaluationSeriesContext, EvaluationSeriesData, IdentifySeriesContext, IdentifySeriesData, IdentifySeriesResult, IdentifySeriesStatus, TrackSeriesContext, LDInspection, } from './api';
|
|
9
|
+
export type { LDEvaluationDetail, LDEvaluationDetailTyped, LDClient, LDOptions, ConnectionMode, LDIdentifyOptions, Hook, HookMetadata, EvaluationSeriesContext, EvaluationSeriesData, IdentifySeriesContext, IdentifySeriesData, IdentifySeriesResult, IdentifySeriesStatus, TrackSeriesContext, LDInspection, LDIdentifyResult, LDIdentifySuccess, LDIdentifyError, LDIdentifyTimeout, LDIdentifyShed, LDClientIdentifyResult, } from './api';
|
|
10
10
|
export type { DataManager, DataManagerFactory, ConnectionParams } from './DataManager';
|
|
11
11
|
export type { FlagManager } from './flag-manager/FlagManager';
|
|
12
12
|
export type { Configuration } from './configuration/Configuration';
|
package/dist/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,gBAAgB,EAAE,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAClF,OAAO,yBAAyB,MAAM,wCAAwC,CAAC;AAC/E,OAAO,SAAS,EAAE,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAC1C,OAAO,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEnD,cAAc,6BAA6B,CAAC;AAE5C,OAAO,KAAK,QAAQ,MAAM,6BAA6B,CAAC;AAMxD,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,QAAQ,EACR,SAAS,EACT,cAAc,EACd,iBAAiB,EACjB,IAAI,EACJ,YAAY,EACZ,uBAAuB,EACvB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,gBAAgB,EAAE,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAClF,OAAO,yBAAyB,MAAM,wCAAwC,CAAC;AAC/E,OAAO,SAAS,EAAE,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAC1C,OAAO,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEnD,cAAc,6BAA6B,CAAC;AAE5C,OAAO,KAAK,QAAQ,MAAM,6BAA6B,CAAC;AAMxD,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,QAAQ,EACR,SAAS,EACT,cAAc,EACd,iBAAiB,EACjB,IAAI,EACJ,YAAY,EACZ,uBAAuB,EACvB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,sBAAsB,GACvB,MAAM,OAAO,CAAC;AAEf,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACvF,YAAY,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,YAAY,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAEnE,YAAY,EAAE,SAAS,EAAE,CAAC;AAC1B,YAAY,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,YAAY,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAEpC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC;AAEpC,OAAO,EACL,gBAAgB,EAChB,yBAAyB,EACzB,YAAY,EACZ,uBAAuB,EACvB,eAAe,EACf,SAAS,IAAI,kBAAkB,GAChC,CAAC"}
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getPollingUri, TypeValidators, createSafeLogger, ServiceEndpoints, ApplicationTags, OptionMessages, NumberWithMinimum, SafeLogger, internal, deepCompact, clone, secondsToMillis, ClientContext, fastDeepEqual, defaultHeaders, Context,
|
|
1
|
+
import { getPollingUri, TypeValidators, createSafeLogger, ServiceEndpoints, ApplicationTags, OptionMessages, NumberWithMinimum, SafeLogger, internal, deepCompact, clone, secondsToMillis, ClientContext, fastDeepEqual, defaultHeaders, Context, LDTimeoutError, AutoEnvAttributes, LDClientError, isHttpRecoverable, httpErrorMessage, LDPollingError, DataSourceErrorKind, getStreamingUri, shouldRetry, LDStreamingError } from '@launchdarkly/js-sdk-common';
|
|
2
2
|
export * from '@launchdarkly/js-sdk-common';
|
|
3
3
|
import * as jsSdkCommon from '@launchdarkly/js-sdk-common';
|
|
4
4
|
export { jsSdkCommon as platform };
|
|
@@ -80,6 +80,143 @@ function makeRequestor(plainContextString, serviceEndpoints, paths, requests, en
|
|
|
80
80
|
return new Requestor(requests, uri, headers, method, body);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
const duplicateExecutionError = new Error('Task has already been executed or shed. This is likely an implementation error. The task will not be executed again.');
|
|
84
|
+
/**
|
|
85
|
+
* Creates a pending task.
|
|
86
|
+
* @param task The async function to execute.
|
|
87
|
+
* @param sheddable Whether the task can be shed from the queue.
|
|
88
|
+
* @returns A pending task.
|
|
89
|
+
*/
|
|
90
|
+
function makePending(task, _logger, sheddable = false) {
|
|
91
|
+
let resolveTask;
|
|
92
|
+
const promise = new Promise((resolve) => {
|
|
93
|
+
resolveTask = (result, beforeResult) => {
|
|
94
|
+
try {
|
|
95
|
+
task.after?.(result, beforeResult);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
_logger?.error(`Error in after callback: ${error}`);
|
|
99
|
+
}
|
|
100
|
+
resolve(result);
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
const beforePromise = task.before ? task.before() : Promise.resolve(undefined);
|
|
104
|
+
let executedOrShed = false;
|
|
105
|
+
return {
|
|
106
|
+
execute: () => {
|
|
107
|
+
if (executedOrShed) {
|
|
108
|
+
// This should never happen. If it does, then it represents an implementation error in the SDK.
|
|
109
|
+
_logger?.error(duplicateExecutionError);
|
|
110
|
+
}
|
|
111
|
+
executedOrShed = true;
|
|
112
|
+
beforePromise
|
|
113
|
+
.then((beforeResult) => {
|
|
114
|
+
task
|
|
115
|
+
.execute(beforeResult)
|
|
116
|
+
.then((result) => resolveTask({ status: 'complete', result }, beforeResult))
|
|
117
|
+
.catch((error) => resolveTask({ status: 'error', error }, beforeResult));
|
|
118
|
+
})
|
|
119
|
+
.catch((error) => {
|
|
120
|
+
_logger?.error(error);
|
|
121
|
+
resolveTask({ status: 'error', error }, undefined);
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
shed: () => {
|
|
125
|
+
if (executedOrShed) {
|
|
126
|
+
// This should never happen. If it does, then it represents an implementation error in the SDK.
|
|
127
|
+
_logger?.error(duplicateExecutionError);
|
|
128
|
+
}
|
|
129
|
+
executedOrShed = true;
|
|
130
|
+
beforePromise.then((beforeResult) => {
|
|
131
|
+
resolveTask({ status: 'shed' }, beforeResult);
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
promise,
|
|
135
|
+
sheddable,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* An asynchronous task queue with the ability to replace pending tasks.
|
|
140
|
+
*
|
|
141
|
+
* This is useful when you have asynchronous operations which much execute in order, and for cases where intermediate
|
|
142
|
+
* operations can be discarded.
|
|
143
|
+
*
|
|
144
|
+
* For instance, the SDK can only have one active context at a time, if you request identification of many contexts,
|
|
145
|
+
* then the ultimate state will be based on the last request. The intermediate identifies can be discarded.
|
|
146
|
+
*
|
|
147
|
+
* This queue will always begin execution of the first item added to the queue, at that point the item itself is not
|
|
148
|
+
* queued, but active. If another request is made while that item is still active, then it is added to the queue.
|
|
149
|
+
* A third request would then replace the second request if the second request had not yet become active, and it was
|
|
150
|
+
* sheddable.
|
|
151
|
+
*
|
|
152
|
+
* Once a task is active the queue will complete it. It doesn't cancel tasks that it has started, but it can shed tasks
|
|
153
|
+
* that have not started.
|
|
154
|
+
*
|
|
155
|
+
* TTaskResult Is the return type of the task to be executed. Tasks accept no parameters. So if you need parameters
|
|
156
|
+
* you should use a lambda to capture them.
|
|
157
|
+
*
|
|
158
|
+
* Exceptions from tasks are always handled and the execute method will never reject a promise.
|
|
159
|
+
*
|
|
160
|
+
* Queue management should be done synchronously. There should not be asynchronous operations between checking the queue
|
|
161
|
+
* and acting on the results of said check.
|
|
162
|
+
*/
|
|
163
|
+
function createAsyncTaskQueue(logger) {
|
|
164
|
+
let activeTask;
|
|
165
|
+
const queue = [];
|
|
166
|
+
function checkPending() {
|
|
167
|
+
// There is an existing active task, so we don't need to do anything.
|
|
168
|
+
if (activeTask) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// There are pending tasks, so we need to execute the next one.
|
|
172
|
+
if (queue.length > 0) {
|
|
173
|
+
const nextTask = queue.shift();
|
|
174
|
+
activeTask = nextTask.promise.finally(() => {
|
|
175
|
+
activeTask = undefined;
|
|
176
|
+
checkPending();
|
|
177
|
+
});
|
|
178
|
+
nextTask.execute();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
/**
|
|
183
|
+
* Execute a task using the queue.
|
|
184
|
+
*
|
|
185
|
+
* @param task The async function to execute.
|
|
186
|
+
* @param sheddable Whether the task can be shed from the queue.
|
|
187
|
+
* @returns A promise that resolves to the result of the task.
|
|
188
|
+
*/
|
|
189
|
+
execute(task, sheddable = false) {
|
|
190
|
+
const pending = makePending(task, logger, sheddable);
|
|
191
|
+
if (!activeTask) {
|
|
192
|
+
activeTask = pending.promise.finally(() => {
|
|
193
|
+
activeTask = undefined;
|
|
194
|
+
checkPending();
|
|
195
|
+
});
|
|
196
|
+
pending.execute();
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
// If the last pending task is sheddable, we need to shed it before adding the new task.
|
|
200
|
+
if (queue[queue.length - 1]?.sheddable) {
|
|
201
|
+
queue.pop()?.shed();
|
|
202
|
+
}
|
|
203
|
+
queue.push(pending);
|
|
204
|
+
}
|
|
205
|
+
return pending.promise;
|
|
206
|
+
},
|
|
207
|
+
/**
|
|
208
|
+
* Returns the number of pending tasks in the queue.
|
|
209
|
+
* Intended for testing purposes only.
|
|
210
|
+
*
|
|
211
|
+
* @internal
|
|
212
|
+
* @returns The number of pending tasks in the queue.
|
|
213
|
+
*/
|
|
214
|
+
pendingCount() {
|
|
215
|
+
return queue.length;
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
83
220
|
// eslint-disable-next-line max-classes-per-file
|
|
84
221
|
const validators = {
|
|
85
222
|
logger: TypeValidators.Object,
|
|
@@ -1194,6 +1331,7 @@ class LDClientImpl {
|
|
|
1194
1331
|
this._eventFactoryDefault = new EventFactory(false);
|
|
1195
1332
|
this._eventFactoryWithReasons = new EventFactory(true);
|
|
1196
1333
|
this._eventSendingEnabled = false;
|
|
1334
|
+
this._identifyQueue = createAsyncTaskQueue();
|
|
1197
1335
|
if (!sdkKey) {
|
|
1198
1336
|
throw new Error('You must configure the client with a client-side SDK key');
|
|
1199
1337
|
}
|
|
@@ -1259,7 +1397,7 @@ class LDClientImpl {
|
|
|
1259
1397
|
}
|
|
1260
1398
|
getContext() {
|
|
1261
1399
|
// The LDContext returned here may have been modified by the SDK (for example: adding auto env attributes).
|
|
1262
|
-
// We are returning an LDContext here to maintain a consistent
|
|
1400
|
+
// We are returning an LDContext here to maintain a consistent representation of context to the consuming
|
|
1263
1401
|
// code. We are returned the unchecked context so that if a consumer identifies with an invalid context
|
|
1264
1402
|
// and then calls getContext, they get back the same context they provided, without any assertion about
|
|
1265
1403
|
// validity.
|
|
@@ -1268,28 +1406,25 @@ class LDClientImpl {
|
|
|
1268
1406
|
getInternalContext() {
|
|
1269
1407
|
return this._checkedContext;
|
|
1270
1408
|
}
|
|
1271
|
-
_createIdentifyPromise(
|
|
1409
|
+
_createIdentifyPromise() {
|
|
1272
1410
|
let res;
|
|
1273
1411
|
let rej;
|
|
1274
1412
|
const basePromise = new Promise((resolve, reject) => {
|
|
1275
1413
|
res = resolve;
|
|
1276
1414
|
rej = reject;
|
|
1277
1415
|
});
|
|
1278
|
-
|
|
1279
|
-
return { identifyPromise: basePromise, identifyResolve: res, identifyReject: rej };
|
|
1280
|
-
}
|
|
1281
|
-
const timed = timedPromise(timeout, 'identify');
|
|
1282
|
-
const raced = Promise.race([timed, basePromise]).catch((e) => {
|
|
1283
|
-
if (e.message.includes('timed out')) {
|
|
1284
|
-
this.logger.error(`identify error: ${e}`);
|
|
1285
|
-
}
|
|
1286
|
-
throw e;
|
|
1287
|
-
});
|
|
1288
|
-
return { identifyPromise: raced, identifyResolve: res, identifyReject: rej };
|
|
1416
|
+
return { identifyPromise: basePromise, identifyResolve: res, identifyReject: rej };
|
|
1289
1417
|
}
|
|
1290
1418
|
/**
|
|
1291
1419
|
* Identifies a context to LaunchDarkly. See {@link LDClient.identify}.
|
|
1292
1420
|
*
|
|
1421
|
+
* If used with the `sheddable` option set to true, then the identify operation will be sheddable. This means that if
|
|
1422
|
+
* multiple identify operations are done, without waiting for the previous one to complete, then intermediate
|
|
1423
|
+
* operations may be discarded.
|
|
1424
|
+
*
|
|
1425
|
+
* It is recommended to use the `identifyResult` method instead when the operation is sheddable. In a future release,
|
|
1426
|
+
* all identify operations will default to being sheddable.
|
|
1427
|
+
*
|
|
1293
1428
|
* @param pristineContext The LDContext object to be identified.
|
|
1294
1429
|
* @param identifyOptions Optional configuration. See {@link LDIdentifyOptions}.
|
|
1295
1430
|
* @returns A Promise which resolves when the flag values for the specified
|
|
@@ -1303,39 +1438,95 @@ class LDClientImpl {
|
|
|
1303
1438
|
* 3. A network error is encountered during initialization.
|
|
1304
1439
|
*/
|
|
1305
1440
|
async identify(pristineContext, identifyOptions) {
|
|
1441
|
+
// In order to manage customization in the derived classes it is important that `identify` MUST be implemented in
|
|
1442
|
+
// terms of `identifyResult`. So that the logic of the identification process can be extended in one place.
|
|
1443
|
+
const result = await this.identifyResult(pristineContext, identifyOptions);
|
|
1444
|
+
if (result.status === 'error') {
|
|
1445
|
+
throw result.error;
|
|
1446
|
+
}
|
|
1447
|
+
else if (result.status === 'timeout') {
|
|
1448
|
+
const timeoutError = new LDTimeoutError(`identify timed out after ${result.timeout} seconds.`);
|
|
1449
|
+
this.logger.error(timeoutError.message);
|
|
1450
|
+
throw timeoutError;
|
|
1451
|
+
}
|
|
1452
|
+
// If completed or shed, then we are done.
|
|
1453
|
+
}
|
|
1454
|
+
async identifyResult(pristineContext, identifyOptions) {
|
|
1306
1455
|
const identifyTimeout = identifyOptions?.timeout ?? DEFAULT_IDENTIFY_TIMEOUT_SECONDS;
|
|
1307
1456
|
const noTimeout = identifyOptions?.timeout === undefined && identifyOptions?.noTimeout === true;
|
|
1308
|
-
// When noTimeout is specified, and a timeout is not
|
|
1457
|
+
// When noTimeout is specified, and a timeout is not specified, then this condition cannot
|
|
1309
1458
|
// be encountered. (Our default would need to be greater)
|
|
1310
1459
|
if (identifyTimeout > this._highTimeoutThreshold) {
|
|
1311
1460
|
this.logger.warn('The identify function was called with a timeout greater than ' +
|
|
1312
1461
|
`${this._highTimeoutThreshold} seconds. We recommend a timeout of less than ` +
|
|
1313
1462
|
`${this._highTimeoutThreshold} seconds.`);
|
|
1314
1463
|
}
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1464
|
+
const callSitePromise = this._identifyQueue
|
|
1465
|
+
.execute({
|
|
1466
|
+
before: async () => {
|
|
1467
|
+
let context = await ensureKey(pristineContext, this.platform);
|
|
1468
|
+
if (this.autoEnvAttributes === AutoEnvAttributes.Enabled) {
|
|
1469
|
+
context = await addAutoEnv(context, this.platform, this._config);
|
|
1470
|
+
}
|
|
1471
|
+
const checkedContext = Context.fromLDContext(context);
|
|
1472
|
+
if (checkedContext.valid) {
|
|
1473
|
+
const afterIdentify = this._hookRunner.identify(context, identifyOptions?.timeout);
|
|
1474
|
+
return {
|
|
1475
|
+
context,
|
|
1476
|
+
checkedContext,
|
|
1477
|
+
afterIdentify,
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
return {
|
|
1481
|
+
context,
|
|
1482
|
+
checkedContext,
|
|
1483
|
+
};
|
|
1484
|
+
},
|
|
1485
|
+
execute: async (beforeResult) => {
|
|
1486
|
+
const { context, checkedContext } = beforeResult;
|
|
1487
|
+
if (!checkedContext.valid) {
|
|
1488
|
+
const error = new Error('Context was unspecified or had no key');
|
|
1489
|
+
this.emitter.emit('error', context, error);
|
|
1490
|
+
return Promise.reject(error);
|
|
1491
|
+
}
|
|
1492
|
+
this._uncheckedContext = context;
|
|
1493
|
+
this._checkedContext = checkedContext;
|
|
1494
|
+
this._eventProcessor?.sendEvent(this._eventFactoryDefault.identifyEvent(this._checkedContext));
|
|
1495
|
+
const { identifyPromise, identifyResolve, identifyReject } = this._createIdentifyPromise();
|
|
1496
|
+
this.logger.debug(`Identifying ${JSON.stringify(this._checkedContext)}`);
|
|
1497
|
+
await this.dataManager.identify(identifyResolve, identifyReject, checkedContext, identifyOptions);
|
|
1498
|
+
return identifyPromise;
|
|
1499
|
+
},
|
|
1500
|
+
after: async (res, beforeResult) => {
|
|
1501
|
+
if (res.status === 'complete') {
|
|
1502
|
+
beforeResult?.afterIdentify?.({ status: 'completed' });
|
|
1503
|
+
}
|
|
1504
|
+
else if (res.status === 'shed') {
|
|
1505
|
+
beforeResult?.afterIdentify?.({ status: 'shed' });
|
|
1506
|
+
}
|
|
1507
|
+
else if (res.status === 'error') {
|
|
1508
|
+
beforeResult?.afterIdentify?.({ status: 'error' });
|
|
1509
|
+
}
|
|
1510
|
+
},
|
|
1511
|
+
}, identifyOptions?.sheddable ?? false)
|
|
1512
|
+
.then((res) => {
|
|
1513
|
+
if (res.status === 'error') {
|
|
1514
|
+
return { status: 'error', error: res.error };
|
|
1515
|
+
}
|
|
1516
|
+
if (res.status === 'shed') {
|
|
1517
|
+
return { status: 'shed' };
|
|
1518
|
+
}
|
|
1519
|
+
return { status: 'completed' };
|
|
1520
|
+
});
|
|
1521
|
+
if (noTimeout) {
|
|
1522
|
+
return callSitePromise;
|
|
1523
|
+
}
|
|
1524
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
1525
|
+
setTimeout(() => {
|
|
1526
|
+
resolve({ status: 'timeout', timeout: identifyTimeout });
|
|
1527
|
+
}, identifyTimeout * 1000);
|
|
1338
1528
|
});
|
|
1529
|
+
return Promise.race([callSitePromise, timeoutPromise]);
|
|
1339
1530
|
}
|
|
1340
1531
|
on(eventName, listener) {
|
|
1341
1532
|
this.emitter.on(eventName, listener);
|
|
@@ -1632,6 +1823,9 @@ class DataSourceStatusManager {
|
|
|
1632
1823
|
}
|
|
1633
1824
|
}
|
|
1634
1825
|
|
|
1826
|
+
function reportClosed(logger) {
|
|
1827
|
+
logger?.debug(`Poll completed after the processor was closed. Skipping processing.`);
|
|
1828
|
+
}
|
|
1635
1829
|
/**
|
|
1636
1830
|
* @internal
|
|
1637
1831
|
*/
|
|
@@ -1658,6 +1852,12 @@ class PollingProcessor {
|
|
|
1658
1852
|
try {
|
|
1659
1853
|
const res = await this._requestor.requestPayload();
|
|
1660
1854
|
try {
|
|
1855
|
+
// If the processor has been stopped, we discard the response.
|
|
1856
|
+
// This response could be for a no longer active context.
|
|
1857
|
+
if (this._stopped) {
|
|
1858
|
+
reportClosed(this._logger);
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1661
1861
|
const flags = JSON.parse(res);
|
|
1662
1862
|
try {
|
|
1663
1863
|
this._dataHandler?.(flags);
|
|
@@ -1671,6 +1871,12 @@ class PollingProcessor {
|
|
|
1671
1871
|
}
|
|
1672
1872
|
}
|
|
1673
1873
|
catch (err) {
|
|
1874
|
+
// If the processor has been stopped, we discard this error.
|
|
1875
|
+
// The original caller would consider this connection no longer active.
|
|
1876
|
+
if (this._stopped) {
|
|
1877
|
+
reportClosed(this._logger);
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1674
1880
|
const requestError = err;
|
|
1675
1881
|
if (requestError.status !== undefined) {
|
|
1676
1882
|
if (!isHttpRecoverable(requestError.status)) {
|
|
@@ -1708,6 +1914,12 @@ const reportJsonError = (type, data, logger, errorHandler) => {
|
|
|
1708
1914
|
logger?.debug(`Invalid JSON follows: ${data}`);
|
|
1709
1915
|
errorHandler?.(new LDStreamingError(DataSourceErrorKind.InvalidData, 'Malformed JSON data in event stream'));
|
|
1710
1916
|
};
|
|
1917
|
+
function reportEventClosed(eventName, logger) {
|
|
1918
|
+
logger?.debug(`Received ${eventName} event after processor was closed. Skipping processing.`);
|
|
1919
|
+
}
|
|
1920
|
+
function reportPingClosed(logger) {
|
|
1921
|
+
logger?.debug('Ping completed after processor was closed. Skipping processing.');
|
|
1922
|
+
}
|
|
1711
1923
|
class StreamingProcessor {
|
|
1712
1924
|
constructor(_plainContextString, _dataSourceConfig, _listeners, _requests, encoding, _pollingRequestor, _diagnosticsManager, _errorHandler, _logger) {
|
|
1713
1925
|
this._plainContextString = _plainContextString;
|
|
@@ -1718,6 +1930,7 @@ class StreamingProcessor {
|
|
|
1718
1930
|
this._diagnosticsManager = _diagnosticsManager;
|
|
1719
1931
|
this._errorHandler = _errorHandler;
|
|
1720
1932
|
this._logger = _logger;
|
|
1933
|
+
this._stopped = false;
|
|
1721
1934
|
let path;
|
|
1722
1935
|
if (_dataSourceConfig.useReport && !_requests.getEventSourceCapabilities().customMethod) {
|
|
1723
1936
|
path = _dataSourceConfig.paths.pathPing(encoding, _plainContextString);
|
|
@@ -1805,6 +2018,12 @@ class StreamingProcessor {
|
|
|
1805
2018
|
};
|
|
1806
2019
|
this._listeners.forEach(({ deserializeData, processJson }, eventName) => {
|
|
1807
2020
|
eventSource.addEventListener(eventName, (event) => {
|
|
2021
|
+
// If an event comes in after the processor has been stopped, we skip processing it.
|
|
2022
|
+
// This event could be for a context which is no longer active.
|
|
2023
|
+
if (this._stopped) {
|
|
2024
|
+
reportEventClosed(eventName, this._logger);
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
1808
2027
|
this._logger?.debug(`Received ${eventName} event`);
|
|
1809
2028
|
if (event?.data) {
|
|
1810
2029
|
this._logConnectionResult(true);
|
|
@@ -1827,6 +2046,12 @@ class StreamingProcessor {
|
|
|
1827
2046
|
try {
|
|
1828
2047
|
const res = await this._pollingRequestor.requestPayload();
|
|
1829
2048
|
try {
|
|
2049
|
+
// If the ping completes after the processor has been stopped, then we discard it.
|
|
2050
|
+
// This event could be for a context which is no longer active.
|
|
2051
|
+
if (this._stopped) {
|
|
2052
|
+
reportPingClosed(this._logger);
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
1830
2055
|
const payload = JSON.parse(res);
|
|
1831
2056
|
try {
|
|
1832
2057
|
// forward the payload on to the PUT listener
|
|
@@ -1843,6 +2068,12 @@ class StreamingProcessor {
|
|
|
1843
2068
|
}
|
|
1844
2069
|
}
|
|
1845
2070
|
catch (err) {
|
|
2071
|
+
if (this._stopped) {
|
|
2072
|
+
// If the ping errors after the processor has been stopped, then we discard it.
|
|
2073
|
+
// The original caller would consider this connection no longer active.
|
|
2074
|
+
reportPingClosed(this._logger);
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
1846
2077
|
const requestError = err;
|
|
1847
2078
|
this._errorHandler?.(new LDPollingError(DataSourceErrorKind.ErrorResponse, requestError.message, requestError.status));
|
|
1848
2079
|
}
|
|
@@ -1851,6 +2082,7 @@ class StreamingProcessor {
|
|
|
1851
2082
|
stop() {
|
|
1852
2083
|
this._eventSource?.close();
|
|
1853
2084
|
this._eventSource = undefined;
|
|
2085
|
+
this._stopped = true;
|
|
1854
2086
|
}
|
|
1855
2087
|
close() {
|
|
1856
2088
|
this.stop();
|