@splitsoftware/splitio-commons 1.2.1-rc.7 → 1.3.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/CHANGES.txt +14 -0
- package/cjs/{utils/consent.js → consent/index.js} +1 -1
- package/cjs/consent/sdkUserConsent.js +58 -0
- package/cjs/listeners/browser.js +1 -1
- package/cjs/logger/constants.js +3 -2
- package/cjs/logger/messages/info.js +1 -0
- package/cjs/sdkClient/client.js +4 -9
- package/cjs/sdkClient/clientCS.js +1 -1
- package/cjs/sdkClient/clientInputValidation.js +6 -8
- package/cjs/sdkClient/sdkClient.js +4 -7
- package/cjs/sdkClient/sdkClientMethodCS.js +3 -9
- package/cjs/sdkClient/sdkClientMethodCSWithTT.js +3 -13
- package/cjs/sdkFactory/index.js +6 -5
- package/cjs/storages/inRedis/RedisAdapter.js +9 -2
- package/cjs/sync/syncManagerOnline.js +1 -1
- package/cjs/trackers/eventTracker.js +8 -1
- package/cjs/trackers/impressionObserver/utils.js +8 -1
- package/cjs/trackers/impressionsTracker.js +6 -5
- package/cjs/utils/inputValidation/attributes.js +1 -1
- package/cjs/utils/lang/index.js +4 -2
- package/cjs/utils/lang/maps.js +16 -2
- package/cjs/utils/settingsValidation/index.js +21 -3
- package/esm/{utils/consent.js → consent/index.js} +1 -1
- package/esm/consent/sdkUserConsent.js +54 -0
- package/esm/listeners/browser.js +1 -1
- package/esm/logger/constants.js +1 -0
- package/esm/logger/messages/info.js +1 -0
- package/esm/sdkClient/client.js +5 -10
- package/esm/sdkClient/clientCS.js +1 -1
- package/esm/sdkClient/clientInputValidation.js +6 -8
- package/esm/sdkClient/sdkClient.js +4 -7
- package/esm/sdkClient/sdkClientMethodCS.js +3 -9
- package/esm/sdkClient/sdkClientMethodCSWithTT.js +3 -13
- package/esm/sdkFactory/index.js +6 -5
- package/esm/storages/inRedis/RedisAdapter.js +9 -2
- package/esm/sync/syncManagerOnline.js +1 -1
- package/esm/trackers/eventTracker.js +8 -1
- package/esm/trackers/impressionObserver/utils.js +7 -1
- package/esm/trackers/impressionsTracker.js +6 -5
- package/esm/utils/inputValidation/attributes.js +1 -1
- package/esm/utils/lang/index.js +4 -2
- package/esm/utils/lang/maps.js +14 -1
- package/esm/utils/settingsValidation/index.js +21 -3
- package/package.json +1 -1
- package/src/{utils/consent.ts → consent/index.ts} +1 -1
- package/src/consent/sdkUserConsent.ts +58 -0
- package/src/evaluator/parser/index.ts +1 -1
- package/src/evaluator/types.ts +2 -2
- package/src/evaluator/value/index.ts +2 -2
- package/src/evaluator/value/sanitize.ts +2 -2
- package/src/integrations/pluggable.ts +2 -2
- package/src/listeners/browser.ts +1 -1
- package/src/logger/constants.ts +1 -0
- package/src/logger/messages/info.ts +1 -0
- package/src/sdkClient/client.ts +7 -9
- package/src/sdkClient/clientCS.ts +1 -1
- package/src/sdkClient/clientInputValidation.ts +8 -7
- package/src/sdkClient/sdkClient.ts +6 -9
- package/src/sdkClient/sdkClientMethod.ts +2 -2
- package/src/sdkClient/sdkClientMethodCS.ts +5 -11
- package/src/sdkClient/sdkClientMethodCSWithTT.ts +6 -16
- package/src/sdkFactory/index.ts +6 -5
- package/src/sdkFactory/types.ts +13 -4
- package/src/storages/inRedis/RedisAdapter.ts +8 -2
- package/src/sync/syncManagerOnline.ts +1 -1
- package/src/trackers/eventTracker.ts +11 -3
- package/src/trackers/impressionObserver/utils.ts +8 -1
- package/src/trackers/impressionsTracker.ts +7 -8
- package/src/types.ts +1 -1
- package/src/utils/inputValidation/attributes.ts +1 -2
- package/src/utils/lang/index.ts +7 -3
- package/src/utils/lang/maps.ts +15 -1
- package/src/utils/settingsValidation/index.ts +21 -3
- package/src/utils/settingsValidation/types.ts +5 -1
- package/types/consent/index.d.ts +2 -0
- package/types/consent/sdkUserConsent.d.ts +13 -0
- package/types/evaluator/types.d.ts +2 -2
- package/types/evaluator/value/index.d.ts +1 -1
- package/types/evaluator/value/sanitize.d.ts +1 -1
- package/types/logger/constants.d.ts +1 -0
- package/types/sdkClient/client.d.ts +2 -2
- package/types/sdkClient/clientInputValidation.d.ts +2 -3
- package/types/sdkClient/sdkClient.d.ts +2 -2
- package/types/sdkClient/sdkClientMethod.d.ts +2 -2
- package/types/sdkClient/sdkClientMethodCS.d.ts +2 -2
- package/types/sdkClient/sdkClientMethodCSWithTT.d.ts +2 -2
- package/types/sdkFactory/types.d.ts +12 -4
- package/types/storages/inRedis/RedisAdapter.d.ts +1 -1
- package/types/trackers/eventTracker.d.ts +2 -2
- package/types/trackers/impressionObserver/utils.d.ts +4 -0
- package/types/trackers/impressionsTracker.d.ts +2 -3
- package/types/types.d.ts +1 -1
- package/types/utils/lang/index.d.ts +2 -1
- package/types/utils/lang/maps.d.ts +7 -0
- package/types/utils/settingsValidation/types.d.ts +5 -1
- package/cjs/sdkClient/types.js +0 -2
- package/cjs/sdkFactory/userConsentProps.js +0 -37
- package/esm/sdkClient/types.js +0 -1
- package/esm/sdkFactory/userConsentProps.js +0 -33
- package/src/sdkClient/types.ts +0 -21
- package/src/sdkFactory/userConsentProps.ts +0 -40
|
@@ -4,7 +4,7 @@ import { ILogger } from '../../logger/types';
|
|
|
4
4
|
import { sanitize } from './sanitize';
|
|
5
5
|
import { ENGINE_VALUE, ENGINE_VALUE_NO_ATTRIBUTES, ENGINE_VALUE_INVALID } from '../../logger/constants';
|
|
6
6
|
|
|
7
|
-
function parseValue(log: ILogger, key: string, attributeName: string | null, attributes
|
|
7
|
+
function parseValue(log: ILogger, key: string, attributeName: string | null, attributes?: SplitIO.Attributes) {
|
|
8
8
|
let value = undefined;
|
|
9
9
|
if (attributeName) {
|
|
10
10
|
if (attributes) {
|
|
@@ -23,7 +23,7 @@ function parseValue(log: ILogger, key: string, attributeName: string | null, att
|
|
|
23
23
|
/**
|
|
24
24
|
* Defines value to be matched (key / attribute).
|
|
25
25
|
*/
|
|
26
|
-
export function sanitizeValue(log: ILogger, key: string, matcherDto: IMatcherDto, attributes
|
|
26
|
+
export function sanitizeValue(log: ILogger, key: string, matcherDto: IMatcherDto, attributes?: SplitIO.Attributes) {
|
|
27
27
|
const attributeName = matcherDto.attribute;
|
|
28
28
|
const valueToMatch = parseValue(log, key, attributeName, attributes);
|
|
29
29
|
const sanitizedValue = sanitize(log, matcherDto.type, valueToMatch, matcherDto.dataType, attributes);
|
|
@@ -41,7 +41,7 @@ function sanitizeBoolean(val: any): boolean | undefined {
|
|
|
41
41
|
return undefined;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function dependencyProcessor(sanitizedValue: string, attributes
|
|
44
|
+
function dependencyProcessor(sanitizedValue: string, attributes?: SplitIO.Attributes): IDependencyMatcherValue {
|
|
45
45
|
return {
|
|
46
46
|
key: sanitizedValue,
|
|
47
47
|
attributes
|
|
@@ -69,7 +69,7 @@ function getProcessingFunction(matcherTypeID: number, dataType: string) {
|
|
|
69
69
|
/**
|
|
70
70
|
* Sanitize matcher value
|
|
71
71
|
*/
|
|
72
|
-
export function sanitize(log: ILogger, matcherTypeID: number, value: string | number | boolean | Array<string | number> | undefined, dataType: string, attributes
|
|
72
|
+
export function sanitize(log: ILogger, matcherTypeID: number, value: string | number | boolean | Array<string | number> | undefined, dataType: string, attributes?: SplitIO.Attributes) {
|
|
73
73
|
const processor = getProcessingFunction(matcherTypeID, dataType);
|
|
74
74
|
let sanitizedValue: string | number | boolean | Array<string | number> | IDependencyMatcherValue | undefined;
|
|
75
75
|
|
|
@@ -29,10 +29,10 @@ export function pluggableIntegrationsManagerFactory(
|
|
|
29
29
|
|
|
30
30
|
// Exception safe methods: each integration module is responsable for handling errors
|
|
31
31
|
return {
|
|
32
|
-
handleImpression
|
|
32
|
+
handleImpression(impressionData: SplitIO.ImpressionData) {
|
|
33
33
|
listeners.forEach(listener => listener.queue({ type: SPLIT_IMPRESSION, payload: impressionData }));
|
|
34
34
|
},
|
|
35
|
-
handleEvent
|
|
35
|
+
handleEvent(eventData: SplitIO.EventData) {
|
|
36
36
|
listeners.forEach(listener => listener.queue({ type: SPLIT_EVENT, payload: eventData }));
|
|
37
37
|
}
|
|
38
38
|
};
|
package/src/listeners/browser.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { OPTIMIZED, DEBUG } from '../utils/constants';
|
|
|
11
11
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
12
12
|
import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants';
|
|
13
13
|
import { ISyncManager } from '../sync/types';
|
|
14
|
-
import { isConsentGranted } from '../
|
|
14
|
+
import { isConsentGranted } from '../consent';
|
|
15
15
|
|
|
16
16
|
// 'unload' event is used instead of 'beforeunload', since 'unload' is not a cancelable event, so no other listeners can stop the event from occurring.
|
|
17
17
|
const UNLOAD_DOM_EVENT = 'unload';
|
package/src/logger/constants.ts
CHANGED
|
@@ -70,6 +70,7 @@ export const EVENTS_TRACKER_SUCCESS = 120;
|
|
|
70
70
|
export const IMPRESSIONS_TRACKER_SUCCESS = 121;
|
|
71
71
|
export const USER_CONSENT_UPDATED = 122;
|
|
72
72
|
export const USER_CONSENT_NOT_UPDATED = 123;
|
|
73
|
+
export const USER_CONSENT_INITIAL = 124;
|
|
73
74
|
|
|
74
75
|
export const ENGINE_VALUE_INVALID = 200;
|
|
75
76
|
export const ENGINE_VALUE_NO_ATTRIBUTES = 201;
|
|
@@ -16,6 +16,7 @@ export const codesInfo: [number, string][] = codesWarn.concat([
|
|
|
16
16
|
[c.IMPRESSIONS_TRACKER_SUCCESS, c.LOG_PREFIX_IMPRESSIONS_TRACKER + 'Successfully stored %s impression(s).'],
|
|
17
17
|
[c.USER_CONSENT_UPDATED, 'setUserConsent: consent status changed from %s to %s.'],
|
|
18
18
|
[c.USER_CONSENT_NOT_UPDATED, 'setUserConsent: call had no effect because it was the current consent status (%s).'],
|
|
19
|
+
[c.USER_CONSENT_INITIAL, 'Starting the SDK with %s user consent. No data will be sent.'],
|
|
19
20
|
|
|
20
21
|
// synchronizer
|
|
21
22
|
[c.POLLING_SMART_PAUSING, c.LOG_PREFIX_SYNC_POLLING + 'Turning segments data polling %s.'],
|
package/src/sdkClient/client.ts
CHANGED
|
@@ -4,18 +4,17 @@ import { getMatching, getBucketing } from '../utils/key';
|
|
|
4
4
|
import { validateSplitExistance } from '../utils/inputValidation/splitExistance';
|
|
5
5
|
import { validateTrafficTypeExistance } from '../utils/inputValidation/trafficTypeExistance';
|
|
6
6
|
import { SDK_NOT_READY } from '../utils/labels';
|
|
7
|
-
import {
|
|
8
|
-
import { IClientFactoryParams } from './types';
|
|
7
|
+
import { CONTROL } from '../utils/constants';
|
|
9
8
|
import { IEvaluationResult } from '../evaluator/types';
|
|
10
9
|
import { SplitIO, ImpressionDTO } from '../types';
|
|
11
10
|
import { IMPRESSION, IMPRESSION_QUEUEING } from '../logger/constants';
|
|
12
|
-
|
|
11
|
+
import { ISdkFactoryContext } from '../sdkFactory/types';
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* Creator of base client with getTreatments and track methods.
|
|
16
15
|
*/
|
|
17
16
|
// @TODO missing time tracking to collect telemetry
|
|
18
|
-
export function clientFactory(params:
|
|
17
|
+
export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | SplitIO.IAsyncClient {
|
|
19
18
|
const { sdkReadinessManager: { readinessManager }, storage, settings, impressionsTracker, eventTracker } = params;
|
|
20
19
|
const { log, mode } = settings;
|
|
21
20
|
|
|
@@ -23,7 +22,7 @@ export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | S
|
|
|
23
22
|
const wrapUp = (evaluationResult: IEvaluationResult) => {
|
|
24
23
|
const queue: ImpressionDTO[] = [];
|
|
25
24
|
const treatment = processEvaluation(evaluationResult, splitName, key, attributes, withConfig, `getTreatment${withConfig ? 'withConfig' : ''}`, queue);
|
|
26
|
-
|
|
25
|
+
impressionsTracker.track(queue, attributes);
|
|
27
26
|
return treatment;
|
|
28
27
|
};
|
|
29
28
|
|
|
@@ -43,7 +42,7 @@ export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | S
|
|
|
43
42
|
Object.keys(evaluationResults).forEach(splitName => {
|
|
44
43
|
treatments[splitName] = processEvaluation(evaluationResults[splitName], splitName, key, attributes, withConfig, `getTreatments${withConfig ? 'withConfig' : ''}`, queue);
|
|
45
44
|
});
|
|
46
|
-
|
|
45
|
+
impressionsTracker.track(queue, attributes);
|
|
47
46
|
return treatments;
|
|
48
47
|
};
|
|
49
48
|
|
|
@@ -116,8 +115,7 @@ export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | S
|
|
|
116
115
|
// This may be async but we only warn, we don't actually care if it is valid or not in terms of queueing the event.
|
|
117
116
|
validateTrafficTypeExistance(log, readinessManager, storage.splits, mode, trafficTypeName, 'track');
|
|
118
117
|
|
|
119
|
-
|
|
120
|
-
else return false;
|
|
118
|
+
return eventTracker.track(eventData, size);
|
|
121
119
|
}
|
|
122
120
|
|
|
123
121
|
return {
|
|
@@ -126,6 +124,6 @@ export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | S
|
|
|
126
124
|
getTreatments,
|
|
127
125
|
getTreatmentsWithConfig,
|
|
128
126
|
track,
|
|
129
|
-
|
|
127
|
+
isClientSide: false
|
|
130
128
|
} as SplitIO.IClient | SplitIO.IAsyncClient;
|
|
131
129
|
}
|
|
@@ -25,6 +25,6 @@ export function clientCSDecorator(log: ILogger, client: SplitIO.IClient, key: Sp
|
|
|
25
25
|
// Key is bound to the `track` method. Same thing happens with trafficType but only if provided
|
|
26
26
|
track: trafficType ? clientCS.track.bind(clientCS, key, trafficType) : clientCS.track.bind(clientCS, key),
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
isClientSide: true
|
|
29
29
|
}) as SplitIO.ICsClient;
|
|
30
30
|
}
|
|
@@ -15,14 +15,17 @@ import { startsWith } from '../utils/lang';
|
|
|
15
15
|
import { CONTROL, CONTROL_WITH_CONFIG } from '../utils/constants';
|
|
16
16
|
import { IReadinessManager } from '../readiness/types';
|
|
17
17
|
import { MaybeThenable } from '../dtos/types';
|
|
18
|
-
import { SplitIO } from '../types';
|
|
19
|
-
import {
|
|
18
|
+
import { ISettings, SplitIO } from '../types';
|
|
19
|
+
import { isStorageSync } from '../trackers/impressionObserver/utils';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Decorator that validates the input before actually executing the client methods.
|
|
23
23
|
* We should "guard" the client here, while not polluting the "real" implementation of those methods.
|
|
24
24
|
*/
|
|
25
|
-
export function clientInputValidationDecorator<TClient extends SplitIO.IClient | SplitIO.IAsyncClient>(
|
|
25
|
+
export function clientInputValidationDecorator<TClient extends SplitIO.IClient | SplitIO.IAsyncClient>(settings: ISettings, client: TClient, readinessManager: IReadinessManager): TClient {
|
|
26
|
+
|
|
27
|
+
const log = settings.log;
|
|
28
|
+
const isSync = isStorageSync(settings);
|
|
26
29
|
|
|
27
30
|
/**
|
|
28
31
|
* Avoid repeating this validations code
|
|
@@ -47,8 +50,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
function wrapResult<T>(value: T): MaybeThenable<T> {
|
|
50
|
-
|
|
51
|
-
return Promise.resolve(value);
|
|
53
|
+
return isSync ? value : Promise.resolve(value);
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
function getTreatment(maybeKey: SplitIO.SplitKey, maybeSplit: string, maybeAttributes?: SplitIO.Attributes) {
|
|
@@ -108,8 +110,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
|
|
|
108
110
|
if (isOperational && key && tt && event && eventValue !== false && properties !== false) { // @ts-expect-error
|
|
109
111
|
return client.track(key, tt, event, eventValue, properties, size);
|
|
110
112
|
} else {
|
|
111
|
-
|
|
112
|
-
return Promise.resolve(false);
|
|
113
|
+
return isSync ? false : Promise.resolve(false);
|
|
113
114
|
}
|
|
114
115
|
}
|
|
115
116
|
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
2
2
|
import { IStatusInterface, SplitIO } from '../types';
|
|
3
|
-
import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE } from '../utils/constants';
|
|
4
3
|
import { releaseApiKey } from '../utils/inputValidation/apiKey';
|
|
5
4
|
import { clientFactory } from './client';
|
|
6
5
|
import { clientInputValidationDecorator } from './clientInputValidation';
|
|
7
|
-
import {
|
|
6
|
+
import { ISdkFactoryContext } from '../sdkFactory/types';
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Creates an Sdk client, i.e., a base client with status and destroy interface
|
|
11
10
|
*/
|
|
12
|
-
export function sdkClientFactory(params:
|
|
13
|
-
const { sdkReadinessManager, syncManager, storage, signalListener, settings
|
|
11
|
+
export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: boolean): SplitIO.IClient | SplitIO.IAsyncClient {
|
|
12
|
+
const { sdkReadinessManager, syncManager, storage, signalListener, settings } = params;
|
|
14
13
|
|
|
15
14
|
return objectAssign(
|
|
16
15
|
// Proto-linkage of the readiness Event Emitter
|
|
@@ -18,11 +17,9 @@ export function sdkClientFactory(params: ISdkClientFactoryParams): SplitIO.IClie
|
|
|
18
17
|
|
|
19
18
|
// Client API (getTreatment* & track methods)
|
|
20
19
|
clientInputValidationDecorator(
|
|
21
|
-
settings
|
|
20
|
+
settings,
|
|
22
21
|
clientFactory(params),
|
|
23
|
-
sdkReadinessManager.readinessManager
|
|
24
|
-
// storage is async if and only if mode is consumer or partial consumer
|
|
25
|
-
[CONSUMER_MODE, CONSUMER_PARTIAL_MODE].indexOf(settings.mode) === -1 ? true : false,
|
|
22
|
+
sdkReadinessManager.readinessManager
|
|
26
23
|
),
|
|
27
24
|
|
|
28
25
|
// Sdk destroy
|
|
@@ -38,7 +35,7 @@ export function sdkClientFactory(params: ISdkClientFactoryParams): SplitIO.IClie
|
|
|
38
35
|
signalListener && signalListener.stop();
|
|
39
36
|
|
|
40
37
|
// Release the API Key if it is the main client
|
|
41
|
-
if (!
|
|
38
|
+
if (!isSharedClient) releaseApiKey(settings.core.authorizationKey);
|
|
42
39
|
|
|
43
40
|
// Cleanup storage
|
|
44
41
|
return storage.destroy();
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { ISdkClientFactoryParams } from './types';
|
|
2
1
|
import { SplitIO } from '../types';
|
|
3
2
|
import { sdkClientFactory } from './sdkClient';
|
|
4
3
|
import { RETRIEVE_CLIENT_DEFAULT } from '../logger/constants';
|
|
4
|
+
import { ISdkFactoryContext } from '../sdkFactory/types';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Factory of client method for server-side SDKs (ISDK and IAsyncSDK)
|
|
8
8
|
*/
|
|
9
|
-
export function sdkClientMethodFactory(params:
|
|
9
|
+
export function sdkClientMethodFactory(params: ISdkFactoryContext): () => SplitIO.IClient | SplitIO.IAsyncClient {
|
|
10
10
|
const log = params.settings.log;
|
|
11
11
|
const clientInstance = sdkClientFactory(params);
|
|
12
12
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { clientCSDecorator } from './clientCS';
|
|
2
|
-
import { ISdkClientFactoryParams } from './types';
|
|
3
2
|
import { SplitIO } from '../types';
|
|
4
3
|
import { validateKey } from '../utils/inputValidation/key';
|
|
5
4
|
import { getMatching, keyParser } from '../utils/key';
|
|
@@ -8,6 +7,7 @@ import { ISyncManagerCS } from '../sync/types';
|
|
|
8
7
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
9
8
|
import { RETRIEVE_CLIENT_DEFAULT, NEW_SHARED_CLIENT, RETRIEVE_CLIENT_EXISTING } from '../logger/constants';
|
|
10
9
|
import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
|
|
10
|
+
import { ISdkFactoryContext } from '../sdkFactory/types';
|
|
11
11
|
|
|
12
12
|
function buildInstanceId(key: SplitIO.SplitKey) {
|
|
13
13
|
// @ts-ignore
|
|
@@ -20,18 +20,13 @@ const method = 'Client instantiation';
|
|
|
20
20
|
* Factory of client method for the client-side API variant where TT is ignored and thus
|
|
21
21
|
* clients don't have a binded TT for the track method.
|
|
22
22
|
*/
|
|
23
|
-
export function sdkClientMethodCSFactory(params:
|
|
23
|
+
export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: SplitIO.SplitKey) => SplitIO.ICsClient {
|
|
24
24
|
const { storage, syncManager, sdkReadinessManager, settings: { core: { key }, startup: { readyTimeout }, log } } = params;
|
|
25
25
|
|
|
26
|
-
// Keeping similar behaviour as in the isomorphic JS SDK: if settings key is invalid,
|
|
27
|
-
// `false` value is used as binded key of the default client, but trafficType is ignored
|
|
28
|
-
// @TODO handle as a non-recoverable error
|
|
29
|
-
const validKey = validateKey(log, key, method);
|
|
30
|
-
|
|
31
26
|
const mainClientInstance = clientCSDecorator(
|
|
32
27
|
log,
|
|
33
|
-
sdkClientFactory(params) as SplitIO.IClient,
|
|
34
|
-
|
|
28
|
+
sdkClientFactory(params) as SplitIO.IClient,
|
|
29
|
+
key
|
|
35
30
|
);
|
|
36
31
|
|
|
37
32
|
const parsedDefaultKey = keyParser(key);
|
|
@@ -81,8 +76,7 @@ export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key?
|
|
|
81
76
|
storage: sharedStorage || storage,
|
|
82
77
|
syncManager: sharedSyncManager,
|
|
83
78
|
signalListener: undefined, // only the main client "destroy" method stops the signal listener
|
|
84
|
-
|
|
85
|
-
})) as SplitIO.IClient,
|
|
79
|
+
}), true) as SplitIO.IClient,
|
|
86
80
|
validKey
|
|
87
81
|
);
|
|
88
82
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { clientCSDecorator } from './clientCS';
|
|
2
|
-
import { ISdkClientFactoryParams } from './types';
|
|
3
2
|
import { SplitIO } from '../types';
|
|
4
3
|
import { validateKey } from '../utils/inputValidation/key';
|
|
5
4
|
import { validateTrafficType } from '../utils/inputValidation/trafficType';
|
|
@@ -9,6 +8,7 @@ import { ISyncManagerCS } from '../sync/types';
|
|
|
9
8
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
10
9
|
import { RETRIEVE_CLIENT_DEFAULT, NEW_SHARED_CLIENT, RETRIEVE_CLIENT_EXISTING } from '../logger/constants';
|
|
11
10
|
import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
|
|
11
|
+
import { ISdkFactoryContext } from '../sdkFactory/types';
|
|
12
12
|
|
|
13
13
|
function buildInstanceId(key: SplitIO.SplitKey, trafficType?: string) {
|
|
14
14
|
// @ts-ignore
|
|
@@ -22,23 +22,14 @@ const method = 'Client instantiation';
|
|
|
22
22
|
* where clients can have a binded TT for the track method, which is provided via the settings
|
|
23
23
|
* (default client) or the client method (shared clients).
|
|
24
24
|
*/
|
|
25
|
-
export function sdkClientMethodCSFactory(params:
|
|
25
|
+
export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: SplitIO.SplitKey, trafficType?: string) => SplitIO.ICsClient {
|
|
26
26
|
const { storage, syncManager, sdkReadinessManager, settings: { core: { key, trafficType }, startup: { readyTimeout }, log } } = params;
|
|
27
27
|
|
|
28
|
-
// Keeping the behaviour as in the isomorphic JS SDK: if settings key or TT are invalid,
|
|
29
|
-
// `false` value is used as binded key/TT of the default client, which leads to several issues.
|
|
30
|
-
// @TODO update when supporting non-recoverable errors
|
|
31
|
-
const validKey = validateKey(log, key, method);
|
|
32
|
-
let validTrafficType;
|
|
33
|
-
if (trafficType !== undefined) {
|
|
34
|
-
validTrafficType = validateTrafficType(log, trafficType, method);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
28
|
const mainClientInstance = clientCSDecorator(
|
|
38
29
|
log,
|
|
39
|
-
sdkClientFactory(params) as SplitIO.IClient,
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
sdkClientFactory(params) as SplitIO.IClient,
|
|
31
|
+
key,
|
|
32
|
+
trafficType
|
|
42
33
|
);
|
|
43
34
|
|
|
44
35
|
const parsedDefaultKey = keyParser(key);
|
|
@@ -95,8 +86,7 @@ export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key?
|
|
|
95
86
|
storage: sharedStorage || storage,
|
|
96
87
|
syncManager: sharedSyncManager,
|
|
97
88
|
signalListener: undefined, // only the main client "destroy" method stops the signal listener
|
|
98
|
-
|
|
99
|
-
})) as SplitIO.IClient,
|
|
89
|
+
}), true) as SplitIO.IClient,
|
|
100
90
|
validKey,
|
|
101
91
|
validTrafficType
|
|
102
92
|
);
|
package/src/sdkFactory/index.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { objectAssign } from '../utils/lang/objectAssign';
|
|
|
20
20
|
export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.ISDK | SplitIO.IAsyncSDK {
|
|
21
21
|
|
|
22
22
|
const { settings, platform, storageFactory, splitApiFactory, extraProps,
|
|
23
|
-
syncManagerFactory, SignalListener, impressionsObserverFactory,
|
|
23
|
+
syncManagerFactory, SignalListener, impressionsObserverFactory,
|
|
24
24
|
integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory } = params;
|
|
25
25
|
const log = settings.log;
|
|
26
26
|
|
|
@@ -74,14 +74,15 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
74
74
|
|
|
75
75
|
// trackers
|
|
76
76
|
const observer = impressionsObserverFactory && impressionsObserverFactory();
|
|
77
|
-
const impressionsTracker = impressionsTrackerFactory(
|
|
78
|
-
const eventTracker = eventTrackerFactory(
|
|
77
|
+
const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, integrationsManager, observer, storage.impressionCounts);
|
|
78
|
+
const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager);
|
|
79
79
|
|
|
80
80
|
// signal listener
|
|
81
81
|
const signalListener = SignalListener && new SignalListener(syncManager, settings, storage, splitApi);
|
|
82
82
|
|
|
83
83
|
// Sdk client and manager
|
|
84
|
-
const
|
|
84
|
+
const ctx = { eventTracker, impressionsTracker, sdkReadinessManager, settings, storage, syncManager, signalListener };
|
|
85
|
+
const clientMethod = sdkClientMethodFactory(ctx);
|
|
85
86
|
const managerInstance = sdkManagerFactory(log, storage.splits, sdkReadinessManager);
|
|
86
87
|
|
|
87
88
|
syncManager && syncManager.start();
|
|
@@ -104,5 +105,5 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
|
|
|
104
105
|
Logger: createLoggerAPI(settings.log),
|
|
105
106
|
|
|
106
107
|
settings,
|
|
107
|
-
}, extraProps && extraProps(
|
|
108
|
+
}, extraProps && extraProps(ctx));
|
|
108
109
|
}
|
package/src/sdkFactory/types.ts
CHANGED
|
@@ -2,13 +2,23 @@ import { IIntegrationManager, IIntegrationFactoryParams } from '../integrations/
|
|
|
2
2
|
import { ISignalListener } from '../listeners/types';
|
|
3
3
|
import { ILogger } from '../logger/types';
|
|
4
4
|
import { ISdkReadinessManager } from '../readiness/types';
|
|
5
|
-
import { ISdkClientFactoryParams } from '../sdkClient/types';
|
|
6
5
|
import { IFetch, ISplitApi, IEventSourceConstructor } from '../services/types';
|
|
7
6
|
import { IStorageAsync, IStorageSync, ISplitsCacheSync, ISplitsCacheAsync, IStorageFactoryParams } from '../storages/types';
|
|
8
7
|
import { ISyncManager, ISyncManagerFactoryParams } from '../sync/types';
|
|
9
8
|
import { IImpressionObserver } from '../trackers/impressionObserver/types';
|
|
9
|
+
import { IImpressionsTracker, IEventTracker } from '../trackers/types';
|
|
10
10
|
import { SplitIO, ISettings, IEventEmitter } from '../types';
|
|
11
11
|
|
|
12
|
+
export interface ISdkFactoryContext {
|
|
13
|
+
storage: IStorageSync | IStorageAsync,
|
|
14
|
+
sdkReadinessManager: ISdkReadinessManager,
|
|
15
|
+
settings: ISettings
|
|
16
|
+
impressionsTracker: IImpressionsTracker,
|
|
17
|
+
eventTracker: IEventTracker,
|
|
18
|
+
signalListener?: ISignalListener
|
|
19
|
+
syncManager?: ISyncManager,
|
|
20
|
+
}
|
|
21
|
+
|
|
12
22
|
/**
|
|
13
23
|
* Environment related dependencies.
|
|
14
24
|
* These getters are called a fixed number of times per factory instantiation.
|
|
@@ -53,7 +63,7 @@ export interface ISdkFactoryParams {
|
|
|
53
63
|
|
|
54
64
|
// Sdk client method factory (ISDK::client method).
|
|
55
65
|
// It Allows to distinguish SDK clients with the client-side API (`ICsSDK`) or server-side API (`ISDK` or `IAsyncSDK`).
|
|
56
|
-
sdkClientMethodFactory: (params:
|
|
66
|
+
sdkClientMethodFactory: (params: ISdkFactoryContext) => ({ (): SplitIO.ICsClient; (key: SplitIO.SplitKey, trafficType?: string | undefined): SplitIO.ICsClient; } | (() => SplitIO.IClient) | (() => SplitIO.IAsyncClient))
|
|
57
67
|
|
|
58
68
|
// Optional signal listener constructor. Used to handle special app states, like shutdown, app paused or resumed.
|
|
59
69
|
// Pass only if `syncManager` (used by Node listener) and `splitApi` (used by Browser listener) are passed.
|
|
@@ -64,12 +74,11 @@ export interface ISdkFactoryParams {
|
|
|
64
74
|
serviceApi: ISplitApi | undefined) => ISignalListener, // Used by BrowserSignalListener
|
|
65
75
|
|
|
66
76
|
// @TODO review impressionListener and integrations interfaces. What about handling impressionListener as an integration ?
|
|
67
|
-
impressionListener?: SplitIO.IImpressionListener,
|
|
68
77
|
integrationsManagerFactory?: (params: IIntegrationFactoryParams) => IIntegrationManager | undefined,
|
|
69
78
|
|
|
70
79
|
// Impression observer factory. If provided, will be used for impressions dedupe
|
|
71
80
|
impressionsObserverFactory?: () => IImpressionObserver
|
|
72
81
|
|
|
73
82
|
// Optional function to assign additional properties to the factory instance
|
|
74
|
-
extraProps?: (
|
|
83
|
+
extraProps?: (params: ISdkFactoryContext) => object
|
|
75
84
|
}
|
|
@@ -164,6 +164,12 @@ export class RedisAdapter extends ioredis {
|
|
|
164
164
|
} else { // If it IS the string URL, that'll be the first param for ioredis.
|
|
165
165
|
result.unshift(options.url);
|
|
166
166
|
}
|
|
167
|
+
if (options.connectionTimeout) {
|
|
168
|
+
merge(opts, { connectTimeout: options.connectionTimeout });
|
|
169
|
+
}
|
|
170
|
+
if (options.tls) {
|
|
171
|
+
merge(opts, { tls: options.tls });
|
|
172
|
+
}
|
|
167
173
|
|
|
168
174
|
return result;
|
|
169
175
|
}
|
|
@@ -171,9 +177,9 @@ export class RedisAdapter extends ioredis {
|
|
|
171
177
|
/**
|
|
172
178
|
* Parses the options into what we care about.
|
|
173
179
|
*/
|
|
174
|
-
static _defineOptions({ connectionTimeout, operationTimeout, url, host, port, db, pass }: Record<string, any>) {
|
|
180
|
+
static _defineOptions({ connectionTimeout, operationTimeout, url, host, port, db, pass, tls }: Record<string, any>) {
|
|
175
181
|
const parsedOptions = {
|
|
176
|
-
connectionTimeout, operationTimeout, url, host, port, db, pass
|
|
182
|
+
connectionTimeout, operationTimeout, url, host, port, db, pass, tls
|
|
177
183
|
};
|
|
178
184
|
|
|
179
185
|
return merge({}, DEFAULT_OPTIONS, parsedOptions);
|
|
@@ -6,7 +6,7 @@ import { IPushManager } from './streaming/types';
|
|
|
6
6
|
import { IPollingManager, IPollingManagerCS } from './polling/types';
|
|
7
7
|
import { PUSH_SUBSYSTEM_UP, PUSH_SUBSYSTEM_DOWN } from './streaming/constants';
|
|
8
8
|
import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '../logger/constants';
|
|
9
|
-
import { isConsentGranted } from '../
|
|
9
|
+
import { isConsentGranted } from '../consent';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Online SyncManager factory.
|
|
@@ -2,9 +2,10 @@ import { objectAssign } from '../utils/lang/objectAssign';
|
|
|
2
2
|
import { thenable } from '../utils/promise/thenable';
|
|
3
3
|
import { IEventsCacheBase } from '../storages/types';
|
|
4
4
|
import { IEventsHandler, IEventTracker } from './types';
|
|
5
|
-
import { SplitIO } from '../types';
|
|
6
|
-
import { ILogger } from '../logger/types';
|
|
5
|
+
import { ISettings, SplitIO } from '../types';
|
|
7
6
|
import { EVENTS_TRACKER_SUCCESS, ERROR_EVENTS_TRACKER } from '../logger/constants';
|
|
7
|
+
import { CONSENT_DECLINED } from '../utils/constants';
|
|
8
|
+
import { isStorageSync } from './impressionObserver/utils';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Event tracker stores events in cache and pass them to the integrations manager if provided.
|
|
@@ -13,11 +14,14 @@ import { EVENTS_TRACKER_SUCCESS, ERROR_EVENTS_TRACKER } from '../logger/constant
|
|
|
13
14
|
* @param integrationsManager optional event handler used for integrations
|
|
14
15
|
*/
|
|
15
16
|
export function eventTrackerFactory(
|
|
16
|
-
|
|
17
|
+
settings: ISettings,
|
|
17
18
|
eventsCache: IEventsCacheBase,
|
|
18
19
|
integrationsManager?: IEventsHandler
|
|
19
20
|
): IEventTracker {
|
|
20
21
|
|
|
22
|
+
const log = settings.log;
|
|
23
|
+
const isSync = isStorageSync(settings);
|
|
24
|
+
|
|
21
25
|
function queueEventsCallback(eventData: SplitIO.EventData, tracked: boolean) {
|
|
22
26
|
const { eventTypeId, trafficTypeName, key, value, timestamp, properties } = eventData;
|
|
23
27
|
// Logging every prop would be too much.
|
|
@@ -44,6 +48,10 @@ export function eventTrackerFactory(
|
|
|
44
48
|
|
|
45
49
|
return {
|
|
46
50
|
track(eventData: SplitIO.EventData, size?: number) {
|
|
51
|
+
if (settings.userConsent === CONSENT_DECLINED) {
|
|
52
|
+
return isSync ? false : Promise.resolve(false);
|
|
53
|
+
}
|
|
54
|
+
|
|
47
55
|
const tracked = eventsCache.track(eventData, size);
|
|
48
56
|
|
|
49
57
|
if (thenable(tracked)) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CONSUMER_PARTIAL_MODE, OPTIMIZED, PRODUCER_MODE, STANDALONE_MODE } from '../../utils/constants';
|
|
1
|
+
import { CONSUMER_MODE, CONSUMER_PARTIAL_MODE, OPTIMIZED, PRODUCER_MODE, STANDALONE_MODE } from '../../utils/constants';
|
|
2
2
|
import { ISettings } from '../../types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -15,3 +15,10 @@ export function shouldBeOptimized(settings: ISettings) {
|
|
|
15
15
|
if (!shouldAddPt(settings)) return false;
|
|
16
16
|
return settings.sync.impressionsMode === OPTIMIZED ? true : false;
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Storage is async if mode is consumer or partial consumer
|
|
21
|
+
*/
|
|
22
|
+
export function isStorageSync(settings: ISettings) {
|
|
23
|
+
return [CONSUMER_MODE, CONSUMER_PARTIAL_MODE].indexOf(settings.mode) === -1 ? true : false;
|
|
24
|
+
}
|
|
@@ -5,8 +5,8 @@ import { IImpressionCountsCacheSync, IImpressionsCacheBase } from '../storages/t
|
|
|
5
5
|
import { IImpressionsHandler, IImpressionsTracker } from './types';
|
|
6
6
|
import { SplitIO, ImpressionDTO, ISettings } from '../types';
|
|
7
7
|
import { IImpressionObserver } from './impressionObserver/types';
|
|
8
|
-
import { ILogger } from '../logger/types';
|
|
9
8
|
import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIONS_LISTENER } from '../logger/constants';
|
|
9
|
+
import { CONSENT_DECLINED } from '../utils/constants';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Impressions tracker stores impressions in cache and pass them to the listener and integrations manager if provided.
|
|
@@ -19,22 +19,21 @@ import { IMPRESSIONS_TRACKER_SUCCESS, ERROR_IMPRESSIONS_TRACKER, ERROR_IMPRESSIO
|
|
|
19
19
|
* @param countsCache optional cache to save impressions count. If provided, impressions will be deduped (OPTIMIZED mode)
|
|
20
20
|
*/
|
|
21
21
|
export function impressionsTrackerFactory(
|
|
22
|
-
|
|
22
|
+
settings: ISettings,
|
|
23
23
|
impressionsCache: IImpressionsCacheBase,
|
|
24
|
-
|
|
25
|
-
// @TODO consider passing only an optional integrationsManager to handle impressions
|
|
26
|
-
{ runtime: { ip, hostname }, version }: Pick<ISettings, 'version' | 'runtime'>,
|
|
27
|
-
impressionListener?: SplitIO.IImpressionListener,
|
|
28
24
|
integrationsManager?: IImpressionsHandler,
|
|
29
|
-
|
|
30
25
|
// if observer is provided, it implies `shouldAddPreviousTime` flag (i.e., if impressions previous time should be added or not)
|
|
31
26
|
observer?: IImpressionObserver,
|
|
32
27
|
// if countsCache is provided, it implies `isOptimized` flag (i.e., if impressions should be deduped or not)
|
|
33
28
|
countsCache?: IImpressionCountsCacheSync
|
|
34
29
|
): IImpressionsTracker {
|
|
35
30
|
|
|
31
|
+
const { log, impressionListener, runtime: { ip, hostname }, version } = settings;
|
|
32
|
+
|
|
36
33
|
return {
|
|
37
34
|
track(impressions: ImpressionDTO[], attributes?: SplitIO.Attributes) {
|
|
35
|
+
if (settings.userConsent === CONSENT_DECLINED) return;
|
|
36
|
+
|
|
38
37
|
const impressionsCount = impressions.length;
|
|
39
38
|
|
|
40
39
|
const impressionsToStore: ImpressionDTO[] = []; // Track only the impressions that are going to be stored
|
|
@@ -85,7 +84,7 @@ export function impressionsTrackerFactory(
|
|
|
85
84
|
// integrationsManager.handleImpression does not throw errors
|
|
86
85
|
if (integrationsManager) integrationsManager.handleImpression(impressionData);
|
|
87
86
|
|
|
88
|
-
try { // An exception on the listeners should not break the SDK.
|
|
87
|
+
try { // @ts-ignore. An exception on the listeners should not break the SDK.
|
|
89
88
|
if (impressionListener) impressionListener.logImpression(impressionData);
|
|
90
89
|
} catch (err) {
|
|
91
90
|
log.error(ERROR_IMPRESSIONS_LISTENER, [err]);
|
package/src/types.ts
CHANGED
|
@@ -398,7 +398,7 @@ interface IBasicClient extends IStatusInterface {
|
|
|
398
398
|
|
|
399
399
|
// Whether the client implements the client-side API, i.e, with bound key, (true), or the server-side API (false).
|
|
400
400
|
// Exposed for internal purposes only. Not considered part of the public API, and might be renamed eventually.
|
|
401
|
-
|
|
401
|
+
isClientSide: boolean
|
|
402
402
|
}
|
|
403
403
|
/**
|
|
404
404
|
* Common definitions between SDK instances for different environments interface.
|
|
@@ -6,7 +6,7 @@ import { ERROR_NOT_PLAIN_OBJECT } from '../../logger/constants';
|
|
|
6
6
|
|
|
7
7
|
export function validateAttributes(log: ILogger, maybeAttrs: any, method: string): SplitIO.Attributes | undefined | false {
|
|
8
8
|
// Attributes are optional
|
|
9
|
-
if (
|
|
9
|
+
if (maybeAttrs == undefined || isObject(maybeAttrs)) // eslint-disable-line eqeqeq
|
|
10
10
|
return maybeAttrs;
|
|
11
11
|
|
|
12
12
|
log.error(ERROR_NOT_PLAIN_OBJECT, [method, 'attributes']);
|
|
@@ -23,5 +23,4 @@ export function validateAttributesDeep(log: ILogger, maybeAttributes: Record<str
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
return result;
|
|
26
|
-
|
|
27
26
|
}
|
package/src/utils/lang/index.ts
CHANGED
|
@@ -151,10 +151,14 @@ export function isNaNNumber(val: any): boolean {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
/**
|
|
154
|
-
* Validates if a value is an object
|
|
154
|
+
* Validates if a value is an object created by the Object constructor (plain object).
|
|
155
|
+
* It checks `constructor.name` to avoid false negatives when validating values on a separate VM context, which has its own global built-ins.
|
|
155
156
|
*/
|
|
156
|
-
export function isObject(obj: any)
|
|
157
|
-
return obj !== null && typeof obj === 'object' &&
|
|
157
|
+
export function isObject(obj: any) {
|
|
158
|
+
return obj !== null && typeof obj === 'object' && (
|
|
159
|
+
obj.constructor === Object ||
|
|
160
|
+
(obj.constructor != null && obj.constructor.name === 'Object')
|
|
161
|
+
);
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
/**
|
package/src/utils/lang/maps.ts
CHANGED
|
@@ -79,4 +79,18 @@ interface IMapConstructor {
|
|
|
79
79
|
readonly prototype: IMap<any, any>;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
/**
|
|
83
|
+
* return the Map constructor to use. If native Map is not available or it doesn't support the required features (e.g., IE11),
|
|
84
|
+
* a ponyfill with minimal features is returned instead.
|
|
85
|
+
*
|
|
86
|
+
* Exported for testing purposes only.
|
|
87
|
+
*/
|
|
88
|
+
export function __getMapConstructor(): IMapConstructor {
|
|
89
|
+
// eslint-disable-next-line compat/compat
|
|
90
|
+
if (typeof Array.from === 'function' && typeof Map === 'function' && Map.prototype && Map.prototype.values) {
|
|
91
|
+
return Map;
|
|
92
|
+
}
|
|
93
|
+
return MapPoly;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const _Map = __getMapConstructor();
|