@ninetailed/experience.js 7.5.3 → 7.6.0-beta.2
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/index.cjs.d.ts +1 -0
- package/{index.cjs → index.cjs.js} +697 -375
- package/index.esm.js +1203 -0
- package/package.json +6 -9
- package/src/index.d.ts +1 -1
- package/src/lib/ElementSeenObserver.d.ts +1 -1
- package/src/lib/Ninetailed.d.ts +19 -5
- package/src/lib/NinetailedCorePlugin/NinetailedCorePlugin.d.ts +69 -0
- package/src/lib/{ninetailedCorePlugin → NinetailedCorePlugin}/index.d.ts +1 -1
- package/src/lib/constants.d.ts +1 -2
- package/src/lib/experience/makeExperienceSelectMiddleware.d.ts +9 -9
- package/src/lib/guards/hasComponentViewTrackingThreshold.d.ts +3 -0
- package/src/lib/guards/hasExperienceSelectionMiddleware.d.ts +1 -1
- package/src/lib/plugins/selectPluginsHavingExperienceSelectionMiddleware.d.ts +2 -2
- package/src/lib/plugins/selectPluginsHavingOnChangeEmitter.d.ts +1 -1
- package/src/lib/types/OnSelectVariant.d.ts +51 -0
- package/src/lib/types/index.d.ts +9 -9
- package/src/lib/types/interfaces/HasComponentViewTrackingThreshold.d.ts +3 -0
- package/src/lib/types/interfaces/HasExperienceSelectionMiddleware.d.ts +10 -10
- package/src/lib/types/interfaces/InterestedInHiddenPage.d.ts +1 -1
- package/src/lib/types/interfaces/InterestedInProfileChange.d.ts +1 -1
- package/src/lib/types/interfaces/InterestedInSeenElements.d.ts +1 -3
- package/src/lib/utils/EventBuilder.d.ts +221 -0
- package/src/lib/utils/noop.d.ts +1 -0
- package/index.js +0 -886
- package/src/lib/ninetailedCorePlugin/ninetailedCorePlugin.d.ts +0 -17
- package/src/lib/test-helpers/intersection-observer-test-helper.d.ts +0 -2
- package/src/lib/types/ElementSeenPayload.d.ts +0 -82
- package/src/lib/types/EventHandler.d.ts +0 -6
- package/src/lib/types/NinetailedPlugin.d.ts +0 -11
- package/src/lib/types/TrackingProperties.d.ts +0 -35
- /package/src/lib/{ninetailedCorePlugin → NinetailedCorePlugin}/Events/build-context.d.ts +0 -0
- /package/src/lib/{ninetailedCorePlugin → NinetailedCorePlugin}/Events/build-locale.d.ts +0 -0
- /package/src/lib/{ninetailedCorePlugin → NinetailedCorePlugin}/Events/index.d.ts +0 -0
- /package/src/lib/{ninetailedCorePlugin → NinetailedCorePlugin}/constants.d.ts +0 -0
package/index.js
DELETED
|
@@ -1,886 +0,0 @@
|
|
|
1
|
-
import { FEATURES, logger, ConsoleLogSink, buildPageEvent, buildTrackEvent, buildIdentifyEvent, unionBy, NinetailedApiClient, OnLogLogSink, OnErrorLogSink, PageviewProperties, Properties, Traits, pipe } from '@ninetailed/experience.js-shared';
|
|
2
|
-
export { EXPERIENCE_TRAIT_PREFIX, isExperienceMatch, selectActiveExperiments, selectDistribution, selectExperience, selectBaselineWithVariants as selectExperienceBaselineWithVariants, selectVariant as selectExperienceVariant, selectVariants as selectExperienceVariants, selectHasVariants as selectHasExperienceVariants } from '@ninetailed/experience.js-shared';
|
|
3
|
-
import Analytics from 'analytics';
|
|
4
|
-
import { z } from 'zod';
|
|
5
|
-
|
|
6
|
-
const HAS_SEEN_COMPONENT = 'has_seen_component';
|
|
7
|
-
const HAS_SEEN_ELEMENT = 'has_seen_element';
|
|
8
|
-
const COMPONENT = 'component';
|
|
9
|
-
const COMPONENT_START = 'componentStart';
|
|
10
|
-
const PAGE_HIDDEN = 'page_hidden';
|
|
11
|
-
|
|
12
|
-
/******************************************************************************
|
|
13
|
-
Copyright (c) Microsoft Corporation.
|
|
14
|
-
|
|
15
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
16
|
-
purpose with or without fee is hereby granted.
|
|
17
|
-
|
|
18
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
19
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
20
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
21
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
22
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
23
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
24
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
25
|
-
***************************************************************************** */
|
|
26
|
-
|
|
27
|
-
function __rest(s, e) {
|
|
28
|
-
var t = {};
|
|
29
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
30
|
-
t[p] = s[p];
|
|
31
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
32
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
33
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
34
|
-
t[p[i]] = s[p[i]];
|
|
35
|
-
}
|
|
36
|
-
return t;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function __awaiter(thisArg, _arguments, P, generator) {
|
|
40
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
41
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
42
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
43
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
44
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
45
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const buildClientLocale = () => navigator.languages && navigator.languages.length ? navigator.languages[0] : navigator.language;
|
|
50
|
-
|
|
51
|
-
const buildClientNinetailedRequestContext = () => ({
|
|
52
|
-
url: window.location.href,
|
|
53
|
-
referrer: document.referrer,
|
|
54
|
-
locale: buildClientLocale(),
|
|
55
|
-
userAgent: navigator.userAgent,
|
|
56
|
-
document: {
|
|
57
|
-
title: document.title
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Similar to _.throttle but waits for the promise to resolve.
|
|
63
|
-
* There is no "wait time" because you can simply await `Promise.timeout` inside `fn` to wait some time before the next call.
|
|
64
|
-
*/
|
|
65
|
-
function asyncThrottle(fn) {
|
|
66
|
-
let runningPromise;
|
|
67
|
-
let queuedPromise;
|
|
68
|
-
let nextArgs;
|
|
69
|
-
return args => __awaiter(this, void 0, void 0, function* () {
|
|
70
|
-
if (runningPromise) {
|
|
71
|
-
nextArgs = args;
|
|
72
|
-
if (queuedPromise) {
|
|
73
|
-
return queuedPromise;
|
|
74
|
-
} else {
|
|
75
|
-
queuedPromise = runningPromise.then(() => {
|
|
76
|
-
queuedPromise = undefined;
|
|
77
|
-
runningPromise = fn(nextArgs);
|
|
78
|
-
return runningPromise;
|
|
79
|
-
});
|
|
80
|
-
return queuedPromise;
|
|
81
|
-
}
|
|
82
|
-
} else {
|
|
83
|
-
runningPromise = fn(args);
|
|
84
|
-
return runningPromise;
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const LEGACY_ANONYMOUS_ID = '__anon_id';
|
|
90
|
-
const ANONYMOUS_ID = '__nt_anonymous_id__';
|
|
91
|
-
const DEBUG_FLAG = '__nt_debug__';
|
|
92
|
-
const PROFILE_FALLBACK_CACHE = '__nt_profile__';
|
|
93
|
-
const EXPERIENCES_FALLBACK_CACHE = '__nt_experiences__';
|
|
94
|
-
const PROFILE_CHANGE = 'profile-change';
|
|
95
|
-
const PROFILE_RESET = 'profile-reset';
|
|
96
|
-
const CONSENT = '__nt-consent__';
|
|
97
|
-
const SET_ENABLED_FEATURES = 'set-enabled-features';
|
|
98
|
-
const EMPTY_MERGE_ID = 'nt:empty-merge-id';
|
|
99
|
-
|
|
100
|
-
const PLUGIN_NAME = 'ninetailed';
|
|
101
|
-
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
102
|
-
const ninetailedCorePlugin = ({
|
|
103
|
-
apiClient,
|
|
104
|
-
locale,
|
|
105
|
-
ninetailed,
|
|
106
|
-
onInitProfileId,
|
|
107
|
-
buildClientContext
|
|
108
|
-
}) => {
|
|
109
|
-
let _instance;
|
|
110
|
-
let queue = [];
|
|
111
|
-
let enabledFeatures = Object.values(FEATURES);
|
|
112
|
-
const buildContext = buildClientContext || buildClientNinetailedRequestContext;
|
|
113
|
-
const flush = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
114
|
-
const events = Object.assign([], queue);
|
|
115
|
-
logger.info('Start flushing events.');
|
|
116
|
-
queue = [];
|
|
117
|
-
if (!events.length) {
|
|
118
|
-
return {
|
|
119
|
-
success: true
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
try {
|
|
123
|
-
const anonymousId = _instance.storage.getItem(ANONYMOUS_ID);
|
|
124
|
-
const {
|
|
125
|
-
profile,
|
|
126
|
-
experiences
|
|
127
|
-
} = yield apiClient.upsertProfile({
|
|
128
|
-
profileId: anonymousId,
|
|
129
|
-
events
|
|
130
|
-
}, {
|
|
131
|
-
locale,
|
|
132
|
-
enabledFeatures
|
|
133
|
-
});
|
|
134
|
-
_instance.storage.setItem(ANONYMOUS_ID, profile.id);
|
|
135
|
-
_instance.storage.setItem(PROFILE_FALLBACK_CACHE, profile);
|
|
136
|
-
_instance.storage.setItem(EXPERIENCES_FALLBACK_CACHE, experiences);
|
|
137
|
-
logger.debug('Profile from api: ', profile);
|
|
138
|
-
logger.debug('Experiences from api: ', experiences);
|
|
139
|
-
_instance.dispatch({
|
|
140
|
-
type: PROFILE_CHANGE,
|
|
141
|
-
profile,
|
|
142
|
-
experiences
|
|
143
|
-
});
|
|
144
|
-
yield delay(20);
|
|
145
|
-
return {
|
|
146
|
-
success: true
|
|
147
|
-
};
|
|
148
|
-
} catch (error) {
|
|
149
|
-
logger.debug('An error occurred during flushing the events: ', error);
|
|
150
|
-
const fallbackProfile = _instance.storage.getItem(PROFILE_FALLBACK_CACHE);
|
|
151
|
-
const fallbackExperiences = _instance.storage.getItem(EXPERIENCES_FALLBACK_CACHE) || [];
|
|
152
|
-
if (fallbackProfile) {
|
|
153
|
-
logger.debug('Found a fallback profile - will use this.');
|
|
154
|
-
_instance.dispatch({
|
|
155
|
-
type: PROFILE_CHANGE,
|
|
156
|
-
profile: fallbackProfile,
|
|
157
|
-
experiences: fallbackExperiences
|
|
158
|
-
});
|
|
159
|
-
} else {
|
|
160
|
-
logger.debug('No fallback profile found - setting profile to null.');
|
|
161
|
-
_instance.dispatch({
|
|
162
|
-
type: PROFILE_CHANGE,
|
|
163
|
-
profile: null,
|
|
164
|
-
experiences: fallbackExperiences,
|
|
165
|
-
error
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
return {
|
|
169
|
-
success: false
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
const enqueueEvent = event => __awaiter(void 0, void 0, void 0, function* () {
|
|
174
|
-
queue = unionBy([event], queue, 'messageId');
|
|
175
|
-
});
|
|
176
|
-
const abortNonClientEvents = ({
|
|
177
|
-
abort,
|
|
178
|
-
payload
|
|
179
|
-
}) => {
|
|
180
|
-
if (typeof window !== 'object') {
|
|
181
|
-
return abort();
|
|
182
|
-
}
|
|
183
|
-
return payload;
|
|
184
|
-
};
|
|
185
|
-
return {
|
|
186
|
-
name: 'ninetailed',
|
|
187
|
-
config: {},
|
|
188
|
-
initialize: ({
|
|
189
|
-
instance
|
|
190
|
-
}) => __awaiter(void 0, void 0, void 0, function* () {
|
|
191
|
-
_instance = instance;
|
|
192
|
-
if (instance.storage.getItem(DEBUG_FLAG)) {
|
|
193
|
-
logger.addSink(new ConsoleLogSink());
|
|
194
|
-
logger.info('Ninetailed Debug Mode is enabled.');
|
|
195
|
-
}
|
|
196
|
-
// legacy support for the old anonymousId
|
|
197
|
-
const legacyAnonymousId = instance.storage.getItem(LEGACY_ANONYMOUS_ID);
|
|
198
|
-
if (legacyAnonymousId) {
|
|
199
|
-
logger.debug('Found legacy anonymousId, migrating to new one.', legacyAnonymousId);
|
|
200
|
-
instance.storage.setItem(ANONYMOUS_ID, legacyAnonymousId);
|
|
201
|
-
instance.storage.removeItem(LEGACY_ANONYMOUS_ID);
|
|
202
|
-
}
|
|
203
|
-
if (typeof onInitProfileId === 'function') {
|
|
204
|
-
const profileId = yield onInitProfileId(instance.storage.getItem(ANONYMOUS_ID));
|
|
205
|
-
if (typeof profileId === 'string') {
|
|
206
|
-
instance.storage.setItem(ANONYMOUS_ID, profileId);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
instance.on(SET_ENABLED_FEATURES, ({
|
|
210
|
-
payload
|
|
211
|
-
}) => {
|
|
212
|
-
enabledFeatures = payload.features || [];
|
|
213
|
-
});
|
|
214
|
-
logger.debug('Ninetailed Core plugin initialized.');
|
|
215
|
-
}),
|
|
216
|
-
flush: asyncThrottle(flush),
|
|
217
|
-
pageStart: params => {
|
|
218
|
-
return abortNonClientEvents(params);
|
|
219
|
-
},
|
|
220
|
-
page: ({
|
|
221
|
-
payload
|
|
222
|
-
}) => __awaiter(void 0, void 0, void 0, function* () {
|
|
223
|
-
logger.info('Sending Page event.');
|
|
224
|
-
const ctx = buildContext();
|
|
225
|
-
return enqueueEvent(buildPageEvent({
|
|
226
|
-
messageId: payload.meta.rid,
|
|
227
|
-
timestamp: payload.meta.ts,
|
|
228
|
-
properties: payload.properties,
|
|
229
|
-
ctx
|
|
230
|
-
}));
|
|
231
|
-
}),
|
|
232
|
-
trackStart: params => {
|
|
233
|
-
return abortNonClientEvents(params);
|
|
234
|
-
},
|
|
235
|
-
track: ({
|
|
236
|
-
payload
|
|
237
|
-
}) => __awaiter(void 0, void 0, void 0, function* () {
|
|
238
|
-
logger.info('Sending Track event.');
|
|
239
|
-
const ctx = buildContext();
|
|
240
|
-
return enqueueEvent(buildTrackEvent({
|
|
241
|
-
messageId: payload.meta.rid,
|
|
242
|
-
timestamp: payload.meta.ts,
|
|
243
|
-
event: payload.event,
|
|
244
|
-
properties: payload.properties,
|
|
245
|
-
ctx
|
|
246
|
-
}));
|
|
247
|
-
}),
|
|
248
|
-
identifyStart: params => {
|
|
249
|
-
return abortNonClientEvents(params);
|
|
250
|
-
},
|
|
251
|
-
identify: ({
|
|
252
|
-
payload
|
|
253
|
-
}) => __awaiter(void 0, void 0, void 0, function* () {
|
|
254
|
-
logger.info('Sending Identify event.');
|
|
255
|
-
const ctx = buildContext();
|
|
256
|
-
if (payload.userId === EMPTY_MERGE_ID && (!payload.traits || typeof payload.traits === 'object' && Object.keys(payload.traits).length === 0)) {
|
|
257
|
-
logger.info('Skipping Identify event as no userId and no traits are set.');
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
return enqueueEvent(buildIdentifyEvent({
|
|
261
|
-
messageId: payload.meta.rid,
|
|
262
|
-
timestamp: payload.meta.ts,
|
|
263
|
-
traits: payload.traits,
|
|
264
|
-
userId: payload.userId === EMPTY_MERGE_ID ? '' : payload.userId,
|
|
265
|
-
ctx
|
|
266
|
-
}));
|
|
267
|
-
}),
|
|
268
|
-
setItemStart: ({
|
|
269
|
-
abort,
|
|
270
|
-
payload
|
|
271
|
-
}) => {
|
|
272
|
-
if (![ANONYMOUS_ID, DEBUG_FLAG, PROFILE_FALLBACK_CACHE, EXPERIENCES_FALLBACK_CACHE, CONSENT].includes(payload.key)) {
|
|
273
|
-
return abort();
|
|
274
|
-
}
|
|
275
|
-
return payload;
|
|
276
|
-
},
|
|
277
|
-
methods: {
|
|
278
|
-
reset: (...args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
279
|
-
logger.debug('Resetting profile.');
|
|
280
|
-
const instance = args[args.length - 1];
|
|
281
|
-
instance.dispatch({
|
|
282
|
-
type: PROFILE_RESET
|
|
283
|
-
});
|
|
284
|
-
instance.storage.removeItem(ANONYMOUS_ID);
|
|
285
|
-
instance.storage.removeItem(PROFILE_FALLBACK_CACHE);
|
|
286
|
-
instance.storage.removeItem(EXPERIENCES_FALLBACK_CACHE);
|
|
287
|
-
logger.debug('Removed old profile data from localstorage.');
|
|
288
|
-
if (typeof onInitProfileId === 'function') {
|
|
289
|
-
const profileId = yield onInitProfileId(undefined);
|
|
290
|
-
if (typeof profileId === 'string') {
|
|
291
|
-
instance.storage.setItem(ANONYMOUS_ID, profileId);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
yield ninetailed.track('nt_reset');
|
|
295
|
-
logger.info('Profile reset successful.');
|
|
296
|
-
yield delay(10);
|
|
297
|
-
}),
|
|
298
|
-
debug: (...args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
299
|
-
const enabled = args[0];
|
|
300
|
-
const instance = args[args.length - 1];
|
|
301
|
-
const consoleLogSink = new ConsoleLogSink();
|
|
302
|
-
if (enabled) {
|
|
303
|
-
instance.storage.setItem(DEBUG_FLAG, true);
|
|
304
|
-
logger.addSink(consoleLogSink);
|
|
305
|
-
logger.info('Debug mode enabled.');
|
|
306
|
-
} else {
|
|
307
|
-
instance.storage.removeItem(DEBUG_FLAG);
|
|
308
|
-
logger.info('Debug mode disabled.');
|
|
309
|
-
logger.removeSink(consoleLogSink.name);
|
|
310
|
-
}
|
|
311
|
-
})
|
|
312
|
-
}
|
|
313
|
-
};
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
class ElementSeenObserver {
|
|
317
|
-
constructor(_options) {
|
|
318
|
-
this._options = _options;
|
|
319
|
-
this._elementDelays = new WeakMap();
|
|
320
|
-
this._intersectionTimers = new WeakMap();
|
|
321
|
-
if (typeof IntersectionObserver !== 'undefined') {
|
|
322
|
-
this._intersectionObserver = new IntersectionObserver(this.onIntersection.bind(this));
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
onIntersection(entries) {
|
|
326
|
-
entries.forEach(entry => {
|
|
327
|
-
const {
|
|
328
|
-
isIntersecting,
|
|
329
|
-
target
|
|
330
|
-
} = entry;
|
|
331
|
-
if (isIntersecting) {
|
|
332
|
-
const delay = this._elementDelays.get(target);
|
|
333
|
-
const timeOut = window.setTimeout(() => {
|
|
334
|
-
this._options.onElementSeen(target);
|
|
335
|
-
}, delay);
|
|
336
|
-
this._intersectionTimers.set(target, timeOut);
|
|
337
|
-
} else {
|
|
338
|
-
const timeOut = this._intersectionTimers.get(target);
|
|
339
|
-
if (typeof timeOut !== 'undefined') {
|
|
340
|
-
window.clearTimeout(timeOut);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
observe(element, options) {
|
|
346
|
-
var _a, _b;
|
|
347
|
-
this._elementDelays.set(element, (_a = options === null || options === void 0 ? void 0 : options.delay) !== null && _a !== void 0 ? _a : 2000);
|
|
348
|
-
(_b = this._intersectionObserver) === null || _b === void 0 ? void 0 : _b.observe(element);
|
|
349
|
-
}
|
|
350
|
-
unobserve(element) {
|
|
351
|
-
var _a;
|
|
352
|
-
(_a = this._intersectionObserver) === null || _a === void 0 ? void 0 : _a.unobserve(element);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const acceptsCredentials = plugin => {
|
|
357
|
-
return typeof plugin === 'object' && plugin !== null && 'setCredentials' in plugin && typeof plugin.setCredentials === 'function';
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
const isInterestedInHiddenPage = arg => {
|
|
361
|
-
return typeof arg === 'object' && arg !== null && PAGE_HIDDEN in arg && typeof arg[PAGE_HIDDEN] === 'function';
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
class Ninetailed {
|
|
365
|
-
constructor(ninetailedApiClientInstanceOrOptions, {
|
|
366
|
-
plugins,
|
|
367
|
-
url,
|
|
368
|
-
locale,
|
|
369
|
-
requestTimeout,
|
|
370
|
-
onLog,
|
|
371
|
-
onError,
|
|
372
|
-
buildClientContext,
|
|
373
|
-
onInitProfileId,
|
|
374
|
-
componentViewTrackingThreshold = 2000,
|
|
375
|
-
storageImpl
|
|
376
|
-
} = {}) {
|
|
377
|
-
this.isInitialized = false;
|
|
378
|
-
this.page = (data, options) => __awaiter(this, void 0, void 0, function* () {
|
|
379
|
-
try {
|
|
380
|
-
const result = PageviewProperties.partial().default({}).safeParse(data);
|
|
381
|
-
if (!result.success) {
|
|
382
|
-
throw new Error(`[Validation Error] "page" was called with invalid params. Page data is not valid: ${result.error.format()}`);
|
|
383
|
-
}
|
|
384
|
-
yield this.waitUntilInitialized();
|
|
385
|
-
yield this.instance.page(data, this.buildOptions(options));
|
|
386
|
-
return this.ninetailedCorePlugin.flush();
|
|
387
|
-
} catch (error) {
|
|
388
|
-
logger.error(error);
|
|
389
|
-
if (error instanceof RangeError) {
|
|
390
|
-
throw new Error(`[Validation Error] "page" was called with invalid params. Could not validate due to "RangeError: Maximum call stack size exceeded". This can be caused by passing a cyclic data structure as a parameter. Refrain from passing a cyclic data structure or sanitize it beforehand.`);
|
|
391
|
-
}
|
|
392
|
-
throw error;
|
|
393
|
-
}
|
|
394
|
-
});
|
|
395
|
-
this.track = (event, properties, options) => __awaiter(this, void 0, void 0, function* () {
|
|
396
|
-
try {
|
|
397
|
-
const result = Properties.default({}).safeParse(properties);
|
|
398
|
-
if (!result.success) {
|
|
399
|
-
throw new Error(`[Validation Error] "track" was called with invalid params. Properties are no valid json object: ${result.error.format()}`);
|
|
400
|
-
}
|
|
401
|
-
yield this.waitUntilInitialized();
|
|
402
|
-
yield this.instance.track(event.toString(), result.data, this.buildOptions(options));
|
|
403
|
-
return this.ninetailedCorePlugin.flush();
|
|
404
|
-
} catch (error) {
|
|
405
|
-
logger.error(error);
|
|
406
|
-
if (error instanceof RangeError) {
|
|
407
|
-
throw new Error(`[Validation Error] "track" was called with invalid params. Could not validate due to "RangeError: Maximum call stack size exceeded". This can be caused by passing a cyclic data structure as a parameter. Refrain from passing a cyclic data structure or sanitize it beforehand.`);
|
|
408
|
-
}
|
|
409
|
-
throw error;
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
/**
|
|
413
|
-
* @deprecated The legacy datamodel is not recommended anymore
|
|
414
|
-
* Will be removed in the next version of the SDK
|
|
415
|
-
*/
|
|
416
|
-
this.trackHasSeenComponent = properties => __awaiter(this, void 0, void 0, function* () {
|
|
417
|
-
return this.instance.dispatch(Object.assign(Object.assign({}, properties), {
|
|
418
|
-
type: HAS_SEEN_COMPONENT
|
|
419
|
-
}));
|
|
420
|
-
});
|
|
421
|
-
this.trackComponentView = properties => {
|
|
422
|
-
return this.instance.dispatch(Object.assign(Object.assign({}, properties), {
|
|
423
|
-
type: HAS_SEEN_ELEMENT
|
|
424
|
-
}));
|
|
425
|
-
};
|
|
426
|
-
this.observeElement = (payload, options) => {
|
|
427
|
-
const {
|
|
428
|
-
element
|
|
429
|
-
} = payload,
|
|
430
|
-
remaingPayload = __rest(payload, ["element"]);
|
|
431
|
-
if (!(element instanceof Element)) {
|
|
432
|
-
const isObject = typeof element === 'object' && element !== null;
|
|
433
|
-
const constructorName = isObject ? element.constructor.name : '';
|
|
434
|
-
const isConstructorNameNotObject = constructorName && constructorName !== 'Object';
|
|
435
|
-
logger.warn(`ElementSeenObserver.observeElement was called with an invalid element. Expected an Element but got ${typeof element}${isConstructorNameNotObject ? ` of type ${constructorName}` : ''}. This call will be ignored.`);
|
|
436
|
-
} else {
|
|
437
|
-
const existingPayloads = this.observedElements.get(element);
|
|
438
|
-
if (!existingPayloads) {
|
|
439
|
-
this.observedElements.set(element, [remaingPayload]);
|
|
440
|
-
} else {
|
|
441
|
-
const isPayloadAlreadyObserved = existingPayloads.some(payload => {
|
|
442
|
-
return JSON.stringify(payload) === JSON.stringify(remaingPayload);
|
|
443
|
-
});
|
|
444
|
-
if (isPayloadAlreadyObserved) {
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
this.observedElements.set(element, [...existingPayloads, remaingPayload]);
|
|
448
|
-
}
|
|
449
|
-
this.elementSeenObserver.observe(element, Object.assign({
|
|
450
|
-
delay: this.componentViewTrackingThreshold
|
|
451
|
-
}, options));
|
|
452
|
-
}
|
|
453
|
-
};
|
|
454
|
-
this.unobserveElement = element => {
|
|
455
|
-
this.observedElements.delete(element);
|
|
456
|
-
this.elementSeenObserver.unobserve(element);
|
|
457
|
-
};
|
|
458
|
-
this.onElementSeen = element => {
|
|
459
|
-
const payloads = this.observedElements.get(element);
|
|
460
|
-
if (typeof payloads !== 'undefined') {
|
|
461
|
-
for (const payload of payloads) {
|
|
462
|
-
this.trackComponentView(Object.assign({
|
|
463
|
-
element
|
|
464
|
-
}, payload));
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
};
|
|
468
|
-
this.identify = (uid, traits, options) => __awaiter(this, void 0, void 0, function* () {
|
|
469
|
-
try {
|
|
470
|
-
const result = Traits.default({}).safeParse(traits);
|
|
471
|
-
if (!result.success) {
|
|
472
|
-
throw new Error(`[Validation Error] "identify" was called with invalid params. Traits are no valid json: ${result.error.format()}`);
|
|
473
|
-
}
|
|
474
|
-
yield this.waitUntilInitialized();
|
|
475
|
-
yield this.instance.identify(uid && uid.toString() !== '' ? uid.toString() : EMPTY_MERGE_ID, result.data, this.buildOptions(options));
|
|
476
|
-
return this.ninetailedCorePlugin.flush();
|
|
477
|
-
} catch (error) {
|
|
478
|
-
logger.error(error);
|
|
479
|
-
if (error instanceof RangeError) {
|
|
480
|
-
throw new Error(`[Validation Error] "identify" was called with invalid params. Could not validate due to "RangeError: Maximum call stack size exceeded". This can be caused by passing a cyclic data structure as a parameter. Refrain from passing a cyclic data structure or sanitize it beforehand.`);
|
|
481
|
-
}
|
|
482
|
-
throw error;
|
|
483
|
-
}
|
|
484
|
-
});
|
|
485
|
-
this.reset = () => __awaiter(this, void 0, void 0, function* () {
|
|
486
|
-
yield this.waitUntilInitialized();
|
|
487
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
488
|
-
// @ts-ignore
|
|
489
|
-
this.instance.plugins[PLUGIN_NAME].reset();
|
|
490
|
-
});
|
|
491
|
-
this.debug = enabled => __awaiter(this, void 0, void 0, function* () {
|
|
492
|
-
yield this.waitUntilInitialized();
|
|
493
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
494
|
-
// @ts-ignore
|
|
495
|
-
this.instance.plugins[PLUGIN_NAME].debug(enabled);
|
|
496
|
-
});
|
|
497
|
-
this.onProfileChange = cb => {
|
|
498
|
-
cb(this.profileState);
|
|
499
|
-
return this.instance.on(PROFILE_CHANGE, ({
|
|
500
|
-
payload
|
|
501
|
-
}) => {
|
|
502
|
-
if (payload.error) {
|
|
503
|
-
cb(Object.assign(Object.assign({}, this._profileState), {
|
|
504
|
-
status: 'error',
|
|
505
|
-
profile: payload.profile,
|
|
506
|
-
experiences: payload.experiences,
|
|
507
|
-
error: payload.error
|
|
508
|
-
}));
|
|
509
|
-
} else {
|
|
510
|
-
cb(Object.assign(Object.assign({}, this._profileState), {
|
|
511
|
-
status: 'success',
|
|
512
|
-
profile: payload.profile,
|
|
513
|
-
experiences: payload.experiences,
|
|
514
|
-
error: null
|
|
515
|
-
}));
|
|
516
|
-
}
|
|
517
|
-
});
|
|
518
|
-
};
|
|
519
|
-
this.onIsInitialized = onIsInitialized => {
|
|
520
|
-
if (typeof onIsInitialized === 'function') {
|
|
521
|
-
if (this.isInitialized) {
|
|
522
|
-
onIsInitialized();
|
|
523
|
-
} else {
|
|
524
|
-
const detachOnReadyListener = this.instance.on('ready', () => {
|
|
525
|
-
this.isInitialized = true;
|
|
526
|
-
onIsInitialized();
|
|
527
|
-
detachOnReadyListener();
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
};
|
|
532
|
-
this.waitUntilInitialized = () => {
|
|
533
|
-
return new Promise(resolve => {
|
|
534
|
-
this.onIsInitialized(resolve);
|
|
535
|
-
});
|
|
536
|
-
};
|
|
537
|
-
this.onVisibilityChange = () => {
|
|
538
|
-
if (typeof document === 'undefined') {
|
|
539
|
-
return;
|
|
540
|
-
}
|
|
541
|
-
document.addEventListener('visibilitychange', () => {
|
|
542
|
-
if (document.visibilityState === 'hidden') {
|
|
543
|
-
this.instance.dispatch({
|
|
544
|
-
type: PAGE_HIDDEN
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
});
|
|
548
|
-
};
|
|
549
|
-
if (ninetailedApiClientInstanceOrOptions instanceof NinetailedApiClient) {
|
|
550
|
-
this.apiClient = ninetailedApiClientInstanceOrOptions;
|
|
551
|
-
} else {
|
|
552
|
-
const {
|
|
553
|
-
clientId,
|
|
554
|
-
environment,
|
|
555
|
-
preview
|
|
556
|
-
} = ninetailedApiClientInstanceOrOptions;
|
|
557
|
-
this.clientId = clientId;
|
|
558
|
-
this.environment = environment || 'main';
|
|
559
|
-
this.apiClient = new NinetailedApiClient({
|
|
560
|
-
clientId,
|
|
561
|
-
environment,
|
|
562
|
-
url,
|
|
563
|
-
preview
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
this.plugins = (plugins !== null && plugins !== void 0 ? plugins : []).flat();
|
|
567
|
-
this.plugins.forEach(plugin => {
|
|
568
|
-
if (acceptsCredentials(plugin) && this.clientId && this.environment) {
|
|
569
|
-
plugin.setCredentials({
|
|
570
|
-
clientId: this.clientId,
|
|
571
|
-
environment: this.environment
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
});
|
|
575
|
-
this._profileState = {
|
|
576
|
-
status: 'loading',
|
|
577
|
-
profile: null,
|
|
578
|
-
experiences: null,
|
|
579
|
-
error: null,
|
|
580
|
-
from: 'api'
|
|
581
|
-
};
|
|
582
|
-
if (typeof onLog === 'function') {
|
|
583
|
-
logger.addSink(new OnLogLogSink(onLog));
|
|
584
|
-
}
|
|
585
|
-
if (typeof onError === 'function') {
|
|
586
|
-
logger.addSink(new OnErrorLogSink(onError));
|
|
587
|
-
}
|
|
588
|
-
this.logger = logger;
|
|
589
|
-
this.ninetailedCorePlugin = ninetailedCorePlugin({
|
|
590
|
-
apiClient: this.apiClient,
|
|
591
|
-
locale,
|
|
592
|
-
requestTimeout,
|
|
593
|
-
buildClientContext,
|
|
594
|
-
onInitProfileId,
|
|
595
|
-
ninetailed: this
|
|
596
|
-
});
|
|
597
|
-
this.instance = Analytics(Object.assign({
|
|
598
|
-
app: 'ninetailed',
|
|
599
|
-
plugins: [...this.plugins, this.ninetailedCorePlugin]
|
|
600
|
-
}, storageImpl ? {
|
|
601
|
-
storage: storageImpl
|
|
602
|
-
} : {}));
|
|
603
|
-
const detachOnReadyListener = this.instance.on('ready', () => {
|
|
604
|
-
this.isInitialized = true;
|
|
605
|
-
logger.info('Ninetailed Experience.js SDK is completely initialized.');
|
|
606
|
-
detachOnReadyListener();
|
|
607
|
-
});
|
|
608
|
-
// put in private method
|
|
609
|
-
this.onProfileChange(profileState => {
|
|
610
|
-
this._profileState = profileState;
|
|
611
|
-
if (typeof window !== 'undefined') {
|
|
612
|
-
window.ninetailed = Object.assign({}, window.ninetailed, {
|
|
613
|
-
profile: this.profileState.profile,
|
|
614
|
-
experiences: this.profileState.experiences
|
|
615
|
-
});
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
this.observedElements = new WeakMap();
|
|
619
|
-
this.elementSeenObserver = new ElementSeenObserver({
|
|
620
|
-
onElementSeen: this.onElementSeen.bind(this)
|
|
621
|
-
});
|
|
622
|
-
this.componentViewTrackingThreshold = componentViewTrackingThreshold;
|
|
623
|
-
const hasPluginsInterestedInHiddenPage = this.plugins.some(isInterestedInHiddenPage);
|
|
624
|
-
if (hasPluginsInterestedInHiddenPage) {
|
|
625
|
-
this.onVisibilityChange();
|
|
626
|
-
}
|
|
627
|
-
this.registerWindowHandlers();
|
|
628
|
-
}
|
|
629
|
-
get profileState() {
|
|
630
|
-
return this._profileState;
|
|
631
|
-
}
|
|
632
|
-
buildOptions(options = {}) {
|
|
633
|
-
return Object.assign({}, options);
|
|
634
|
-
}
|
|
635
|
-
registerWindowHandlers() {
|
|
636
|
-
if (typeof window !== 'undefined') {
|
|
637
|
-
window.ninetailed = Object.assign({}, window.ninetailed, {
|
|
638
|
-
page: this.page.bind(this),
|
|
639
|
-
track: this.track.bind(this),
|
|
640
|
-
identify: this.identify.bind(this),
|
|
641
|
-
reset: this.reset.bind(this),
|
|
642
|
-
debug: this.debug.bind(this),
|
|
643
|
-
profile: this.profileState.profile
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
const selectVariant = (baseline, variants, {
|
|
650
|
-
status,
|
|
651
|
-
profile,
|
|
652
|
-
error
|
|
653
|
-
}, options = {
|
|
654
|
-
holdout: -1
|
|
655
|
-
}) => {
|
|
656
|
-
if (status === 'loading') {
|
|
657
|
-
return {
|
|
658
|
-
loading: true,
|
|
659
|
-
variant: Object.assign(Object.assign({}, baseline), {
|
|
660
|
-
id: 'baseline',
|
|
661
|
-
audience: {
|
|
662
|
-
id: 'baseline'
|
|
663
|
-
}
|
|
664
|
-
}),
|
|
665
|
-
audience: {
|
|
666
|
-
id: 'baseline'
|
|
667
|
-
},
|
|
668
|
-
isPersonalized: false,
|
|
669
|
-
error: null
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
if (status === 'error') {
|
|
673
|
-
return {
|
|
674
|
-
loading: false,
|
|
675
|
-
variant: Object.assign(Object.assign({}, baseline), {
|
|
676
|
-
id: 'baseline',
|
|
677
|
-
audience: {
|
|
678
|
-
id: 'baseline'
|
|
679
|
-
}
|
|
680
|
-
}),
|
|
681
|
-
audience: {
|
|
682
|
-
id: 'baseline'
|
|
683
|
-
},
|
|
684
|
-
isPersonalized: false,
|
|
685
|
-
error: error
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
const variant = variants.find(variant => {
|
|
689
|
-
var _a, _b;
|
|
690
|
-
return (_a = profile === null || profile === void 0 ? void 0 : profile.audiences) === null || _a === void 0 ? void 0 : _a.includes((_b = variant.audience) === null || _b === void 0 ? void 0 : _b.id);
|
|
691
|
-
});
|
|
692
|
-
if (variant) {
|
|
693
|
-
if ((options === null || options === void 0 ? void 0 : options.holdout) || -1 > ((profile === null || profile === void 0 ? void 0 : profile.random) || 0)) {
|
|
694
|
-
return {
|
|
695
|
-
loading: false,
|
|
696
|
-
variant: Object.assign(Object.assign({}, baseline), {
|
|
697
|
-
audience: {
|
|
698
|
-
id: 'baseline'
|
|
699
|
-
}
|
|
700
|
-
}),
|
|
701
|
-
audience: Object.assign(Object.assign({}, variant.audience), {
|
|
702
|
-
id: variant.audience.id
|
|
703
|
-
}),
|
|
704
|
-
isPersonalized: false,
|
|
705
|
-
error: null
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
return {
|
|
709
|
-
loading: false,
|
|
710
|
-
variant,
|
|
711
|
-
audience: Object.assign(Object.assign({}, variant.audience), {
|
|
712
|
-
id: variant.audience.id
|
|
713
|
-
}),
|
|
714
|
-
isPersonalized: true,
|
|
715
|
-
error: null
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
/**
|
|
719
|
-
* There was no matching audience found.
|
|
720
|
-
*/
|
|
721
|
-
return {
|
|
722
|
-
loading: false,
|
|
723
|
-
variant: Object.assign(Object.assign({}, baseline), {
|
|
724
|
-
id: 'baseline',
|
|
725
|
-
audience: {
|
|
726
|
-
id: 'baseline'
|
|
727
|
-
}
|
|
728
|
-
}),
|
|
729
|
-
audience: {
|
|
730
|
-
id: 'baseline'
|
|
731
|
-
},
|
|
732
|
-
isPersonalized: false,
|
|
733
|
-
error: null
|
|
734
|
-
};
|
|
735
|
-
};
|
|
736
|
-
|
|
737
|
-
const TrackComponentProperties = z.object({
|
|
738
|
-
variant: z.object({
|
|
739
|
-
id: z.string()
|
|
740
|
-
}),
|
|
741
|
-
audience: z.object({
|
|
742
|
-
id: z.string()
|
|
743
|
-
}),
|
|
744
|
-
isPersonalized: z.boolean()
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
class NinetailedPlugin {}
|
|
748
|
-
|
|
749
|
-
const ElementSeenPayloadSchema = z.object({
|
|
750
|
-
element: z.any(),
|
|
751
|
-
experience: z.object({
|
|
752
|
-
id: z.string(),
|
|
753
|
-
type: z.union([z.literal('nt_experiment'), z.literal('nt_personalization')]),
|
|
754
|
-
name: z.string().optional(),
|
|
755
|
-
description: z.string().optional()
|
|
756
|
-
}).optional().nullable(),
|
|
757
|
-
audience: z.object({
|
|
758
|
-
id: z.string(),
|
|
759
|
-
name: z.string().optional(),
|
|
760
|
-
description: z.string().optional()
|
|
761
|
-
}).optional().nullable().default({
|
|
762
|
-
id: 'ALL_VISITORS',
|
|
763
|
-
name: 'All Visitors',
|
|
764
|
-
description: 'This is the default all visitors audience as no audience was set.'
|
|
765
|
-
}),
|
|
766
|
-
variant: z.object({
|
|
767
|
-
id: z.string()
|
|
768
|
-
}).catchall(z.unknown()),
|
|
769
|
-
variantIndex: z.number()
|
|
770
|
-
});
|
|
771
|
-
|
|
772
|
-
const decodeExperienceVariantsMap = encodedExperienceVariantsMap => {
|
|
773
|
-
return encodedExperienceVariantsMap.split(',').map(experienceIdWithVariant => {
|
|
774
|
-
const [experienceId, _variantIndex] = experienceIdWithVariant.split('=');
|
|
775
|
-
const variantIndex = parseInt(_variantIndex);
|
|
776
|
-
if (!experienceId || !variantIndex) {
|
|
777
|
-
return null;
|
|
778
|
-
}
|
|
779
|
-
return {
|
|
780
|
-
experienceId,
|
|
781
|
-
variantIndex
|
|
782
|
-
};
|
|
783
|
-
}).filter(x => !!x).reduce((acc, curr) => Object.assign(Object.assign({}, acc), {
|
|
784
|
-
[curr.experienceId]: curr.variantIndex
|
|
785
|
-
}), {});
|
|
786
|
-
};
|
|
787
|
-
|
|
788
|
-
class OnChangeEmitter {
|
|
789
|
-
constructor() {
|
|
790
|
-
this.onChangeListeners = [];
|
|
791
|
-
}
|
|
792
|
-
addListener(listener) {
|
|
793
|
-
this.onChangeListeners.push(listener);
|
|
794
|
-
return () => {
|
|
795
|
-
this.removeOnChangeListener(listener);
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
invokeListeners() {
|
|
799
|
-
this.onChangeListeners.forEach(listener => listener());
|
|
800
|
-
}
|
|
801
|
-
removeOnChangeListener(listener) {
|
|
802
|
-
this.onChangeListeners = this.onChangeListeners.filter(l => l !== listener);
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
const hasOnChangeEmitter = arg => {
|
|
807
|
-
return typeof arg === 'object' && arg !== null && 'onChangeEmitter' in arg && typeof arg.onChangeEmitter === 'object' && arg.onChangeEmitter !== null && arg.onChangeEmitter.constructor === OnChangeEmitter;
|
|
808
|
-
};
|
|
809
|
-
|
|
810
|
-
const selectPluginsHavingOnChangeEmitter = plugins => {
|
|
811
|
-
const filteredPlugins = [];
|
|
812
|
-
for (const plugin of plugins) {
|
|
813
|
-
if (hasOnChangeEmitter(plugin)) {
|
|
814
|
-
filteredPlugins.push(plugin);
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
return filteredPlugins;
|
|
818
|
-
};
|
|
819
|
-
|
|
820
|
-
const hasExperienceSelectionMiddleware = arg => {
|
|
821
|
-
return typeof arg === 'object' && arg !== null && 'getExperienceSelectionMiddleware' in arg && typeof arg.getExperienceSelectionMiddleware === 'function';
|
|
822
|
-
};
|
|
823
|
-
|
|
824
|
-
const selectPluginsHavingExperienceSelectionMiddleware = plugins => {
|
|
825
|
-
const filteredPlugins = [];
|
|
826
|
-
for (const plugin of plugins) {
|
|
827
|
-
if (hasExperienceSelectionMiddleware(plugin)) {
|
|
828
|
-
filteredPlugins.push(plugin);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
return filteredPlugins;
|
|
832
|
-
};
|
|
833
|
-
|
|
834
|
-
const createPassThroughMiddleware = () => {
|
|
835
|
-
return ({
|
|
836
|
-
experience,
|
|
837
|
-
variant,
|
|
838
|
-
variantIndex
|
|
839
|
-
}) => {
|
|
840
|
-
return {
|
|
841
|
-
experience,
|
|
842
|
-
variant,
|
|
843
|
-
variantIndex
|
|
844
|
-
};
|
|
845
|
-
};
|
|
846
|
-
};
|
|
847
|
-
const makeExperienceSelectMiddleware = ({
|
|
848
|
-
plugins,
|
|
849
|
-
onChange,
|
|
850
|
-
experiences,
|
|
851
|
-
baseline,
|
|
852
|
-
profile
|
|
853
|
-
}) => {
|
|
854
|
-
let removeChangeListeners = [];
|
|
855
|
-
const pluginsHavingChangeEmitters = selectPluginsHavingOnChangeEmitter(plugins);
|
|
856
|
-
const prepareMiddleware = () => {
|
|
857
|
-
if (profile === null) {
|
|
858
|
-
return createPassThroughMiddleware();
|
|
859
|
-
}
|
|
860
|
-
const pluginsWithMiddleware = selectPluginsHavingExperienceSelectionMiddleware(plugins);
|
|
861
|
-
const middlewareFunctions = pluginsWithMiddleware.map(plugin => plugin.getExperienceSelectionMiddleware({
|
|
862
|
-
experiences,
|
|
863
|
-
baseline
|
|
864
|
-
}));
|
|
865
|
-
return pipe(...middlewareFunctions);
|
|
866
|
-
};
|
|
867
|
-
const addListeners = () => {
|
|
868
|
-
removeChangeListeners = pluginsHavingChangeEmitters.map(plugin => {
|
|
869
|
-
const listener = () => {
|
|
870
|
-
onChange();
|
|
871
|
-
};
|
|
872
|
-
return plugin.onChangeEmitter.addListener(listener);
|
|
873
|
-
});
|
|
874
|
-
};
|
|
875
|
-
const removeListeners = () => {
|
|
876
|
-
removeChangeListeners.forEach(listener => listener());
|
|
877
|
-
};
|
|
878
|
-
const middleware = prepareMiddleware();
|
|
879
|
-
return {
|
|
880
|
-
addListeners,
|
|
881
|
-
removeListeners,
|
|
882
|
-
middleware
|
|
883
|
-
};
|
|
884
|
-
};
|
|
885
|
-
|
|
886
|
-
export { ANONYMOUS_ID, COMPONENT, COMPONENT_START, CONSENT, DEBUG_FLAG, EMPTY_MERGE_ID, EXPERIENCES_FALLBACK_CACHE, ElementSeenPayloadSchema, HAS_SEEN_COMPONENT, HAS_SEEN_ELEMENT, LEGACY_ANONYMOUS_ID, Ninetailed, NinetailedPlugin, OnChangeEmitter, PAGE_HIDDEN, PLUGIN_NAME, PROFILE_CHANGE, PROFILE_FALLBACK_CACHE, PROFILE_RESET, SET_ENABLED_FEATURES, TrackComponentProperties, buildClientNinetailedRequestContext, decodeExperienceVariantsMap, makeExperienceSelectMiddleware, ninetailedCorePlugin, selectPluginsHavingExperienceSelectionMiddleware, selectPluginsHavingOnChangeEmitter, selectVariant };
|