@mparticle/web-sdk 3.8.1 → 3.10.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/dist/mparticle.js CHANGED
@@ -204,7 +204,7 @@ var mParticle = (function () {
204
204
  Base64: Base64$1
205
205
  };
206
206
 
207
- var version = "3.8.1";
207
+ var version = "3.10.0";
208
208
 
209
209
  var Constants = {
210
210
  sdkVersion: version,
@@ -5882,7 +5882,7 @@ var mParticle = (function () {
5882
5882
  }
5883
5883
  date.setTime(date.getTime() - 24 * 60 * 60 * 1000);
5884
5884
  expires = '; expires=' + date.toUTCString();
5885
- document.cookie = cookieName + '=' + '' + expires + '; path=/' + domain;
5885
+ writeCookie(cookieName + '=' + '' + expires + '; path=/' + domain);
5886
5886
  };
5887
5887
  this.getCookie = function () {
5888
5888
  var cookies,
@@ -5964,7 +5964,7 @@ var mParticle = (function () {
5964
5964
  }
5965
5965
  encodedCookiesWithExpirationAndPath = self.reduceAndEncodePersistence(cookies, expires, domain, mpInstance._Store.SDKConfig.maxCookieSize);
5966
5966
  mpInstance.Logger.verbose(Messages$4.InformationMessages.CookieSet);
5967
- window.document.cookie = encodeURIComponent(key) + '=' + encodedCookiesWithExpirationAndPath;
5967
+ writeCookie(encodeURIComponent(key) + '=' + encodedCookiesWithExpirationAndPath);
5968
5968
  };
5969
5969
  /* This function determines if a cookie is greater than the configured maxCookieSize.
5970
5970
  - If it is, we remove an MPID and its associated UI/UA/CSD from the cookie.
@@ -6056,6 +6056,10 @@ var mParticle = (function () {
6056
6056
  }
6057
6057
  return removeCurrentSessionMpidsByAge(persistence, currentSessionMPIDs, expires, domain, maxCookieSize);
6058
6058
  };
6059
+ function writeCookie(cookie) {
6060
+ var isHttpsPage = window.location.protocol === 'https:';
6061
+ window.document.cookie = isHttpsPage ? cookie + ';Secure' : cookie;
6062
+ }
6059
6063
  function createFullEncodedCookie(persistence, expires, domain) {
6060
6064
  return self.encodePersistence(JSON.stringify(persistence)) + ';expires=' + expires + ';path=/' + domain;
6061
6065
  }
@@ -6319,7 +6323,7 @@ var mParticle = (function () {
6319
6323
  }
6320
6324
  if (mpInstance._Store.SDKConfig.useCookieStorage) {
6321
6325
  var encodedCookiesWithExpirationAndPath = self.reduceAndEncodePersistence(persistence, expires, domain, mpInstance._Store.SDKConfig.maxCookieSize);
6322
- window.document.cookie = encodeURIComponent(key) + '=' + encodedCookiesWithExpirationAndPath;
6326
+ writeCookie(encodeURIComponent(key) + '=' + encodedCookiesWithExpirationAndPath);
6323
6327
  } else {
6324
6328
  if (mpInstance._Store.isLocalStorageAvailable) {
6325
6329
  try {
@@ -6433,6 +6437,144 @@ var mParticle = (function () {
6433
6437
 
6434
6438
  var HISTORY_METHODS = ['pushState', 'replaceState'];
6435
6439
  var WRAPPED_MARKER = '__mpApvWrapped__';
6440
+ var supportsHistoryTracking = function supportsHistoryTracking(win) {
6441
+ return !!win && win.history !== undefined && typeof win.history.pushState === 'function' && typeof win.addEventListener === 'function';
6442
+ };
6443
+ // Wraps pushState/replaceState so `onNavigate` runs after the real method, and
6444
+ // returns the single function that undoes it — or null when nothing was
6445
+ // installed (history is already wrapped, or a frozen/sealed History rejected the
6446
+ // assignment). Handing back one undo closure keeps the wrapper and original
6447
+ // references out of the tracker entirely: there is no half-patched state for a
6448
+ // caller to inspect, and no way to restore the wrong pair.
6449
+ var patchHistory = function patchHistory(onNavigate, log) {
6450
+ if (window.history.pushState[WRAPPED_MARKER]) {
6451
+ log('[patch] history already wrapped, skipping to avoid double-wrap — STACKED WRAPPER DETECTED');
6452
+ return null;
6453
+ }
6454
+ var originals = {};
6455
+ var wrappers = {};
6456
+ HISTORY_METHODS.forEach(function (name) {
6457
+ var original = window.history[name];
6458
+ originals[name] = original;
6459
+ var wrapper = function wrapper() {
6460
+ var args = [];
6461
+ for (var _i = 0; _i < arguments.length; _i++) {
6462
+ args[_i] = arguments[_i];
6463
+ }
6464
+ var result = original.apply(this, args);
6465
+ onNavigate(name);
6466
+ return result;
6467
+ };
6468
+ Object.defineProperty(wrapper, WRAPPED_MARKER, {
6469
+ value: true,
6470
+ enumerable: false
6471
+ });
6472
+ wrappers[name] = wrapper;
6473
+ });
6474
+ // Restore per method: a third party may have patched one of the two on top of
6475
+ // ours after we installed. Clobbering theirs would break their tracking, so
6476
+ // leave anything that is no longer ours in place.
6477
+ var restore = function restore() {
6478
+ return HISTORY_METHODS.forEach(function (name) {
6479
+ if (window.history[name] === wrappers[name]) {
6480
+ window.history[name] = originals[name];
6481
+ log("[teardown] restored original ".concat(name));
6482
+ } else {
6483
+ log("[teardown] ".concat(name, " no longer ours; leaving in place, gating callback to no-op"));
6484
+ }
6485
+ });
6486
+ };
6487
+ try {
6488
+ HISTORY_METHODS.forEach(function (name) {
6489
+ window.history[name] = wrappers[name];
6490
+ });
6491
+ } catch (e) {
6492
+ log("[error] failed to patch history methods (frozen/sealed), rolling back: ".concat(e));
6493
+ try {
6494
+ restore();
6495
+ } catch (restoreError) {
6496
+ log("[error] failed to restore history methods after patch failure: ".concat(restoreError));
6497
+ }
6498
+ return null;
6499
+ }
6500
+ return restore;
6501
+ };
6502
+ // One History patch for the whole SDK, so page-view tracking and preselection do not
6503
+ // stack two wrappers.
6504
+ //
6505
+ // State lives on `window`, not module scope, because Next.js re-executes the bundle per
6506
+ // SPA navigation while the patch from the previous execution stays installed.
6507
+ var WIN_ROUTE_MONITOR_KEY = '__mpRouteMonitor__';
6508
+ var monitorState = function monitorState() {
6509
+ var win = window;
6510
+ if (!win[WIN_ROUTE_MONITOR_KEY]) {
6511
+ win[WIN_ROUTE_MONITOR_KEY] = {
6512
+ listeners: {},
6513
+ undoHistoryPatch: null,
6514
+ popStateListener: null
6515
+ };
6516
+ }
6517
+ return win[WIN_ROUTE_MONITOR_KEY];
6518
+ };
6519
+ // Reads the shared state at call time, so a wrapper installed by a previous bundle
6520
+ // execution still reaches the current listeners.
6521
+ var emit = function emit(source) {
6522
+ Object.values(monitorState().listeners).forEach(function (listener) {
6523
+ try {
6524
+ listener(source);
6525
+ } catch (e) {
6526
+ // One subscriber must not stop the others, and must never break navigation.
6527
+ }
6528
+ });
6529
+ };
6530
+ var install = function install(state, log) {
6531
+ if (state.undoHistoryPatch || state.popStateListener) {
6532
+ return;
6533
+ }
6534
+ state.undoHistoryPatch = patchHistory(emit, log);
6535
+ state.popStateListener = function () {
6536
+ return emit('popstate');
6537
+ };
6538
+ window.addEventListener('popstate', state.popStateListener);
6539
+ };
6540
+ var uninstall = function uninstall(state) {
6541
+ if (state.popStateListener) {
6542
+ window.removeEventListener('popstate', state.popStateListener);
6543
+ state.popStateListener = null;
6544
+ }
6545
+ if (state.undoHistoryPatch) {
6546
+ state.undoHistoryPatch();
6547
+ state.undoHistoryPatch = null;
6548
+ }
6549
+ };
6550
+ // Installs on the first subscriber and tears down after the last leaves, so a workspace
6551
+ // that needs neither is never patched. Unsubscribing only removes the listener while it
6552
+ // still owns its key, so an outgoing instance cannot remove its replacement.
6553
+ var subscribeToRouteChange = function subscribeToRouteChange(key, listener, log) {
6554
+ if (log === void 0) {
6555
+ log = function log() {
6556
+ return undefined;
6557
+ };
6558
+ }
6559
+ if (!supportsHistoryTracking(typeof window === 'undefined' ? null : window)) {
6560
+ return function () {
6561
+ return undefined;
6562
+ };
6563
+ }
6564
+ var state = monitorState();
6565
+ state.listeners[key] = listener;
6566
+ install(state, log);
6567
+ return function () {
6568
+ if (state.listeners[key] !== listener) {
6569
+ return;
6570
+ }
6571
+ delete state.listeners[key];
6572
+ if (Object.keys(state.listeners).length === 0) {
6573
+ uninstall(state);
6574
+ }
6575
+ };
6576
+ };
6577
+
6436
6578
  // All APV state hangs off one window key, and it is the public debugging
6437
6579
  // contract: `window.__mpApv__` is what you inspect in a console to see whether
6438
6580
  // tracking is live.
@@ -6531,9 +6673,6 @@ var mParticle = (function () {
6531
6673
  var isNewPage = function isNewPage(lastKey, candidateKey) {
6532
6674
  return candidateKey !== lastKey;
6533
6675
  };
6534
- var supportsHistoryTracking = function supportsHistoryTracking(win) {
6535
- return !!win && win.history !== undefined && typeof win.history.pushState === 'function' && typeof win.addEventListener === 'function';
6536
- };
6537
6676
  // Mirrors the event shape of the public mParticle.logPageView(), but carries the
6538
6677
  // path and query params captured when the navigation was accepted rather than the
6539
6678
  // live location.
@@ -6650,65 +6789,6 @@ var mParticle = (function () {
6650
6789
  // ---------------------------------------------------------------------------
6651
6790
  // History patching
6652
6791
  // ---------------------------------------------------------------------------
6653
- // Wraps pushState/replaceState so `onNavigate` runs after the real method, and
6654
- // returns the single function that undoes it — or null when nothing was
6655
- // installed (history is already wrapped, or a frozen/sealed History rejected the
6656
- // assignment). Handing back one undo closure keeps the wrapper and original
6657
- // references out of the tracker entirely: there is no half-patched state for a
6658
- // caller to inspect, and no way to restore the wrong pair.
6659
- var patchHistory = function patchHistory(onNavigate, log) {
6660
- if (window.history.pushState[WRAPPED_MARKER]) {
6661
- log('[patch] history already wrapped, skipping to avoid double-wrap — STACKED WRAPPER DETECTED');
6662
- return null;
6663
- }
6664
- var originals = {};
6665
- var wrappers = {};
6666
- HISTORY_METHODS.forEach(function (name) {
6667
- var original = window.history[name];
6668
- originals[name] = original;
6669
- var wrapper = function wrapper() {
6670
- var args = [];
6671
- for (var _i = 0; _i < arguments.length; _i++) {
6672
- args[_i] = arguments[_i];
6673
- }
6674
- var result = original.apply(this, args);
6675
- onNavigate(name);
6676
- return result;
6677
- };
6678
- Object.defineProperty(wrapper, WRAPPED_MARKER, {
6679
- value: true,
6680
- enumerable: false
6681
- });
6682
- wrappers[name] = wrapper;
6683
- });
6684
- // Restore per method: a third party may have patched one of the two on top of
6685
- // ours after we installed. Clobbering theirs would break their tracking, so
6686
- // leave anything that is no longer ours in place.
6687
- var restore = function restore() {
6688
- return HISTORY_METHODS.forEach(function (name) {
6689
- if (window.history[name] === wrappers[name]) {
6690
- window.history[name] = originals[name];
6691
- log("[teardown] restored original ".concat(name));
6692
- } else {
6693
- log("[teardown] ".concat(name, " no longer ours; leaving in place, gating callback to no-op"));
6694
- }
6695
- });
6696
- };
6697
- try {
6698
- HISTORY_METHODS.forEach(function (name) {
6699
- window.history[name] = wrappers[name];
6700
- });
6701
- } catch (e) {
6702
- log("[error] failed to patch history methods (frozen/sealed), rolling back: ".concat(e));
6703
- try {
6704
- restore();
6705
- } catch (restoreError) {
6706
- log("[error] failed to restore history methods after patch failure: ".concat(restoreError));
6707
- }
6708
- return null;
6709
- }
6710
- return restore;
6711
- };
6712
6792
  // ---------------------------------------------------------------------------
6713
6793
  // Tracker
6714
6794
  // ---------------------------------------------------------------------------
@@ -6718,7 +6798,6 @@ var mParticle = (function () {
6718
6798
  this.active = false;
6719
6799
  this.pendingNavigations = [];
6720
6800
  this.undoHistoryPatch = null;
6721
- this.popStateListener = null;
6722
6801
  this.mpInstance = mpInstance;
6723
6802
  this.isAutoPageView = !!(options && options.isAutoPageView);
6724
6803
  }
@@ -6753,15 +6832,11 @@ var mParticle = (function () {
6753
6832
  this.active = true;
6754
6833
  this.lastPage = currentPage();
6755
6834
  this.log("[init] seeded lastPage: ".concat(describePage(this.lastPage)));
6756
- this.undoHistoryPatch = patchHistory(function (source) {
6835
+ this.undoHistoryPatch = subscribeToRouteChange('pageView', function (source) {
6757
6836
  return _this.safeHandleNavigation(source);
6758
6837
  }, function (message) {
6759
6838
  return _this.log(message);
6760
6839
  });
6761
- this.popStateListener = function () {
6762
- return _this.safeHandleNavigation('popstate');
6763
- };
6764
- window.addEventListener('popstate', this.popStateListener);
6765
6840
  setActiveTracker(this);
6766
6841
  this.log('[init] patched pushState/replaceState + listening for popstate');
6767
6842
  inheritedPages.forEach(function (page) {
@@ -6773,10 +6848,6 @@ var mParticle = (function () {
6773
6848
  // re-init). A handoff calls takePendingNavigations() first, so by this
6774
6849
  // point those paths are already transferred rather than dropped.
6775
6850
  this.takePendingNavigations();
6776
- if (this.popStateListener) {
6777
- window.removeEventListener('popstate', this.popStateListener);
6778
- this.popStateListener = null;
6779
- }
6780
6851
  if (this.undoHistoryPatch) {
6781
6852
  this.undoHistoryPatch();
6782
6853
  this.undoHistoryPatch = null;
@@ -10608,7 +10679,10 @@ var mParticle = (function () {
10608
10679
  //
10609
10680
  // https://github.com/mparticle-integrations/mparticle-javascript-integration-rokt
10610
10681
  var RoktManager = /** @class */function () {
10611
- function RoktManager() {
10682
+ // Keys this manager's route subscription, so a re-executed bundle's manager replaces
10683
+ // it while a second named instance keeps its own.
10684
+ function RoktManager(instanceName) {
10685
+ this.instanceName = instanceName;
10612
10686
  this.kit = null;
10613
10687
  this.filters = {};
10614
10688
  this.currentUser = null;
@@ -10618,6 +10692,7 @@ var mParticle = (function () {
10618
10692
  this.onReadyCallback = null;
10619
10693
  this.initialized = false;
10620
10694
  this.isShoppableAdsLoaded = false;
10695
+ this.stopRouteChangeWatch = null;
10621
10696
  }
10622
10697
  /**
10623
10698
  * Sets a callback to be invoked when RoktManager becomes ready
@@ -10695,6 +10770,7 @@ var mParticle = (function () {
10695
10770
  RoktManager.prototype.attachKit = function (kit) {
10696
10771
  var _a, _b, _c, _d;
10697
10772
  this.kit = kit;
10773
+ this.watchRouteChanges();
10698
10774
  if ((_a = kit.settings) === null || _a === void 0 ? void 0 : _a.accountId) {
10699
10775
  this.store.setRoktAccountId(kit.settings.accountId);
10700
10776
  }
@@ -10708,6 +10784,42 @@ var mParticle = (function () {
10708
10784
  (_d = this.logger) === null || _d === void 0 ? void 0 : _d.error('RoktManager: Error in onReadyCallback: ' + e);
10709
10785
  }
10710
10786
  };
10787
+ // Core already emits a page view per navigation when AutoLogPageView is on, so the kit
10788
+ // subscribes to route changes only when it is off. The call on attach happens either
10789
+ // way: the auto page view fires once per page load, so a re-init emits none.
10790
+ RoktManager.prototype.watchRouteChanges = function () {
10791
+ var _this = this;
10792
+ var _a, _b, _c;
10793
+ if (!isFunction((_a = this.kit) === null || _a === void 0 ? void 0 : _a.onRouteChange)) {
10794
+ (_b = this.stopRouteChangeWatch) === null || _b === void 0 ? void 0 : _b.call(this);
10795
+ this.stopRouteChangeWatch = null;
10796
+ return;
10797
+ }
10798
+ if (this.isAutoLogPageViewEnabled()) {
10799
+ (_c = this.stopRouteChangeWatch) === null || _c === void 0 ? void 0 : _c.call(this);
10800
+ this.stopRouteChangeWatch = null;
10801
+ } else if (!this.stopRouteChangeWatch) {
10802
+ this.stopRouteChangeWatch = subscribeToRouteChange("rokt:".concat(this.instanceName), function () {
10803
+ return _this.notifyRouteChange();
10804
+ });
10805
+ }
10806
+ // A full navigation or a re-init lands on the trigger route without a route change
10807
+ // of its own.
10808
+ this.notifyRouteChange();
10809
+ };
10810
+ // Reads `this.kit` at call time, so a kit attached by a later init() takes over.
10811
+ RoktManager.prototype.notifyRouteChange = function () {
10812
+ var _a, _b, _c;
10813
+ try {
10814
+ (_b = (_a = this.kit) === null || _a === void 0 ? void 0 : _a.onRouteChange) === null || _b === void 0 ? void 0 : _b.call(_a);
10815
+ } catch (e) {
10816
+ (_c = this.logger) === null || _c === void 0 ? void 0 : _c.error("RoktManager: Error in onRouteChange: ".concat(getErrorMessage(e)));
10817
+ }
10818
+ };
10819
+ RoktManager.prototype.isAutoLogPageViewEnabled = function () {
10820
+ var _a, _b, _c;
10821
+ return ((_c = (_b = (_a = this.store) === null || _a === void 0 ? void 0 : _a.SDKConfig) === null || _b === void 0 ? void 0 : _b.flags) === null || _c === void 0 ? void 0 : _c[Constants.FeatureFlags.AutoLogPageView]) === true;
10822
+ };
10711
10823
  /**
10712
10824
  * Renders ads based on the options provided
10713
10825
  *
@@ -11335,7 +11447,7 @@ var mParticle = (function () {
11335
11447
  };
11336
11448
  this._ErrorReportingDispatcher = new ErrorReportingDispatcher();
11337
11449
  this._LoggingDispatcher = new LoggingDispatcher();
11338
- this._RoktManager = new RoktManager();
11450
+ this._RoktManager = new RoktManager(instanceName);
11339
11451
  this._RoktManager.setOnReadyCallback(function () {
11340
11452
  self.processQueueOnIdentityFailure();
11341
11453
  });
@@ -1,8 +1,6 @@
1
1
  import { IMParticleWebSDKInstance } from './mp-instance';
2
2
  import { BaseEvent } from './sdkRuntimeModels';
3
3
  import { Dictionary } from './utils';
4
- type HistoryMethodName = 'pushState' | 'replaceState';
5
- type NavigationSource = HistoryMethodName | 'popstate';
6
4
  export declare const WIN_APV_KEY = "__mpApv__";
7
5
  export declare const ALLOWED_QUERY_PARAMS: string[];
8
6
  interface IPageSnapshot {
@@ -22,20 +20,17 @@ export declare const AUTO_PAGE_VIEW_ATTRIBUTE = "is_auto_page_view";
22
20
  export declare const autoPageViewAttribute: (isAutoPageView?: boolean) => Dictionary<boolean>;
23
21
  export declare const pageKey: (page: IPageSnapshot) => string;
24
22
  export declare const isNewPage: (lastKey: string | null, candidateKey: string) => boolean;
25
- export declare const supportsHistoryTracking: (win: Window | null) => boolean;
26
23
  export declare const buildPageViewEvent: ({ params, hostname, title, path, isAutoPageView, }: IPageViewData) => BaseEvent;
27
24
  export declare const getActiveTracker: () => PageViewTracker | undefined;
28
25
  export declare const hasInitialPageViewFired: () => boolean;
29
26
  export declare const markInitialPageViewFired: () => void;
30
27
  export declare const resetPageViewTracking: () => void;
31
- export declare const patchHistory: (onNavigate: (source: NavigationSource) => void, log: (message: string) => void) => (() => void) | null;
32
28
  export declare class PageViewTracker {
33
29
  private readonly mpInstance;
34
30
  private lastPage;
35
31
  private active;
36
32
  private pendingNavigations;
37
33
  private undoHistoryPatch;
38
- private popStateListener;
39
34
  private readonly isAutoPageView;
40
35
  constructor(mpInstance: IMParticleWebSDKInstance, options?: IPageViewOptions);
41
36
  get isActive(): boolean;
@@ -58,6 +58,7 @@ export interface IRoktKit {
58
58
  use: <T>(name: string) => Promise<T>;
59
59
  onShoppableAdsReady(callback: () => void): void;
60
60
  terminate?: () => Promise<void>;
61
+ onRouteChange?: () => void;
61
62
  launcherOptions?: Dictionary<any>;
62
63
  settings?: IRoktKitSettings;
63
64
  integrationName?: string;
@@ -69,6 +70,7 @@ export interface IRoktOptions {
69
70
  }
70
71
  export type { IRoktLauncherOptions } from "./roktLauncherOptions";
71
72
  export default class RoktManager {
73
+ private readonly instanceName?;
72
74
  kit: IRoktKit;
73
75
  filters: RoktKitFilterSettings;
74
76
  private currentUser;
@@ -88,6 +90,8 @@ export default class RoktManager {
88
90
  private onReadyCallback;
89
91
  private initialized;
90
92
  private isShoppableAdsLoaded;
93
+ private stopRouteChangeWatch;
94
+ constructor(instanceName?: string);
91
95
  /**
92
96
  * Sets a callback to be invoked when RoktManager becomes ready
93
97
  */
@@ -109,6 +113,9 @@ export default class RoktManager {
109
113
  init(roktConfig: IKitConfigs, filteredUser: IMParticleUser, identityService: SDKIdentityApi, store: IStore, logger?: SDKLoggerApi, options?: IRoktOptions, captureTiming?: (metricsName: string) => void, errorReporter?: IErrorReportingService, loggingService?: ILoggingService, integrationCapture?: IntegrationCapture): void;
110
114
  get isInitialized(): boolean;
111
115
  attachKit(kit: IRoktKit): void;
116
+ private watchRouteChanges;
117
+ private notifyRouteChange;
118
+ private isAutoLogPageViewEnabled;
112
119
  /**
113
120
  * Renders ads based on the options provided
114
121
  *
@@ -0,0 +1,9 @@
1
+ type HistoryMethodName = 'pushState' | 'replaceState';
2
+ export type RouteChangeSource = HistoryMethodName | 'popstate';
3
+ export type RouteChangeListener = (source: RouteChangeSource) => void;
4
+ export declare const supportsHistoryTracking: (win: Window | null) => boolean;
5
+ export declare const patchHistory: (onNavigate: RouteChangeListener, log: (message: string) => void) => (() => void) | null;
6
+ export declare const WIN_ROUTE_MONITOR_KEY = "__mpRouteMonitor__";
7
+ export declare const subscribeToRouteChange: (key: string, listener: RouteChangeListener, log?: (message: string) => void) => (() => void);
8
+ export declare const resetRouteChangeMonitor: () => void;
9
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mparticle/web-sdk",
3
- "version": "3.8.1",
3
+ "version": "3.10.0",
4
4
  "description": "mParticle core SDK for web applications",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -159,7 +159,7 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan
159
159
  this._ErrorReportingDispatcher = new ErrorReportingDispatcher();
160
160
  this._LoggingDispatcher = new LoggingDispatcher();
161
161
 
162
- this._RoktManager = new RoktManager();
162
+ this._RoktManager = new RoktManager(instanceName);
163
163
 
164
164
  this._RoktManager.setOnReadyCallback(() => {
165
165
  self.processQueueOnIdentityFailure();
@@ -2,18 +2,13 @@ import { IMParticleWebSDKInstance } from './mp-instance';
2
2
  import { BaseEvent } from './sdkRuntimeModels';
3
3
  import { EventType, MessageType } from './types';
4
4
  import { Dictionary, getHref, hasOwnProp, queryStringParser } from './utils';
5
+ import {
6
+ subscribeToRouteChange,
7
+ supportsHistoryTracking,
8
+ type RouteChangeSource,
9
+ } from './routeChangeMonitor';
5
10
 
6
- type HistoryStateMethod = History['pushState'];
7
- type HistoryMethodName = 'pushState' | 'replaceState';
8
- type NavigationSource = HistoryMethodName | 'popstate';
9
-
10
- const HISTORY_METHODS: HistoryMethodName[] = ['pushState', 'replaceState'];
11
-
12
- const WRAPPED_MARKER = '__mpApvWrapped__';
13
-
14
- type MarkedHistoryMethod = HistoryStateMethod & {
15
- [WRAPPED_MARKER]?: boolean;
16
- };
11
+ type NavigationSource = RouteChangeSource;
17
12
 
18
13
  // All APV state hangs off one window key, and it is the public debugging
19
14
  // contract: `window.__mpApv__` is what you inspect in a console to see whether
@@ -203,12 +198,6 @@ export const isNewPage = (
203
198
  candidateKey: string
204
199
  ): boolean => candidateKey !== lastKey;
205
200
 
206
- export const supportsHistoryTracking = (win: Window | null): boolean =>
207
- !!win &&
208
- win.history !== undefined &&
209
- typeof win.history.pushState === 'function' &&
210
- typeof win.addEventListener === 'function';
211
-
212
201
  // Mirrors the event shape of the public mParticle.logPageView(), but carries the
213
202
  // path and query params captured when the navigation was accepted rather than the
214
203
  // live location.
@@ -339,82 +328,6 @@ const describePage = (page: IPageSnapshot | null): string => {
339
328
  // History patching
340
329
  // ---------------------------------------------------------------------------
341
330
 
342
- // Wraps pushState/replaceState so `onNavigate` runs after the real method, and
343
- // returns the single function that undoes it — or null when nothing was
344
- // installed (history is already wrapped, or a frozen/sealed History rejected the
345
- // assignment). Handing back one undo closure keeps the wrapper and original
346
- // references out of the tracker entirely: there is no half-patched state for a
347
- // caller to inspect, and no way to restore the wrong pair.
348
- export const patchHistory = (
349
- onNavigate: (source: NavigationSource) => void,
350
- log: (message: string) => void
351
- ): (() => void) | null => {
352
- if ((window.history.pushState as MarkedHistoryMethod)[WRAPPED_MARKER]) {
353
- log(
354
- '[patch] history already wrapped, skipping to avoid double-wrap — STACKED WRAPPER DETECTED'
355
- );
356
- return null;
357
- }
358
-
359
- const originals = {} as Record<HistoryMethodName, HistoryStateMethod>;
360
- const wrappers = {} as Record<HistoryMethodName, HistoryStateMethod>;
361
-
362
- HISTORY_METHODS.forEach(name => {
363
- const original = window.history[name];
364
- originals[name] = original;
365
-
366
- const wrapper = function(
367
- this: History,
368
- ...args: Parameters<HistoryStateMethod>
369
- ): void {
370
- const result = original.apply(this, args);
371
- onNavigate(name);
372
- return result;
373
- };
374
-
375
- Object.defineProperty(wrapper, WRAPPED_MARKER, {
376
- value: true,
377
- enumerable: false,
378
- });
379
-
380
- wrappers[name] = wrapper;
381
- });
382
-
383
- // Restore per method: a third party may have patched one of the two on top of
384
- // ours after we installed. Clobbering theirs would break their tracking, so
385
- // leave anything that is no longer ours in place.
386
- const restore = (): void =>
387
- HISTORY_METHODS.forEach(name => {
388
- if (window.history[name] === wrappers[name]) {
389
- window.history[name] = originals[name];
390
- log(`[teardown] restored original ${name}`);
391
- } else {
392
- log(
393
- `[teardown] ${name} no longer ours; leaving in place, gating callback to no-op`
394
- );
395
- }
396
- });
397
-
398
- try {
399
- HISTORY_METHODS.forEach(name => {
400
- window.history[name] = wrappers[name];
401
- });
402
- } catch (e) {
403
- log(
404
- `[error] failed to patch history methods (frozen/sealed), rolling back: ${e}`
405
- );
406
- try {
407
- restore();
408
- } catch (restoreError) {
409
- log(
410
- `[error] failed to restore history methods after patch failure: ${restoreError}`
411
- );
412
- }
413
- return null;
414
- }
415
-
416
- return restore;
417
- };
418
331
 
419
332
  // ---------------------------------------------------------------------------
420
333
  // Tracker
@@ -428,7 +341,6 @@ export class PageViewTracker {
428
341
  private pendingNavigations: IPendingNavigation[] = [];
429
342
 
430
343
  private undoHistoryPatch: (() => void) | null = null;
431
- private popStateListener: (() => void) | null = null;
432
344
 
433
345
  private readonly isAutoPageView: boolean;
434
346
 
@@ -473,12 +385,11 @@ export class PageViewTracker {
473
385
  this.lastPage = currentPage();
474
386
  this.log(`[init] seeded lastPage: ${describePage(this.lastPage)}`);
475
387
 
476
- this.undoHistoryPatch = patchHistory(
388
+ this.undoHistoryPatch = subscribeToRouteChange(
389
+ 'pageView',
477
390
  source => this.safeHandleNavigation(source),
478
391
  message => this.log(message)
479
392
  );
480
- this.popStateListener = () => this.safeHandleNavigation('popstate');
481
- window.addEventListener('popstate', this.popStateListener);
482
393
 
483
394
  setActiveTracker(this);
484
395
 
@@ -495,11 +406,6 @@ export class PageViewTracker {
495
406
  // point those paths are already transferred rather than dropped.
496
407
  this.takePendingNavigations();
497
408
 
498
- if (this.popStateListener) {
499
- window.removeEventListener('popstate', this.popStateListener);
500
- this.popStateListener = null;
501
- }
502
-
503
409
  if (this.undoHistoryPatch) {
504
410
  this.undoHistoryPatch();
505
411
  this.undoHistoryPatch = null;
@@ -407,7 +407,7 @@ export default function _Persistence(
407
407
 
408
408
  date.setTime(date.getTime() - 24 * 60 * 60 * 1000);
409
409
  expires = '; expires=' + date.toUTCString();
410
- document.cookie = cookieName + '=' + '' + expires + '; path=/' + domain;
410
+ writeCookie(cookieName + '=' + '' + expires + '; path=/' + domain);
411
411
  };
412
412
 
413
413
  this.getCookie = function(): IPersistenceMinified | null {
@@ -532,8 +532,9 @@ export default function _Persistence(
532
532
 
533
533
  mpInstance.Logger.verbose(Messages.InformationMessages.CookieSet);
534
534
 
535
- window.document.cookie =
536
- encodeURIComponent(key) + '=' + encodedCookiesWithExpirationAndPath;
535
+ writeCookie(
536
+ encodeURIComponent(key) + '=' + encodedCookiesWithExpirationAndPath
537
+ );
537
538
  };
538
539
 
539
540
  /* This function determines if a cookie is greater than the configured maxCookieSize.
@@ -730,6 +731,11 @@ export default function _Persistence(
730
731
  );
731
732
  };
732
733
 
734
+ function writeCookie(cookie: string): void {
735
+ const isHttpsPage = window.location.protocol === 'https:';
736
+ window.document.cookie = isHttpsPage ? cookie + ';Secure' : cookie;
737
+ }
738
+
733
739
  function createFullEncodedCookie(persistence: IPersistenceMinified, expires: string, domain: string): string {
734
740
  return (
735
741
  self.encodePersistence(JSON.stringify(persistence)) +
@@ -1086,10 +1092,11 @@ export default function _Persistence(
1086
1092
  domain,
1087
1093
  mpInstance._Store.SDKConfig.maxCookieSize
1088
1094
  );
1089
- window.document.cookie =
1095
+ writeCookie(
1090
1096
  encodeURIComponent(key) +
1091
- '=' +
1092
- encodedCookiesWithExpirationAndPath;
1097
+ '=' +
1098
+ encodedCookiesWithExpirationAndPath
1099
+ );
1093
1100
  } else {
1094
1101
  if (mpInstance._Store.isLocalStorageAvailable) {
1095
1102
  try {