mixpanel-browser 2.78.0 → 2.80.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.
Files changed (67) hide show
  1. package/.eslintrc.json +12 -0
  2. package/.github/workflows/integration-tests.yml +1 -0
  3. package/.github/workflows/openfeature-provider-tests.yml +31 -0
  4. package/CHANGELOG.md +14 -1
  5. package/build.sh +2 -2
  6. package/dist/async-modules/{mixpanel-recorder-BjSlYaNJ.min.js → mixpanel-recorder-B61POiHc.min.js} +2 -2
  7. package/dist/async-modules/mixpanel-recorder-B61POiHc.min.js.map +1 -0
  8. package/dist/async-modules/{mixpanel-recorder-zMBXIyeG.js → mixpanel-recorder-C3AW7mPl.js} +63 -35
  9. package/dist/async-modules/{mixpanel-targeting-UHf4eBfC.js → mixpanel-targeting-CBwOQJZw.js} +24 -13
  10. package/dist/async-modules/mixpanel-targeting-kdl-eE-1.min.js +2 -0
  11. package/dist/async-modules/mixpanel-targeting-kdl-eE-1.min.js.map +1 -0
  12. package/dist/mixpanel-core.cjs.d.ts +45 -1
  13. package/dist/mixpanel-core.cjs.js +577 -209
  14. package/dist/mixpanel-recorder.js +63 -35
  15. package/dist/mixpanel-recorder.min.js +1 -1
  16. package/dist/mixpanel-recorder.min.js.map +1 -1
  17. package/dist/mixpanel-targeting.js +24 -13
  18. package/dist/mixpanel-targeting.min.js +1 -1
  19. package/dist/mixpanel-targeting.min.js.map +1 -1
  20. package/dist/mixpanel-with-async-modules.cjs.d.ts +45 -1
  21. package/dist/mixpanel-with-async-modules.cjs.js +579 -211
  22. package/dist/mixpanel-with-async-recorder.cjs.d.ts +45 -1
  23. package/dist/mixpanel-with-async-recorder.cjs.js +579 -211
  24. package/dist/mixpanel-with-recorder.d.ts +45 -1
  25. package/dist/mixpanel-with-recorder.js +508 -136
  26. package/dist/mixpanel-with-recorder.min.d.ts +45 -1
  27. package/dist/mixpanel-with-recorder.min.js +1 -1
  28. package/dist/mixpanel.amd.d.ts +45 -1
  29. package/dist/mixpanel.amd.js +508 -136
  30. package/dist/mixpanel.cjs.d.ts +45 -1
  31. package/dist/mixpanel.cjs.js +508 -136
  32. package/dist/mixpanel.globals.js +579 -211
  33. package/dist/mixpanel.min.js +200 -190
  34. package/dist/mixpanel.module.d.ts +45 -1
  35. package/dist/mixpanel.module.js +508 -136
  36. package/dist/mixpanel.umd.d.ts +45 -1
  37. package/dist/mixpanel.umd.js +508 -136
  38. package/package.json +1 -1
  39. package/packages/openfeature-web-provider/README.md +357 -0
  40. package/packages/openfeature-web-provider/package-lock.json +1636 -0
  41. package/packages/openfeature-web-provider/package.json +51 -0
  42. package/packages/openfeature-web-provider/rollup.config.browser.mjs +26 -0
  43. package/packages/openfeature-web-provider/src/MixpanelProvider.ts +302 -0
  44. package/packages/openfeature-web-provider/src/index.ts +1 -0
  45. package/packages/openfeature-web-provider/src/types.ts +72 -0
  46. package/packages/openfeature-web-provider/test/MixpanelProvider.spec.ts +484 -0
  47. package/packages/openfeature-web-provider/tsconfig.json +15 -0
  48. package/src/autocapture/index.js +17 -12
  49. package/src/config.js +1 -1
  50. package/src/flags/flags-persistence.js +176 -0
  51. package/src/flags/index.js +176 -25
  52. package/src/index.d.ts +45 -1
  53. package/src/mixpanel-core.js +24 -7
  54. package/src/recorder/idb-config.js +16 -0
  55. package/src/recorder/recording-registry.js +7 -2
  56. package/src/recorder/session-recording.js +15 -6
  57. package/src/recorder-manager.js +7 -2
  58. package/src/request-queue.js +1 -2
  59. package/src/shared-lock.js +2 -3
  60. package/src/storage/indexed-db.js +16 -15
  61. package/src/storage/local-storage.js +5 -3
  62. package/src/utils.js +25 -12
  63. package/tsconfig.base.json +9 -0
  64. package/.claude/settings.local.json +0 -16
  65. package/dist/async-modules/mixpanel-recorder-BjSlYaNJ.min.js.map +0 -1
  66. package/dist/async-modules/mixpanel-targeting-BSHal4N9.min.js +0 -2
  67. package/dist/async-modules/mixpanel-targeting-BSHal4N9.min.js.map +0 -1
@@ -0,0 +1,484 @@
1
+ import chai, { expect } from 'chai';
2
+ import sinon from 'sinon';
3
+ import sinonChai from 'sinon-chai';
4
+ import { MixpanelProvider } from '../src/MixpanelProvider';
5
+ import { FlagsManager } from '../src/types';
6
+ import { ErrorCode } from '@openfeature/web-sdk';
7
+
8
+ chai.use(sinonChai);
9
+
10
+ describe('MixpanelProvider', () => {
11
+ let mockFlagsManager: FlagsManager;
12
+ let mockFlags: Map<string, any>;
13
+ let mockLogger: any;
14
+ let provider: MixpanelProvider;
15
+
16
+ beforeEach(() => {
17
+ mockFlags = new Map();
18
+ mockFlagsManager = {
19
+ are_flags_ready: sinon.stub().returns(true),
20
+ load_flags: sinon.stub().resolves(),
21
+ get_variant: sinon.stub().resolves(),
22
+ get_variant_sync: sinon.stub().callsFake((key: string, fallback: any) => {
23
+ return mockFlags.get(key) || fallback;
24
+ }),
25
+ get_variant_value: sinon.stub().resolves(),
26
+ get_variant_value_sync: sinon.stub(),
27
+ is_enabled: sinon.stub().resolves(),
28
+ is_enabled_sync: sinon.stub(),
29
+ update_context: sinon.stub().resolves(),
30
+ when_ready: sinon.stub().resolves(),
31
+ };
32
+ mockLogger = {
33
+ debug: sinon.stub(),
34
+ info: sinon.stub(),
35
+ warn: sinon.stub(),
36
+ error: sinon.stub(),
37
+ };
38
+ provider = new MixpanelProvider(mockFlagsManager);
39
+ });
40
+
41
+ afterEach(() => {
42
+ sinon.restore();
43
+ });
44
+
45
+ describe('metadata', () => {
46
+ it('should have correct provider name', () => {
47
+ expect(provider.metadata.name).to.equal('mixpanel-provider');
48
+ });
49
+
50
+ it('should run on client', () => {
51
+ expect(provider.runsOn).to.equal('client');
52
+ });
53
+ });
54
+
55
+ describe('initialize', () => {
56
+ it('should wait for when_ready to resolve', async () => {
57
+ let readyResolved = false;
58
+ (mockFlagsManager.when_ready as sinon.SinonStub).returns(
59
+ new Promise<void>((resolve) => {
60
+ setTimeout(() => {
61
+ readyResolved = true;
62
+ resolve();
63
+ }, 10);
64
+ })
65
+ );
66
+
67
+ expect(readyResolved).to.be.false;
68
+ await provider.initialize();
69
+ expect(readyResolved).to.be.true;
70
+ });
71
+
72
+ it('should call update_context when context is provided', async () => {
73
+ const context = { userId: 'test-user', email: 'test@example.com' };
74
+
75
+ await provider.initialize(context);
76
+
77
+ expect(mockFlagsManager.update_context).to.have.been.calledOnce;
78
+ expect(mockFlagsManager.update_context).to.have.been.calledWith(context, { replace: true });
79
+ });
80
+
81
+ it('should not call update_context when context is empty', async () => {
82
+ await provider.initialize({});
83
+
84
+ expect(mockFlagsManager.update_context).not.to.have.been.called;
85
+ });
86
+
87
+ it('should not call update_context when context is undefined', async () => {
88
+ await provider.initialize();
89
+
90
+ expect(mockFlagsManager.update_context).not.to.have.been.called;
91
+ });
92
+
93
+ it('should call when_ready during initialize', async () => {
94
+ await provider.initialize();
95
+
96
+ expect(mockFlagsManager.when_ready).to.have.been.calledOnce;
97
+ });
98
+ });
99
+
100
+ describe('onContextChange', () => {
101
+ it('should call update_context with new context and replace: true', async () => {
102
+ const oldContext = { userId: 'old-user' };
103
+ const newContext = { userId: 'new-user', plan: 'premium' };
104
+
105
+ await provider.onContextChange(oldContext, newContext);
106
+
107
+ expect(mockFlagsManager.update_context).to.have.been.calledOnce;
108
+ expect(mockFlagsManager.update_context).to.have.been.calledWith(newContext, { replace: true });
109
+ });
110
+
111
+ it('should handle empty new context', async () => {
112
+ await provider.onContextChange({ userId: 'old' }, {});
113
+
114
+ expect(mockFlagsManager.update_context).to.have.been.calledWith({}, { replace: true });
115
+ });
116
+ });
117
+
118
+ describe('onClose', () => {
119
+ it('should resolve without error', async () => {
120
+ await provider.onClose();
121
+ });
122
+
123
+ it('should return a promise', () => {
124
+ const result = provider.onClose();
125
+
126
+ expect(result).to.be.instanceOf(Promise);
127
+ });
128
+ });
129
+
130
+ describe('resolveBooleanEvaluation', () => {
131
+ it('should return correct value when flag exists with boolean value', () => {
132
+ mockFlags.set('feature-enabled', { key: 'enabled', value: true });
133
+
134
+ const result = provider.resolveBooleanEvaluation('feature-enabled', false, {}, mockLogger);
135
+
136
+ expect(result.value).to.equal(true);
137
+ expect(result.variant).to.equal('enabled');
138
+ expect(result.reason).to.equal('TARGETING_MATCH');
139
+ expect(result.errorCode).to.be.undefined;
140
+ });
141
+
142
+ it('should return false boolean value correctly', () => {
143
+ mockFlags.set('feature-disabled', { key: 'disabled', value: false });
144
+
145
+ const result = provider.resolveBooleanEvaluation('feature-disabled', true, {}, mockLogger);
146
+
147
+ expect(result.value).to.equal(false);
148
+ expect(result.variant).to.equal('disabled');
149
+ });
150
+
151
+ it('should return TYPE_MISMATCH error when value is not boolean (string)', () => {
152
+ mockFlags.set('string-flag', { key: 'variant-a', value: 'not-a-boolean' });
153
+
154
+ const result = provider.resolveBooleanEvaluation('string-flag', false, {}, mockLogger);
155
+
156
+ expect(result.value).to.equal(false);
157
+ expect(result.errorCode).to.equal(ErrorCode.TYPE_MISMATCH);
158
+ expect(result.errorMessage).to.include('not a boolean');
159
+ expect(result.reason).to.equal('ERROR');
160
+ });
161
+
162
+ it('should return TYPE_MISMATCH error when value is not boolean (number)', () => {
163
+ mockFlags.set('number-flag', { key: 'variant-a', value: 42 });
164
+
165
+ const result = provider.resolveBooleanEvaluation('number-flag', true, {}, mockLogger);
166
+
167
+ expect(result.value).to.equal(true);
168
+ expect(result.errorCode).to.equal(ErrorCode.TYPE_MISMATCH);
169
+ });
170
+
171
+ it('should return TYPE_MISMATCH error when value is not boolean (object)', () => {
172
+ mockFlags.set('object-flag', { key: 'variant-a', value: { some: 'object' } });
173
+
174
+ const result = provider.resolveBooleanEvaluation('object-flag', false, {}, mockLogger);
175
+
176
+ expect(result.value).to.equal(false);
177
+ expect(result.errorCode).to.equal(ErrorCode.TYPE_MISMATCH);
178
+ });
179
+ });
180
+
181
+ describe('resolveStringEvaluation', () => {
182
+ it('should return correct value when flag exists with string value', () => {
183
+ mockFlags.set('theme-flag', { key: 'dark', value: 'dark-mode' });
184
+
185
+ const result = provider.resolveStringEvaluation('theme-flag', 'light-mode', {}, mockLogger);
186
+
187
+ expect(result.value).to.equal('dark-mode');
188
+ expect(result.variant).to.equal('dark');
189
+ expect(result.reason).to.equal('TARGETING_MATCH');
190
+ expect(result.errorCode).to.be.undefined;
191
+ });
192
+
193
+ it('should return empty string value correctly', () => {
194
+ mockFlags.set('empty-string-flag', { key: 'empty', value: '' });
195
+
196
+ const result = provider.resolveStringEvaluation('empty-string-flag', 'default', {}, mockLogger);
197
+
198
+ expect(result.value).to.equal('');
199
+ expect(result.variant).to.equal('empty');
200
+ });
201
+
202
+ it('should return TYPE_MISMATCH error when value is not string (boolean)', () => {
203
+ mockFlags.set('bool-flag', { key: 'variant-a', value: true });
204
+
205
+ const result = provider.resolveStringEvaluation('bool-flag', 'default', {}, mockLogger);
206
+
207
+ expect(result.value).to.equal('default');
208
+ expect(result.errorCode).to.equal(ErrorCode.TYPE_MISMATCH);
209
+ expect(result.errorMessage).to.include('not a string');
210
+ expect(result.reason).to.equal('ERROR');
211
+ });
212
+
213
+ it('should return TYPE_MISMATCH error when value is not string (number)', () => {
214
+ mockFlags.set('number-flag', { key: 'variant-a', value: 123 });
215
+
216
+ const result = provider.resolveStringEvaluation('number-flag', 'default', {}, mockLogger);
217
+
218
+ expect(result.value).to.equal('default');
219
+ expect(result.errorCode).to.equal(ErrorCode.TYPE_MISMATCH);
220
+ });
221
+
222
+ it('should return TYPE_MISMATCH error when value is not string (object)', () => {
223
+ mockFlags.set('object-flag', { key: 'variant-a', value: { key: 'value' } });
224
+
225
+ const result = provider.resolveStringEvaluation('object-flag', 'default', {}, mockLogger);
226
+
227
+ expect(result.value).to.equal('default');
228
+ expect(result.errorCode).to.equal(ErrorCode.TYPE_MISMATCH);
229
+ });
230
+ });
231
+
232
+ describe('resolveNumberEvaluation', () => {
233
+ it('should return correct value when flag exists with number value', () => {
234
+ mockFlags.set('percentage-flag', { key: 'variant-50', value: 50 });
235
+
236
+ const result = provider.resolveNumberEvaluation('percentage-flag', 0, {}, mockLogger);
237
+
238
+ expect(result.value).to.equal(50);
239
+ expect(result.variant).to.equal('variant-50');
240
+ expect(result.reason).to.equal('TARGETING_MATCH');
241
+ expect(result.errorCode).to.be.undefined;
242
+ });
243
+
244
+ it('should return zero value correctly', () => {
245
+ mockFlags.set('zero-flag', { key: 'zero', value: 0 });
246
+
247
+ const result = provider.resolveNumberEvaluation('zero-flag', 100, {}, mockLogger);
248
+
249
+ expect(result.value).to.equal(0);
250
+ expect(result.variant).to.equal('zero');
251
+ });
252
+
253
+ it('should return negative number correctly', () => {
254
+ mockFlags.set('negative-flag', { key: 'negative', value: -42 });
255
+
256
+ const result = provider.resolveNumberEvaluation('negative-flag', 0, {}, mockLogger);
257
+
258
+ expect(result.value).to.equal(-42);
259
+ });
260
+
261
+ it('should return float value correctly', () => {
262
+ mockFlags.set('float-flag', { key: 'float', value: 3.14159 });
263
+
264
+ const result = provider.resolveNumberEvaluation('float-flag', 0, {}, mockLogger);
265
+
266
+ expect(result.value).to.equal(3.14159);
267
+ });
268
+
269
+ it('should return TYPE_MISMATCH error when value is not number (string)', () => {
270
+ mockFlags.set('string-flag', { key: 'variant-a', value: '42' });
271
+
272
+ const result = provider.resolveNumberEvaluation('string-flag', 0, {}, mockLogger);
273
+
274
+ expect(result.value).to.equal(0);
275
+ expect(result.errorCode).to.equal(ErrorCode.TYPE_MISMATCH);
276
+ expect(result.errorMessage).to.include('not a number');
277
+ expect(result.reason).to.equal('ERROR');
278
+ });
279
+
280
+ it('should return TYPE_MISMATCH error when value is not number (boolean)', () => {
281
+ mockFlags.set('bool-flag', { key: 'variant-a', value: true });
282
+
283
+ const result = provider.resolveNumberEvaluation('bool-flag', 0, {}, mockLogger);
284
+
285
+ expect(result.value).to.equal(0);
286
+ expect(result.errorCode).to.equal(ErrorCode.TYPE_MISMATCH);
287
+ });
288
+
289
+ it('should return TYPE_MISMATCH error when value is not number (object)', () => {
290
+ mockFlags.set('object-flag', { key: 'variant-a', value: { num: 42 } });
291
+
292
+ const result = provider.resolveNumberEvaluation('object-flag', 0, {}, mockLogger);
293
+
294
+ expect(result.value).to.equal(0);
295
+ expect(result.errorCode).to.equal(ErrorCode.TYPE_MISMATCH);
296
+ });
297
+ });
298
+
299
+ describe('resolveObjectEvaluation', () => {
300
+ it('should return correct value when flag exists with object value', () => {
301
+ const objectValue = { feature: 'enabled', level: 2, options: ['a', 'b'] };
302
+ mockFlags.set('config-flag', { key: 'variant-full', value: objectValue });
303
+
304
+ const result = provider.resolveObjectEvaluation('config-flag', {}, {}, mockLogger);
305
+
306
+ expect(result.value).to.deep.equal(objectValue);
307
+ expect(result.variant).to.equal('variant-full');
308
+ expect(result.reason).to.equal('TARGETING_MATCH');
309
+ expect(result.errorCode).to.be.undefined;
310
+ });
311
+
312
+ it('should return empty object value correctly', () => {
313
+ mockFlags.set('empty-object-flag', { key: 'empty', value: {} });
314
+
315
+ const result = provider.resolveObjectEvaluation('empty-object-flag', { default: true }, {}, mockLogger);
316
+
317
+ expect(result.value).to.deep.equal({});
318
+ expect(result.variant).to.equal('empty');
319
+ });
320
+
321
+ it('should return array value correctly (object accepts any type)', () => {
322
+ const arrayValue = [1, 2, 3, 'four'];
323
+ mockFlags.set('array-flag', { key: 'array-variant', value: arrayValue });
324
+
325
+ const result = provider.resolveObjectEvaluation('array-flag', [], {}, mockLogger);
326
+
327
+ expect(result.value).to.deep.equal(arrayValue);
328
+ expect(result.variant).to.equal('array-variant');
329
+ expect(result.errorCode).to.be.undefined;
330
+ });
331
+
332
+ it('should return nested object value correctly', () => {
333
+ const nestedValue = { level1: { level2: { level3: 'deep' } } };
334
+ mockFlags.set('nested-flag', { key: 'nested', value: nestedValue });
335
+
336
+ const result = provider.resolveObjectEvaluation('nested-flag', {}, {}, mockLogger);
337
+
338
+ expect(result.value).to.deep.equal(nestedValue);
339
+ });
340
+
341
+ it('should return string value via object evaluation (object accepts any type)', () => {
342
+ mockFlags.set('string-flag', { key: 'variant-a', value: 'not-an-object' });
343
+
344
+ const result = provider.resolveObjectEvaluation('string-flag', {}, {}, mockLogger);
345
+
346
+ expect(result.value).to.equal('not-an-object');
347
+ expect(result.variant).to.equal('variant-a');
348
+ expect(result.errorCode).to.be.undefined;
349
+ });
350
+
351
+ it('should return number value via object evaluation (object accepts any type)', () => {
352
+ mockFlags.set('number-flag', { key: 'variant-a', value: 42 });
353
+
354
+ const result = provider.resolveObjectEvaluation('number-flag', {}, {}, mockLogger);
355
+
356
+ expect(result.value).to.equal(42);
357
+ expect(result.errorCode).to.be.undefined;
358
+ });
359
+
360
+ it('should return boolean value via object evaluation (object accepts any type)', () => {
361
+ mockFlags.set('bool-flag', { key: 'variant-a', value: true });
362
+
363
+ const result = provider.resolveObjectEvaluation('bool-flag', {}, {}, mockLogger);
364
+
365
+ expect(result.value).to.equal(true);
366
+ expect(result.errorCode).to.be.undefined;
367
+ });
368
+
369
+ it('should return null value via object evaluation (object accepts any type)', () => {
370
+ mockFlags.set('null-flag', { key: 'variant-a', value: null });
371
+
372
+ const result = provider.resolveObjectEvaluation('null-flag', { default: true }, {}, mockLogger);
373
+
374
+ expect(result.value).to.be.null;
375
+ expect(result.variant).to.equal('variant-a');
376
+ expect(result.errorCode).to.be.undefined;
377
+ });
378
+ });
379
+
380
+ // FLAG_NOT_FOUND and PROVIDER_NOT_READY share the same code path (resolveFlag)
381
+ // across all resolve methods — test them once parameterized rather than 4x each.
382
+ describe('shared error behavior', () => {
383
+ const resolveMethodCases = [
384
+ { method: 'resolveBooleanEvaluation' as const, defaultValue: false },
385
+ { method: 'resolveStringEvaluation' as const, defaultValue: 'fallback' },
386
+ { method: 'resolveNumberEvaluation' as const, defaultValue: 99 },
387
+ { method: 'resolveObjectEvaluation' as const, defaultValue: { fallback: true } },
388
+ ];
389
+
390
+ resolveMethodCases.forEach(({ method, defaultValue }) => {
391
+ it(`${method} should return FLAG_NOT_FOUND when flag does not exist`, () => {
392
+ const result = (provider as any)[method]('non-existent-flag', defaultValue, {}, mockLogger);
393
+
394
+ expect(result.value).to.deep.equal(defaultValue);
395
+ expect(result.errorCode).to.equal(ErrorCode.FLAG_NOT_FOUND);
396
+ expect(result.errorMessage).to.include('not found');
397
+ expect(result.reason).to.equal('DEFAULT');
398
+ });
399
+ });
400
+
401
+ resolveMethodCases.forEach(({ method, defaultValue }) => {
402
+ it(`${method} should return PROVIDER_NOT_READY when flags not loaded`, () => {
403
+ (mockFlagsManager.are_flags_ready as sinon.SinonStub).returns(false);
404
+
405
+ const result = (provider as any)[method]('any-flag', defaultValue, {}, mockLogger);
406
+
407
+ expect(result.value).to.deep.equal(defaultValue);
408
+ expect(result.errorCode).to.equal(ErrorCode.PROVIDER_NOT_READY);
409
+ expect(result.errorMessage).to.include('not been loaded');
410
+ expect(result.reason).to.equal('ERROR');
411
+ });
412
+ });
413
+ });
414
+
415
+ describe('edge cases', () => {
416
+ it('should handle flag with experiment metadata', () => {
417
+ mockFlags.set('experiment-flag', {
418
+ key: 'treatment',
419
+ value: true,
420
+ experiment_id: 'exp-123',
421
+ is_experiment_active: true,
422
+ is_qa_tester: false,
423
+ });
424
+
425
+ const result = provider.resolveBooleanEvaluation('experiment-flag', false, {}, mockLogger);
426
+
427
+ expect(result.value).to.equal(true);
428
+ expect(result.variant).to.equal('treatment');
429
+ });
430
+
431
+ it('should handle special characters in flag key', () => {
432
+ mockFlags.set('flag-with-special_chars.and/slashes', { key: 'variant', value: 'special' });
433
+
434
+ const result = provider.resolveStringEvaluation(
435
+ 'flag-with-special_chars.and/slashes',
436
+ 'default',
437
+ {},
438
+ mockLogger
439
+ );
440
+
441
+ expect(result.value).to.equal('special');
442
+ });
443
+
444
+ // NaN passes typeof === 'number', documenting this intentional passthrough
445
+ it('should handle NaN as valid number (typeof NaN === number)', () => {
446
+ mockFlags.set('nan-flag', { key: 'nan', value: NaN });
447
+
448
+ const result = provider.resolveNumberEvaluation('nan-flag', 0, {}, mockLogger);
449
+
450
+ expect(result.value).to.be.NaN;
451
+ expect(result.errorCode).to.be.undefined;
452
+ });
453
+
454
+ it('should handle Infinity as valid number', () => {
455
+ mockFlags.set('infinity-flag', { key: 'infinity', value: Infinity });
456
+
457
+ const result = provider.resolveNumberEvaluation('infinity-flag', 0, {}, mockLogger);
458
+
459
+ expect(result.value).to.equal(Infinity);
460
+ });
461
+
462
+ it('should handle evaluation context passed to methods', () => {
463
+ mockFlags.set('context-flag', { key: 'variant', value: true });
464
+
465
+ const context = { userId: 'user-123', plan: 'premium' };
466
+ const result = provider.resolveBooleanEvaluation('context-flag', false, context, mockLogger);
467
+
468
+ expect(result.value).to.equal(true);
469
+ });
470
+
471
+ it('should create new provider instances independently', () => {
472
+ const provider1 = new MixpanelProvider(mockFlagsManager);
473
+ const provider2 = new MixpanelProvider(mockFlagsManager);
474
+
475
+ mockFlags.set('test-flag', { key: 'v', value: 'test' });
476
+
477
+ provider1.resolveStringEvaluation('test-flag', '', {}, mockLogger);
478
+ expect(mockFlagsManager.get_variant_sync).to.have.been.calledOnce;
479
+
480
+ provider2.resolveStringEvaluation('test-flag', '', {}, mockLogger);
481
+ expect(mockFlagsManager.get_variant_sync).to.have.been.calledTwice;
482
+ });
483
+ });
484
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "target": "ES2018",
5
+ "module": "commonjs",
6
+ "lib": ["ES2018", "DOM"],
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "sourceMap": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src"
12
+ },
13
+ "include": ["src/**/*"],
14
+ "exclude": ["node_modules", "dist", "test"]
15
+ }
@@ -292,7 +292,7 @@ Autocapture.prototype._initScrollDepthTracking = function() {
292
292
  };
293
293
 
294
294
  Autocapture.prototype.initClickTracking = function() {
295
- window.removeEventListener(EV_CLICK, this.listenerClick);
295
+ window.removeEventListener(EV_CLICK, this.listenerClick, true);
296
296
 
297
297
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
298
298
  return;
@@ -305,7 +305,7 @@ Autocapture.prototype.initClickTracking = function() {
305
305
  }
306
306
  this.trackDomEvent(ev, MP_EV_CLICK);
307
307
  }.bind(this);
308
- window.addEventListener(EV_CLICK, this.listenerClick);
308
+ window.addEventListener(EV_CLICK, this.listenerClick, true);
309
309
  };
310
310
 
311
311
  Autocapture.prototype.initDeadClickTracking = function() {
@@ -340,12 +340,12 @@ Autocapture.prototype.initDeadClickTracking = function() {
340
340
  }
341
341
  this._deadClickTracker.trackClick(ev, normalizedConfig);
342
342
  }.bind(this);
343
- window.addEventListener(EV_CLICK, this.listenerDeadClick);
343
+ window.addEventListener(EV_CLICK, this.listenerDeadClick, true);
344
344
  }
345
345
  };
346
346
 
347
347
  Autocapture.prototype.initInputTracking = function() {
348
- window.removeEventListener(EV_CHANGE, this.listenerChange);
348
+ window.removeEventListener(EV_CHANGE, this.listenerChange, true);
349
349
 
350
350
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
351
351
  return;
@@ -358,20 +358,21 @@ Autocapture.prototype.initInputTracking = function() {
358
358
  }
359
359
  this.trackDomEvent(ev, MP_EV_INPUT);
360
360
  }.bind(this);
361
- window.addEventListener(EV_CHANGE, this.listenerChange);
361
+ window.addEventListener(EV_CHANGE, this.listenerChange, true);
362
362
  };
363
363
 
364
364
  Autocapture.prototype.initPageviewTracking = function() {
365
365
  window.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
366
366
 
367
- if (!this.pageviewTrackingConfig()) {
367
+ if (!this.pageviewTrackingConfig() && !this.mp.get_config('record_heatmap_data')) {
368
368
  return;
369
369
  }
370
370
  logger.log('Initializing pageview tracking');
371
371
 
372
372
  var previousTrackedUrl = '';
373
373
  var tracked = false;
374
- if (!this.currentUrlBlocked()) {
374
+ // Track initial pageview if pageview tracking enabled OR heatmap recording is active
375
+ if ((this.pageviewTrackingConfig() || this.mp.is_recording_heatmap_data()) && !this.currentUrlBlocked()) {
375
376
  tracked = this.mp.track_pageview(DEFAULT_PROPS);
376
377
  }
377
378
  if (tracked) {
@@ -387,6 +388,10 @@ Autocapture.prototype.initPageviewTracking = function() {
387
388
  var shouldTrack = false;
388
389
  var didPathChange = currentUrl.split('#')[0].split('?')[0] !== previousTrackedUrl.split('#')[0].split('?')[0];
389
390
  var trackPageviewOption = this.pageviewTrackingConfig();
391
+ if (!trackPageviewOption && this.mp.is_recording_heatmap_data()) {
392
+ trackPageviewOption = PAGEVIEW_OPTION_FULL_URL;
393
+ }
394
+
390
395
  if (trackPageviewOption === PAGEVIEW_OPTION_FULL_URL) {
391
396
  shouldTrack = currentUrl !== previousTrackedUrl;
392
397
  } else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING) {
@@ -410,7 +415,7 @@ Autocapture.prototype.initPageviewTracking = function() {
410
415
  };
411
416
 
412
417
  Autocapture.prototype.initRageClickTracking = function() {
413
- window.removeEventListener(EV_CLICK, this.listenerRageClick);
418
+ window.removeEventListener(EV_CLICK, this.listenerRageClick, true);
414
419
 
415
420
  var rageClickConfig = this._getClickTrackingConfig(CONFIG_TRACK_RAGE_CLICK);
416
421
  if (!rageClickConfig && !this.mp.get_config('record_heatmap_data')) {
@@ -436,7 +441,7 @@ Autocapture.prototype.initRageClickTracking = function() {
436
441
  this.trackDomEvent(ev, MP_EV_RAGE_CLICK);
437
442
  }
438
443
  }.bind(this);
439
- window.addEventListener(EV_CLICK, this.listenerRageClick);
444
+ window.addEventListener(EV_CLICK, this.listenerRageClick, true);
440
445
  };
441
446
 
442
447
  Autocapture.prototype.initScrollTracking = function() {
@@ -497,7 +502,7 @@ Autocapture.prototype.initScrollTracking = function() {
497
502
  };
498
503
 
499
504
  Autocapture.prototype.initSubmitTracking = function() {
500
- window.removeEventListener(EV_SUBMIT, this.listenerSubmit);
505
+ window.removeEventListener(EV_SUBMIT, this.listenerSubmit, true);
501
506
 
502
507
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
503
508
  return;
@@ -510,7 +515,7 @@ Autocapture.prototype.initSubmitTracking = function() {
510
515
  }
511
516
  this.trackDomEvent(ev, MP_EV_SUBMIT);
512
517
  }.bind(this);
513
- window.addEventListener(EV_SUBMIT, this.listenerSubmit);
518
+ window.addEventListener(EV_SUBMIT, this.listenerSubmit, true);
514
519
  };
515
520
 
516
521
  Autocapture.prototype.initPageLeaveTracking = function() {
@@ -566,7 +571,7 @@ Autocapture.prototype.initPageLeaveTracking = function() {
566
571
 
567
572
  Autocapture.prototype.stopDeadClickTracking = function() {
568
573
  if (this.listenerDeadClick) {
569
- window.removeEventListener(EV_CLICK, this.listenerDeadClick);
574
+ window.removeEventListener(EV_CLICK, this.listenerDeadClick, true);
570
575
  this.listenerDeadClick = null;
571
576
  }
572
577
 
package/src/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export var Config = {
2
2
  DEBUG: false,
3
- LIB_VERSION: '2.78.0'
3
+ LIB_VERSION: '2.80.0'
4
4
  };
5
5
 
6
6
  // Window global names for async modules