mixpanel-browser 2.74.0 → 2.76.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 (61) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/.github/workflows/integration-tests.yml +2 -2
  3. package/.github/workflows/unit-tests.yml +3 -3
  4. package/CHANGELOG.md +15 -0
  5. package/README.md +2 -2
  6. package/build.sh +10 -8
  7. package/dist/async-modules/mixpanel-recorder-bIS4LMGd.js +23595 -0
  8. package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js +2 -0
  9. package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js.map +1 -0
  10. package/dist/async-modules/mixpanel-targeting-BcAPS-Mz.js +2520 -0
  11. package/dist/async-modules/mixpanel-targeting-VOeN7RWY.min.js +2 -0
  12. package/dist/async-modules/mixpanel-targeting-VOeN7RWY.min.js.map +1 -0
  13. package/dist/mixpanel-core.cjs.d.ts +68 -0
  14. package/dist/mixpanel-core.cjs.js +802 -337
  15. package/dist/mixpanel-recorder.js +828 -40
  16. package/dist/mixpanel-recorder.min.js +1 -1
  17. package/dist/mixpanel-recorder.min.js.map +1 -1
  18. package/dist/mixpanel-targeting.js +2520 -0
  19. package/dist/mixpanel-targeting.min.js +2 -0
  20. package/dist/mixpanel-targeting.min.js.map +1 -0
  21. package/dist/mixpanel-with-async-modules.cjs.d.ts +590 -0
  22. package/dist/mixpanel-with-async-modules.cjs.js +9867 -0
  23. package/dist/mixpanel-with-async-recorder.cjs.d.ts +68 -0
  24. package/dist/mixpanel-with-async-recorder.cjs.js +802 -337
  25. package/dist/mixpanel-with-recorder.d.ts +68 -0
  26. package/dist/mixpanel-with-recorder.js +1591 -343
  27. package/dist/mixpanel-with-recorder.min.d.ts +68 -0
  28. package/dist/mixpanel-with-recorder.min.js +1 -1
  29. package/dist/mixpanel.amd.d.ts +68 -0
  30. package/dist/mixpanel.amd.js +2124 -345
  31. package/dist/mixpanel.cjs.d.ts +68 -0
  32. package/dist/mixpanel.cjs.js +2124 -345
  33. package/dist/mixpanel.globals.js +802 -337
  34. package/dist/mixpanel.min.js +185 -175
  35. package/dist/mixpanel.module.d.ts +68 -0
  36. package/dist/mixpanel.module.js +2124 -345
  37. package/dist/mixpanel.umd.d.ts +68 -0
  38. package/dist/mixpanel.umd.js +2124 -345
  39. package/dist/rrweb-bundled.js +119 -5
  40. package/dist/rrweb-compiled.js +116 -5
  41. package/logo.svg +5 -0
  42. package/package.json +5 -3
  43. package/rollup.config.mjs +189 -40
  44. package/src/autocapture/index.js +10 -27
  45. package/src/config.js +9 -3
  46. package/src/flags/index.js +269 -9
  47. package/src/index.d.ts +68 -0
  48. package/src/loaders/loader-module.js +1 -0
  49. package/src/mixpanel-core.js +83 -109
  50. package/src/recorder/index.js +2 -1
  51. package/src/recorder/recorder.js +5 -1
  52. package/src/recorder/rrweb-network-plugin.js +649 -0
  53. package/src/recorder/session-recording.js +31 -11
  54. package/src/recorder-manager.js +216 -0
  55. package/src/request-batcher.js +1 -1
  56. package/src/targeting/event-matcher.js +42 -0
  57. package/src/targeting/index.js +11 -0
  58. package/src/targeting/loader.js +36 -0
  59. package/src/utils.js +14 -9
  60. package/testServer.js +55 -0
  61. /package/src/loaders/{loader-module-with-async-recorder.js → loader-module-with-async-modules.js} +0 -0
package/rollup.config.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import alias from '@rollup/plugin-alias';
2
2
  import closureCompiler from '@ampproject/rollup-plugin-closure-compiler';
3
+ import commonjs from '@rollup/plugin-commonjs';
3
4
  import fs from 'fs';
4
5
  import path from 'path';
5
6
  import esbuild from 'rollup-plugin-esbuild';
@@ -7,21 +8,52 @@ import nodeResolve from '@rollup/plugin-node-resolve';
7
8
  import swc from '@rollup/plugin-swc';
8
9
  import browserTestBuilds from './tests/browser/client/rollup.config.mjs';
9
10
 
11
+ // Capture the hash of async-loaded bundles so we can inject the correct filenames into the main build
12
+ const asyncBundleManifest = {};
13
+
10
14
  const COMPILED_RRWEB_PATH = `build/rrweb-compiled.js`;
11
15
  const BUNDLED_RRWEB_PATH = `build/rrweb-bundled.js`;
12
16
 
17
+ const ASYNC_MODULE_BUILD_PATH = `build/async-modules`;
18
+ const RECORDER_BUNDLE_NAME = `mixpanel_recorder`;
19
+ const RECORDER_MIN_BUNDLE_NAME = `mixpanel_recorder_min`;
20
+ const TARGETING_BUNDLE_NAME = `mixpanel_targeting`;
21
+ const TARGETING_MIN_BUNDLE_NAME = `mixpanel_targeting_min`;
22
+
23
+
13
24
  // Delete output files at build start so build errors don't silently use stale files
25
+ let hasCleanedOnFull = false;
14
26
  const cleanOnRebuild = () => {
15
- let outputFiles = [];
27
+ let filesToClean = [];
16
28
  return {
17
29
  name: `clean-on-rebuild`,
18
30
  options(opts) {
19
31
  const outputs = Array.isArray(opts.output) ? opts.output : [opts.output];
20
- outputFiles = outputs.map(o => o && o.file).filter(Boolean);
32
+ filesToClean = outputs.flatMap(o => {
33
+ if (o.file) {
34
+ return [o.file];
35
+ }
36
+
37
+ if (o.dir && o.entryFileNames) {
38
+ if (!fs.existsSync(o.dir)) return [];
39
+
40
+ // match against regexes like mixpanel-recorder-[hash].js to find the actual output file(s) to clean up
41
+ const regex = new RegExp(`^` + o.entryFileNames.replace(/\./g, `\\.`).replace(`[hash]`, `.+`));
42
+ return fs.readdirSync(o.dir).filter(f => regex.test(f)).map(f => path.join(o.dir, f));
43
+ }
44
+ return [];
45
+ });
21
46
  },
22
47
  buildStart() {
23
- // Delete outputs right before this specific build runs
24
- for (const file of outputFiles) {
48
+ if (process.env.FULL && !hasCleanedOnFull) {
49
+ if (fs.existsSync(`build`)) {
50
+ fs.rmSync(`build`, {recursive: true});
51
+ }
52
+ hasCleanedOnFull = true;
53
+ return;
54
+ }
55
+
56
+ for (const file of filesToClean) {
25
57
  if (fs.existsSync(file)) {
26
58
  fs.unlinkSync(file);
27
59
  }
@@ -30,10 +62,53 @@ const cleanOnRebuild = () => {
30
62
  };
31
63
  };
32
64
 
33
- const withCleanOnRebuild = (builds) => builds.map(build => ({
34
- ...build,
35
- plugins: [cleanOnRebuild(), ...(build.plugins || [])]
36
- }));
65
+ // Capture the resolved (hashed) output filenames from recorder/targeting builds.
66
+ // Stores them in `asyncBundleManifest` so `injectAsyncBundleNames` can inject them into the main build.
67
+ const writeToManifest = () => ({
68
+ name: `write-to-manifest`,
69
+ generateBundle(options, bundle) {
70
+ for (const chunk of Object.values(bundle)) {
71
+ if (chunk.type === `chunk`) {
72
+ asyncBundleManifest[options.name] = chunk.fileName;
73
+ }
74
+ }
75
+ }
76
+ });
77
+
78
+ // Replace __MP_*__ placeholders in src/config.js with build-time values.
79
+ // For the main library build (which runs after), they will be the real hashed filenames.
80
+ const injectAsyncBundleNames = () => ({
81
+ name: `inject-async-bundle-names`,
82
+ renderChunk(code, _chunk, outputOptions) {
83
+ const isMinified = outputOptions.file && outputOptions.file.endsWith(`.min.js`);
84
+
85
+ let recorderBundleName = RECORDER_BUNDLE_NAME;
86
+ let targetingBundleName = TARGETING_BUNDLE_NAME;
87
+ if (isMinified) {
88
+ recorderBundleName = RECORDER_MIN_BUNDLE_NAME;
89
+ targetingBundleName = TARGETING_MIN_BUNDLE_NAME;
90
+ }
91
+
92
+ if (!asyncBundleManifest[recorderBundleName] || !asyncBundleManifest[targetingBundleName]) {
93
+ throw new Error(`Async bundle names not found in manifest. Manifest contents: ${JSON.stringify(asyncBundleManifest)}`);
94
+ }
95
+
96
+ const replaced = code
97
+ .replace(`__MP_RECORDER_FILENAME__`, asyncBundleManifest[recorderBundleName])
98
+ .replace(`__MP_TARGETING_FILENAME__`, asyncBundleManifest[targetingBundleName]);
99
+ return { code: replaced, map: null };
100
+ }
101
+ });
102
+
103
+ const withBasePlugins = (builds) => builds.map((build) => {
104
+ return {
105
+ ...build,
106
+ plugins: [
107
+ cleanOnRebuild(),
108
+ ...(build.plugins || [])
109
+ ]
110
+ };
111
+ });
37
112
 
38
113
  const aliasRrweb = () => alias({
39
114
  entries: [
@@ -68,6 +143,96 @@ const COMMON_CLOSURE_FLAGS = {
68
143
 
69
144
  const MINIFY = process.env.MINIFY || process.env.FULL;
70
145
 
146
+ /**
147
+ * Async bundles for the recorder and targeting modules.
148
+ *
149
+ * These are loaded on-demand by the main library at runtime via script injection.
150
+ * Filenames include a content hash so the main build can reference an exact, immutable
151
+ * bundle — guaranteeing the async module matches what the library expects.
152
+ * The hash is captured in `asyncBundleManifest` and injected into the main build
153
+ * via `injectAsyncBundleNames`.
154
+ *
155
+ * We also produce non-hashed "legacy" versions of each bundle for backward compatibility
156
+ * with customers who proxy specific filenames from the Mixpanel CDN.
157
+ */
158
+ const ASYNC_BUNDLE_BUILDS = [
159
+ // Recorder bundle (wraps rrweb)
160
+ {
161
+ input: `src/recorder/index.js`,
162
+ output: [
163
+ {
164
+ dir: ASYNC_MODULE_BUILD_PATH,
165
+ entryFileNames: `mixpanel-recorder-[hash].js`,
166
+ name: RECORDER_BUNDLE_NAME,
167
+ format: `iife`,
168
+ },
169
+ {
170
+ file: `build/mixpanel-recorder.js`,
171
+ name: `legacy_recorder_bundle`,
172
+ format: `iife`,
173
+ },
174
+ ...(MINIFY
175
+ ? [
176
+ {
177
+ dir: ASYNC_MODULE_BUILD_PATH,
178
+ entryFileNames: `mixpanel-recorder-[hash].min.js`,
179
+ name: RECORDER_MIN_BUNDLE_NAME,
180
+ format: `iife`,
181
+ plugins: [esbuild({target: `es5`, minify: true, sourceMap: true})],
182
+ sourcemap: true,
183
+ },
184
+ {
185
+ file: `build/mixpanel-recorder.min.js`,
186
+ name: `legacy_recorder_min_bundle`,
187
+ format: `iife`,
188
+ plugins: [esbuild({target: `es5`, minify: true, sourceMap: true})],
189
+ sourcemap: true,
190
+ },
191
+ ]
192
+ : []),
193
+ ],
194
+ plugins: [aliasRrweb(), writeToManifest()],
195
+ },
196
+
197
+ // Targeting bundle (feature flags / experiments)
198
+ {
199
+ input: `src/targeting/index.js`,
200
+ output: [
201
+ {
202
+ dir: ASYNC_MODULE_BUILD_PATH,
203
+ entryFileNames: `mixpanel-targeting-[hash].js`,
204
+ name: TARGETING_BUNDLE_NAME,
205
+ format: `iife`,
206
+ },
207
+ {
208
+ file: `build/mixpanel-targeting.js`,
209
+ name: `legacy_targeting_bundle`,
210
+ format: `iife`,
211
+ },
212
+ ...(MINIFY
213
+ ? [
214
+ {
215
+ dir: ASYNC_MODULE_BUILD_PATH,
216
+ entryFileNames: `mixpanel-targeting-[hash].min.js`,
217
+ name: TARGETING_MIN_BUNDLE_NAME,
218
+ format: `iife`,
219
+ plugins: [esbuild({target: `es5`, minify: true, sourceMap: true})],
220
+ sourcemap: true,
221
+ },
222
+ {
223
+ file: `build/mixpanel-targeting.min.js`,
224
+ name: `legacy_targeting_min_bundle`,
225
+ format: `iife`,
226
+ plugins: [esbuild({target: `es5`, minify: true, sourceMap: true})],
227
+ sourcemap: true,
228
+ },
229
+ ]
230
+ : []),
231
+ ],
232
+ plugins: [commonjs(), nodeResolve({browser: true}), writeToManifest()],
233
+ },
234
+ ];
235
+
71
236
  // Main builds used to develop / iterate quickly
72
237
  const MAIN_BUILDS = [
73
238
  {
@@ -91,30 +256,7 @@ const MAIN_BUILDS = [
91
256
  plugins: [swc({swc: {jsc: {target: `es5`}}})]
92
257
  },
93
258
 
94
- // IIFE recorder bundle that is loaded asynchronously
95
- // rrweb uses esbuild to minify, so do that here as well
96
- {
97
- input: `src/recorder/index.js`,
98
- output: [
99
- {
100
- file: `build/mixpanel-recorder.js`,
101
- name: `mixpanel_recorder`,
102
- format: `iife`,
103
- },
104
- ...(MINIFY
105
- ? [
106
- {
107
- file: `build/mixpanel-recorder.min.js`,
108
- name: `mixpanel_recorder`,
109
- format: `iife`,
110
- plugins: [esbuild({target: `es5`, minify: true, sourceMap: true})],
111
- sourcemap: true,
112
- },
113
- ]
114
- : []),
115
- ],
116
- plugins: [aliasRrweb()],
117
- },
259
+ ...ASYNC_BUNDLE_BUILDS,
118
260
 
119
261
  // IIFE main mixpanel build
120
262
  {
@@ -136,6 +278,7 @@ const MAIN_BUILDS = [
136
278
  : []),
137
279
  ],
138
280
  plugins: [
281
+ injectAsyncBundleNames(),
139
282
  nodeResolve({
140
283
  browser: true,
141
284
  main: true,
@@ -203,8 +346,7 @@ const ALL_BUILDS = [
203
346
  ],
204
347
  },
205
348
 
206
-
207
- // Modules builds that are bundled with the recorder
349
+ // Modules builds that are bundled with the recorder and targeting
208
350
  {
209
351
  input: `src/loaders/loader-module.js`,
210
352
  output: [
@@ -230,6 +372,7 @@ const ALL_BUILDS = [
230
372
  },
231
373
  ],
232
374
  plugins: [
375
+ commonjs(),
233
376
  aliasRrweb(),
234
377
  nodeResolve({
235
378
  browser: true,
@@ -240,7 +383,6 @@ const ALL_BUILDS = [
240
383
  ],
241
384
  },
242
385
 
243
-
244
386
  // Alternative CJS builds without recorder
245
387
  {
246
388
  input: `src/loaders/loader-module-core.js`,
@@ -262,8 +404,14 @@ const ALL_BUILDS = [
262
404
  ],
263
405
  },
264
406
  {
265
- input: `src/loaders/loader-module-with-async-recorder.js`,
407
+ input: `src/loaders/loader-module-with-async-modules.js`,
266
408
  output: [
409
+ {
410
+ file: `build/mixpanel-with-async-modules.cjs.js`,
411
+ name: `mixpanel`,
412
+ format: `cjs`,
413
+ },
414
+ // Backward compatibility: keep old output filename for existing users
267
415
  {
268
416
  file: `build/mixpanel-with-async-recorder.cjs.js`,
269
417
  name: `mixpanel`,
@@ -271,6 +419,7 @@ const ALL_BUILDS = [
271
419
  },
272
420
  ],
273
421
  plugins: [
422
+ injectAsyncBundleNames(),
274
423
  aliasRrweb(),
275
424
  nodeResolve({
276
425
  browser: true,
@@ -280,9 +429,9 @@ const ALL_BUILDS = [
280
429
  copyTypes(),
281
430
  ],
282
431
  },
283
-
284
- ...browserTestBuilds,
285
432
  ];
286
433
 
287
- const builds = process.env.FULL ? ALL_BUILDS : MAIN_BUILDS;
288
- export default withCleanOnRebuild(builds);
434
+ const srcBuilds = process.env.FULL ? ALL_BUILDS : MAIN_BUILDS;
435
+ const builds = [...srcBuilds, ...browserTestBuilds];
436
+
437
+ export default withBasePlugins(builds);
@@ -1,4 +1,4 @@
1
- import { _, document, safewrap, safewrapClass } from '../utils';
1
+ import { _, document, safewrap, safewrapClass, urlMatchesRegexList } from '../utils';
2
2
  import { window } from '../window';
3
3
  import {
4
4
  getPolyfillScrollEndFunction, getPropsForDOMEvent, logger, minDOMApisSupported,
@@ -112,27 +112,15 @@ Autocapture.prototype.getConfig = function(key) {
112
112
  };
113
113
 
114
114
  Autocapture.prototype.currentUrlBlocked = function() {
115
- var i;
116
115
  var currentUrl = _.info.currentUrl();
117
116
 
118
117
  var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
119
118
  if (allowUrlRegexes.length) {
120
119
  // we're using an allowlist, only track if current URL matches
121
- var allowed = false;
122
- for (i = 0; i < allowUrlRegexes.length; i++) {
123
- var allowRegex = allowUrlRegexes[i];
124
- try {
125
- if (currentUrl.match(allowRegex)) {
126
- allowed = true;
127
- break;
128
- }
129
- } catch (err) {
130
- logger.critical('Error while checking block URL regex: ' + allowRegex, err);
131
- return true;
132
- }
133
- }
134
- if (!allowed) {
135
- // wasn't allowed by any regex
120
+ try {
121
+ return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
122
+ } catch (err) {
123
+ logger.critical('Error while checking block URL regexes: ', err);
136
124
  return true;
137
125
  }
138
126
  }
@@ -142,17 +130,12 @@ Autocapture.prototype.currentUrlBlocked = function() {
142
130
  return false;
143
131
  }
144
132
 
145
- for (i = 0; i < blockUrlRegexes.length; i++) {
146
- try {
147
- if (currentUrl.match(blockUrlRegexes[i])) {
148
- return true;
149
- }
150
- } catch (err) {
151
- logger.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
152
- return true;
153
- }
133
+ try {
134
+ return urlMatchesRegexList(currentUrl, blockUrlRegexes);
135
+ } catch (err) {
136
+ logger.critical('Error while checking block URL regexes: ', err);
137
+ return true;
154
138
  }
155
- return false;
156
139
  };
157
140
 
158
141
  Autocapture.prototype.pageviewTrackingConfig = function() {
package/src/config.js CHANGED
@@ -1,6 +1,12 @@
1
- var Config = {
1
+ export var Config = {
2
2
  DEBUG: false,
3
- LIB_VERSION: '2.74.0'
3
+ LIB_VERSION: '2.76.0'
4
4
  };
5
5
 
6
- export default Config;
6
+ // Window global names for async modules
7
+ export var TARGETING_GLOBAL_NAME = '__mp_targeting';
8
+ export var RECORDER_GLOBAL_NAME = '__mp_recorder';
9
+
10
+ // Constants that are injected at build-time for the names of async modules.
11
+ export var RECORDER_FILENAME = '__MP_RECORDER_FILENAME__';
12
+ export var TARGETING_FILENAME = '__MP_TARGETING_FILENAME__';