@mparticle/web-sdk 2.76.0 → 2.78.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.
@@ -15,7 +15,7 @@ map:function map(a,b){var c,d,e;if(null===this)throw new TypeError(" this is nul
15
15
  filter:function filter(a/*, thisArg*/){if(void 0===this||null===this)throw new TypeError;var b=Object(this),c=b.length>>>0;if("function"!=typeof a)throw new TypeError;for(var d=[],e=2<=arguments.length?arguments[1]:void 0,f=0;f<c;f++)if(f in b){var g=b[f];a.call(e,g,f,b)&&d.push(g);}return d},// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
16
16
  isArray:function isArray(a){return "[object Array]"===Object.prototype.toString.call(a)},Base64:Base64$1};
17
17
 
18
- var version = "2.76.0";
18
+ var version = "2.78.0";
19
19
 
20
20
  var Constants={sdkVersion:version,sdkVendor:"mparticle",platform:"web",Messages:{DeprecationMessages:{MethodHasBeenDeprecated:"has been deprecated.",MethodMarkedForDeprecationPostfix:"is a deprecated method and will be removed in future releases.",AlternativeMethodPrefix:"Please use the alternate method:"},ErrorMessages:{NoToken:"A token must be specified.",EventNameInvalidType:"Event name must be a valid string value.",EventDataInvalidType:"Event data must be a valid object hash.",LoggingDisabled:"Event logging is currently disabled.",CookieParseError:"Could not parse cookie",EventEmpty:"Event object is null or undefined, cancelling send",APIRequestEmpty:"APIRequest is null or undefined, cancelling send",NoEventType:"Event type must be specified.",TransactionIdRequired:"Transaction ID is required",TransactionRequired:"A transaction attributes object is required",PromotionIdRequired:"Promotion ID is required",BadAttribute:"Attribute value cannot be object or array",BadKey:"Key value cannot be object or array",BadLogPurchase:"Transaction attributes and a product are both required to log a purchase, https://docs.mparticle.com/?javascript#measuring-transactions",AudienceAPINotEnabled:"Your workspace is not enabled to retrieve user audiences."},InformationMessages:{CookieSearch:"Searching for cookie",CookieFound:"Cookie found, parsing values",CookieNotFound:"Cookies not found",CookieSet:"Setting cookie",CookieSync:"Performing cookie sync",SendBegin:"Starting to send event",SendIdentityBegin:"Starting to send event to identity server",SendWindowsPhone:"Sending event to Windows Phone container",SendIOS:"Calling iOS path: ",SendAndroid:"Calling Android JS interface method: ",SendHttp:"Sending event to mParticle HTTP service",SendAliasHttp:"Sending alias request to mParticle HTTP service",SendIdentityHttp:"Sending event to mParticle HTTP service",StartingNewSession:"Starting new Session",StartingLogEvent:"Starting to log event",StartingLogOptOut:"Starting to log user opt in/out",StartingEndSession:"Starting to end session",StartingInitialization:"Starting to initialize",StartingLogCommerceEvent:"Starting to log commerce event",StartingAliasRequest:"Starting to Alias MPIDs",LoadingConfig:"Loading configuration options",AbandonLogEvent:"Cannot log event, logging disabled or developer token not set",AbandonAliasUsers:"Cannot Alias Users, logging disabled or developer token not set",AbandonStartSession:"Cannot start session, logging disabled or developer token not set",AbandonEndSession:"Cannot end session, logging disabled or developer token not set",NoSessionToEnd:"Cannot end session, no active session found"},ValidationMessages:{ModifyIdentityRequestUserIdentitiesPresent:"identityRequests to modify require userIdentities to be present. Request not sent to server. Please fix and try again",IdentityRequesetInvalidKey:"There is an invalid key on your identityRequest object. It can only contain a `userIdentities` object and a `onUserAlias` function. Request not sent to server. Please fix and try again.",OnUserAliasType:"The onUserAlias value must be a function.",UserIdentities:"The userIdentities key must be an object with keys of identityTypes and values of strings. Request not sent to server. Please fix and try again.",UserIdentitiesInvalidKey:"There is an invalid identity key on your `userIdentities` object within the identityRequest. Request not sent to server. Please fix and try again.",UserIdentitiesInvalidValues:"All user identity values must be strings or null. Request not sent to server. Please fix and try again.",AliasMissingMpid:"Alias Request must contain both a destinationMpid and a sourceMpid",AliasNonUniqueMpid:"Alias Request's destinationMpid and sourceMpid must be unique",AliasMissingTime:"Alias Request must have both a startTime and an endTime",AliasStartBeforeEndTime:"Alias Request's endTime must be later than its startTime"}},NativeSdkPaths:{LogEvent:"logEvent",SetUserTag:"setUserTag",RemoveUserTag:"removeUserTag",SetUserAttribute:"setUserAttribute",RemoveUserAttribute:"removeUserAttribute",SetSessionAttribute:"setSessionAttribute",AddToCart:"addToCart",RemoveFromCart:"removeFromCart",ClearCart:"clearCart",LogOut:"logOut",SetUserAttributeList:"setUserAttributeList",RemoveAllUserAttributes:"removeAllUserAttributes",GetUserAttributesLists:"getUserAttributesLists",GetAllUserAttributes:"getAllUserAttributes",Identify:"identify",Logout:"logout",Login:"login",Modify:"modify",Alias:"aliasUsers",Upload:"upload"},StorageNames:{localStorageName:"mprtcl-api",localStorageNameV3:"mprtcl-v3",cookieName:"mprtcl-api",cookieNameV2:"mprtcl-v2",cookieNameV3:"mprtcl-v3",localStorageNameV4:"mprtcl-v4",localStorageProductsV4:"mprtcl-prodv4",cookieNameV4:"mprtcl-v4",currentStorageName:"mprtcl-v4",currentStorageProductsName:"mprtcl-prodv4"},DefaultConfig:{cookieDomain:null,cookieExpiration:365,logLevel:null,timeout:300,sessionTimeout:30,maxProducts:20,forwarderStatsTimeout:5e3,integrationDelayTimeout:5e3,maxCookieSize:3e3,aliasMaxWindow:90,uploadInterval:0// Maximum milliseconds in between batch uploads, below 500 will mean immediate upload. The server returns this as a string, but we are using it as a number internally
21
21
  },DefaultBaseUrls:{v1SecureServiceUrl:"jssdks.mparticle.com/v1/JS/",v2SecureServiceUrl:"jssdks.mparticle.com/v2/JS/",v3SecureServiceUrl:"jssdks.mparticle.com/v3/JS/",configUrl:"jssdkcdns.mparticle.com/JS/v2/",identityUrl:"identity.mparticle.com/v1/",aliasUrl:"jssdks.mparticle.com/v1/identity/",userAudienceUrl:"nativesdks.mparticle.com/v1/"},// These are the paths that are used to construct the CNAME urls
@@ -147,7 +147,7 @@ var Messages$a=Constants.Messages,createCookieString=function(a){return replaceC
147
147
  // -1e11 -> //-100000000000,
148
148
  "".concat(1e7,"-").concat(1e3,"-").concat(4e3,"-").concat(8e3,"-").concat(1e11).replace(/[018]/g,// zeroes, ones, and eights with
149
149
  generateUniqueId// random hex digits
150
- )},getRampNumber=function(a){if(!a)return 100;var b=generateHash(a);return Math.abs(b%100)+1},isObject=function(a){var b=Object.prototype.toString.call(a);return "[object Object]"===b||"[object Error]"===b},parseNumber=function(a){if(isNaN(a)||!isFinite(a))return 0;var b=parseFloat(a);return isNaN(b)?0:b},parseSettingsString=function(a){try{return a?JSON.parse(a.replace(/&quot;/g,"\"")):[]}catch(a){throw new Error("Settings string contains invalid JSON")}},parseStringOrNumber=function(a){return isStringOrNumber(a)?a:null},replaceCommasWithPipes=function(a){return a.replace(/,/g,"|")},replacePipesWithCommas=function(a){return a.replace(/\|/g,",")},replaceApostrophesWithQuotes=function(a){return a.replace(/\'/g,"\"")},replaceQuotesWithApostrophes=function(a){return a.replace(/\"/g,"'")},replaceMPID=function(a,b){return a.replace("%%mpid%%",b)},replaceAmpWithAmpersand=function(a){return a.replace(/&amp;/g,"&")},createCookieSyncUrl=function(a,b,c,d){var e=replaceAmpWithAmpersand(b),f=c?replaceAmpWithAmpersand(c):null,g=replaceMPID(e,a),h=f?replaceMPID(f,a):"",i=g+encodeURIComponent(h);if(d){var j=i.includes("?")?"&":"?";i+="".concat(j,"domain=").concat(d);}return i},returnConvertedBoolean=function(a){return "false"!==a&&"0"!==a&&!!a},decoded=function(a){return decodeURIComponent(a.replace(/\+/g," "))},converted=function(a){return 0===a.indexOf("\"")&&(a=a.slice(1,-1).replace(/\\"/g,"\"").replace(/\\\\/g,"\\")),a},isString=function(a){return "string"==typeof a},isNumber=function(a){return "number"==typeof a},isBoolean=function(a){return "boolean"==typeof a},isFunction=function(a){return "function"==typeof a},isValidAttributeValue=function(a){return a!==void 0&&!isObject(a)&&!Array.isArray(a)},isValidCustomFlagProperty=function(a){return isNumber(a)||isString(a)||isBoolean(a)},toDataPlanSlug=function(a){// Make sure we are only acting on strings or numbers
150
+ )},getRampNumber=function(a){if(!a)return 100;var b=generateHash(a);return Math.abs(b%100)+1},isObject=function(a){var b=Object.prototype.toString.call(a);return "[object Object]"===b||"[object Error]"===b},parseNumber=function(a){if(isNaN(a)||!isFinite(a))return 0;var b=parseFloat(a);return isNaN(b)?0:b},parseSettingsString=function(a){try{return a?JSON.parse(a.replace(/&quot;/g,"\"")):[]}catch(a){throw new Error("Settings string contains invalid JSON")}},parseStringOrNumber=function(a){return isStringOrNumber(a)?a:null},replaceCommasWithPipes=function(a){return a.replace(/,/g,"|")},replacePipesWithCommas=function(a){return a.replace(/\|/g,",")},replaceApostrophesWithQuotes=function(a){return a.replace(/\'/g,"\"")},replaceQuotesWithApostrophes=function(a){return a.replace(/\"/g,"'")},replaceMPID=function(a,b){return a.replace("%%mpid%%",b)},replaceAmpWithAmpersand=function(a){return a.replace(/&amp;/g,"&")},createCookieSyncUrl=function(a,b,c,d,e){var f=replaceAmpWithAmpersand(b),g=c?replaceAmpWithAmpersand(c):null,h=replaceMPID(f,a),i=g?replaceMPID(g,a):"",j=h+encodeURIComponent(i);if(d){var k=j.includes("?")?"&":"?";j+="".concat(k,"domain=").concat(d);}if(e){var k=j.includes("?")?"&":"?";j+="".concat(k,"google_hm=").concat(e);}return j},WEB_SAFE_BASE64_REPLACEMENTS={"+":"-","/":"_","=":""},toWebSafeBase64=function(a){return btoa(a).replace(/[+/=]/g,function(a){return WEB_SAFE_BASE64_REPLACEMENTS[a]})},returnConvertedBoolean=function(a){return "false"!==a&&"0"!==a&&!!a},decoded=function(a){return decodeURIComponent(a.replace(/\+/g," "))},converted=function(a){return 0===a.indexOf("\"")&&(a=a.slice(1,-1).replace(/\\"/g,"\"").replace(/\\\\/g,"\\")),a},isString=function(a){return "string"==typeof a},isNumber=function(a){return "number"==typeof a},isBoolean=function(a){return "boolean"==typeof a},isFunction=function(a){return "function"==typeof a},isValidAttributeValue=function(a){return a!==void 0&&!isObject(a)&&!Array.isArray(a)},isValidCustomFlagProperty=function(a){return isNumber(a)||isString(a)||isBoolean(a)},toDataPlanSlug=function(a){// Make sure we are only acting on strings or numbers
151
151
  return isStringOrNumber(a)?a.toString().toLowerCase().replace(/[^0-9a-zA-Z]+/g,"_"):""},isDataPlanSlug=function(a){return a===toDataPlanSlug(a)},isStringOrNumber=function(a){return isString(a)||isNumber(a)},isEmpty=function(a){return null==a||!(Object.keys(a)||a).length},moveElementToEnd=function(a,b){return a.slice(0,b).concat(a.slice(b+1),a[b])},queryStringParser=function(a,b){void 0===b&&(b=[]);var c,d={},e={};if(!a)return d;if("undefined"!=typeof URL&&"undefined"!=typeof URLSearchParams){var f=new URL(a);c=new URLSearchParams(f.search);}else c=queryStringParserFallback(a);return (c.forEach(function(a,b){e[b.toLowerCase()]=a;}),isEmpty(b))?e:(b.forEach(function(a){var b=e[a.toLowerCase()];b&&(d[a]=b);}),d)},queryStringParserFallback=function(a){var b={},c=a.split("?")[1]||"",d=c.split("&");return d.forEach(function(a){var c=a.split("="),d=c[0],e=c.slice(1),f=e.join("=");if(d&&void 0!==f)try{b[d]=decodeURIComponent(f||"");}catch(a){console.error("Failed to decode value for key ".concat(d,": ").concat(a));}}),{get:function get(a){return b[a]},forEach:function forEach(a){for(var c in b)b.hasOwnProperty(c)&&a(b[c],c);}}},getCookies=function(a){// Helper function to parse cookies from document.cookie
152
152
  var b=function parseCookies(){try{return "undefined"==typeof window?[]:window.document.cookie.split(";").map(function(a){return a.trim()})}catch(a){return console.error("Unable to parse cookies",a),[]}}();// Helper function to filter cookies by keys
153
153
  // Parse cookies from document.cookie
@@ -158,7 +158,11 @@ if(Array.isArray(a))return a.map(function(a){return obfuscateData(a)});// Handle
158
158
  if(isObject(a)){var b={};for(var c in a)a.hasOwnProperty(c)&&(b[c]=obfuscateData(a[c]));return b}// For primitives and other types, return the type as a string
159
159
  return _typeof$1(a)},obfuscateDevData=function(a,b){return b?a:obfuscateData(a)};/**
160
160
  * Returns a value between 1-100 inclusive.
161
- */ // FIXME: REFACTOR for V3
161
+ */ // Google's cookie matching service requires `google_hm` values to be web-safe
162
+ // base64 (RFC 4648 §5) without padding, otherwise the write to the hosted
163
+ // match table fails: https://developers.google.com/authorized-buyers/rtb/cookie-guide
164
+ // (String#replaceAll is not available in our ES5 browser targets.)
165
+ // FIXME: REFACTOR for V3
162
166
  // only used in store.js to sanitize server-side formatting of
163
167
  // booleans when checking for `isDevelopmentMode`
164
168
  // Should be removed in v3
@@ -632,19 +636,23 @@ var PARTNER_MODULE_IDS={AdobeEventForwarder:11,DoubleclickDFP:41,AppNexus:50,Lot
632
636
  // Private
633
637
  this.attemptCookieSync=function(c,d){var e,f=a._Store,g=f.pixelConfigurations,h=f.webviewBridgeEnabled;if(c&&!h&&(null===(e=a._CookieConsentManager)||void 0===e||!e.getNoFunctional()))// When noFunctional is true, persistence is not saved, so we cannot track cookie sync
634
638
  // dates. Skip cookie sync to avoid running it on every page load.
635
- {var i=a._Persistence.getPersistence();isEmpty(i)||g.forEach(function(e){var f,g,h=!1,j=e.filteringConsentRuleValues,k=e.pixelUrl,l=e.redirectUrl,m=e.moduleId,// Tells you how often we should do a cookie sync (in days)
636
- n=e.frequencyCap,o=(j||{}).values;// set requiresConsent to false to start each additional pixel configuration
639
+ {var i=a._Persistence.getPersistence();isEmpty(i)||g.forEach(function(e){var f,g,h,j=!1,k=e.filteringConsentRuleValues,l=e.pixelUrl,m=e.redirectUrl,n=e.moduleId,o=e.settings,// Tells you how often we should do a cookie sync (in days)
640
+ p=e.frequencyCap,q=(k||{}).values;// set requiresConsent to false to start each additional pixel configuration
637
641
  // set to true only if filteringConsenRuleValues.values.length exists
638
642
  // Filtering rules as defined in UI
639
- if(!isEmpty(k)&&(isEmpty(o)||(h=!0),!(h&&d))&&!(m===PARTNER_MODULE_IDS.Rokt&&a._CookieConsentManager.getNoTargeting()))// For Rokt, block cookie sync when noTargeting privacy flag is true
643
+ if(!isEmpty(l)&&(isEmpty(q)||(j=!0),!(j&&d))&&!(n===PARTNER_MODULE_IDS.Rokt&&a._CookieConsentManager.getNoTargeting()))// For Rokt, block cookie sync when noTargeting privacy flag is true
640
644
  // If MPID is new to cookies, we should not try to perform the cookie sync
641
645
  // because a cookie sync can only occur once a user either consents or doesn't.
642
646
  // we should not check if it's enabled if the user has a blank consent
643
647
  // The Trade Desk requires a URL parameter for GDPR enabled users.
644
648
  // It is optional but to simplify the code, we add it for all Trade
645
649
  // // Desk cookie syncs.
646
- // Add domain parameter for Trade Desk
647
- {var p=a._Consent.isEnabledForUserConsent;if(p(j,a.Identity.getCurrentUser())){var q=null!==(g=null===(f=i[c])||void 0===f?void 0:f.csd)&&void 0!==g?g:{},r=q[m]||null;if(isLastSyncDateExpired(n,r)){var s=m===PARTNER_MODULE_IDS.TradeDesk?window.location.hostname:void 0,t=createCookieSyncUrl(c,k,l,s);b.performCookieSync(t,m.toString(),c,q);}}}});}},this.performCookieSync=function(b,c,d,e){var f=document.createElement("img");a.Logger.verbose(InformationMessages.CookieSync),f.onload=function(){e[c]=new Date().getTime(),a._Persistence.saveUserCookieSyncDatesToPersistence(d,e);},f.src=b;};}var isLastSyncDateExpired=function(a,b){// If there is no lastSyncDate, then there is no previous cookie sync, so we should sync the cookie
650
+ // Google Marketing Platform accepts a web-safe base64-encoded MPID via the
651
+ // `google_hm` query parameter, but only for accounts provisioned for Google
652
+ // Hosted Matching — Google returns errors otherwise. It is therefore gated
653
+ // behind the opt-in `enableHmTag` setting (off by default), which the server
654
+ // delivers as the string 'True' when the UI toggle is enabled.
655
+ {var r=a._Consent.isEnabledForUserConsent;if(r(k,a.Identity.getCurrentUser())){var s=null!==(g=null===(f=i[c])||void 0===f?void 0:f.csd)&&void 0!==g?g:{},t=s[n]||null;if(isLastSyncDateExpired(p,t)){var u=n===PARTNER_MODULE_IDS.TradeDesk?window.location.hostname:void 0,v="true"===(null===(h=null===o||void 0===o?void 0:o.enableHmTag)||void 0===h?void 0:h.toLowerCase()),w=n===PARTNER_MODULE_IDS.DoubleclickDFP,x=w&&v?toWebSafeBase64(c):void 0,y=createCookieSyncUrl(c,l,m,u,x);b.performCookieSync(y,n.toString(),c,s);}}}});}},this.performCookieSync=function(b,c,d,e){var f=document.createElement("img");a.Logger.verbose(InformationMessages.CookieSync),f.onload=function(){e[c]=new Date().getTime(),a._Persistence.saveUserCookieSyncDatesToPersistence(d,e);},f.src=b;};}var isLastSyncDateExpired=function(a,b){// If there is no lastSyncDate, then there is no previous cookie sync, so we should sync the cookie
648
656
  return !b||new Date().getTime()>new Date(b).getTime()+a*DAYS_IN_MILLISECONDS;// Otherwise, compare the last sync date to determine if it should do a cookie sync again
649
657
  };
650
658
 
@@ -1650,6 +1658,13 @@ var ErrorReportingDispatcher=/** @class */function(){function a(){this.services=
1650
1658
 
1651
1659
  var LoggingDispatcher=/** @class */function(){function a(){this.services=[];}return a.prototype.register=function(a){this.services.push(a);},a.prototype.log=function(a){var b=this;this.services.forEach(function(c){var d;try{c.log(a);}catch(a){null===(d=b.logger)||void 0===d?void 0:d.error("Error in LoggingService: "+a);}});},a}();
1652
1660
 
1661
+ var WRAPPED_MARKER="__mpApvWrapped__",PageViewTracker=/** @class */function(){function a(a){this.lastPath=null,this.isActive=!1,this.originalPushState=null,this.originalReplaceState=null,this.pushStateWrapper=null,this.replaceStateWrapper=null,this.popStateListener=null,this.mpInstance=a;}return a.prototype.isSupportedEnvironment=function(){return "undefined"!=typeof window&&"undefined"!=typeof window.history&&"function"==typeof window.history.pushState&&"function"==typeof window.addEventListener},a.prototype.init=function(){return this.mpInstance.Logger.verbose("mParticle APV: [init] PageViewTracker Init"),this.isSupportedEnvironment()?void(this.isActive&&(this.mpInstance.Logger.verbose("mParticle APV: [init] starting (teardown-first for idempotency)"),this.teardown()),this.isActive=!0,this.lastPath=this.getCurrentKey(),this.mpInstance.Logger.verbose("mParticle APV: [init] seeded lastPath: ".concat(this.lastPath)),this.patchHistoryMethods(),this.addNavigationListeners(),this.mpInstance.Logger.verbose("mParticle APV: [init] patched pushState/replaceState + listening for popstate")):void this.mpInstance.Logger.verbose("mParticle APV: [init] unsupported environment (no History API), not starting")},a.prototype.patchHistoryMethods=function(){var a=this,b=window.history.pushState;if(b[WRAPPED_MARKER])return void this.mpInstance.Logger.verbose("mParticle APV: [patch] history already wrapped, skipping to avoid double-wrap");var c=window.history.pushState,d=window.history.replaceState;this.originalPushState=c,this.originalReplaceState=d;var e=function(){for(var b=[],d=0;d<arguments.length;d++)b[d]=arguments[d];var e=c.apply(this,b);return a.safeHandleNavigation("pushState"),e},f=function(){for(var b=[],c=0;c<arguments.length;c++)b[c]=arguments[c];var e=d.apply(this,b);return a.safeHandleNavigation("replaceState"),e};Object.defineProperty(e,WRAPPED_MARKER,{value:!0,enumerable:!1}),Object.defineProperty(f,WRAPPED_MARKER,{value:!0,enumerable:!1}),this.pushStateWrapper=e,this.replaceStateWrapper=f;try{window.history.pushState=e,window.history.replaceState=f;}catch(a){this.mpInstance.Logger.verbose("mParticle APV: [error] failed to patch history methods (frozen/sealed), rolling back: ".concat(a));try{window.history.pushState===e&&(window.history.pushState=c),window.history.replaceState===f&&(window.history.replaceState=d);}catch(a){this.mpInstance.Logger.verbose("mParticle APV: [error] failed to restore history methods after patch failure: ".concat(a));}this.pushStateWrapper=null,this.replaceStateWrapper=null,this.originalPushState=null,this.originalReplaceState=null;}},a.prototype.addNavigationListeners=function(){var a=this;this.popStateListener=function(){return a.safeHandleNavigation("popstate")},window.addEventListener("popstate",this.popStateListener);},a.prototype.safeHandleNavigation=function(a){try{this.handleNavigation(a);}catch(b){this.mpInstance.Logger.verbose("mParticle APV: [error] navigation handler threw (".concat(a,"), page view skipped: ").concat(b));}},a.prototype.getCurrentKey=function(){return window.location.pathname},a.prototype.handleNavigation=function(a){var b=this,c=this.getCurrentKey();if(this.mpInstance.Logger.verbose("mParticle APV: [detect] navigation signal (source: ".concat(a,", candidatePath: ").concat(c,", lastPath: ").concat(this.lastPath,")")),c===this.lastPath)return void this.mpInstance.Logger.verbose("mParticle APV: [dedupe] pathname unchanged, skipping (source: ".concat(a,", path: ").concat(c,")"));this.mpInstance.Logger.verbose("mParticle APV: [accept] pathname changed, scheduling fire (source: ".concat(a,", from: ").concat(this.lastPath,", to: ").concat(c,")")),this.lastPath=c;// Snapshot the path now: it is already settled at this point, and a
1662
+ // same-tick navigation would otherwise overwrite window.location
1663
+ // before the deferred flush reads it. The title is intentionally read
1664
+ // later (in the deferred flush) so the router's post-navigation render
1665
+ // commit has a chance to update document.title first.
1666
+ setTimeout(function(){return b.isActive?void(b.mpInstance._SessionManager.resetSessionTimer(),b.firePageView(c)):void b.mpInstance.Logger.verbose("mParticle APV: [defer] fire aborted, tracker inactive (torn down before flush)")},0);},a.prototype.firePageView=function(a){var b=window.document.title;this.mpInstance.Logger.verbose("mParticle APV: [fire] deferred flush -> _Events.logEvent(PageView) (path: ".concat(a,", title: ").concat(b,")")),this.mpInstance._Events.logEvent({messageType:MessageType$1.PageView,name:"PageView",data:{hostname:window.location.hostname,title:b,path:a},eventType:EventType.Unknown});},a.prototype.teardown=function(){this.popStateListener&&(window.removeEventListener("popstate",this.popStateListener),this.popStateListener=null);var a=null!==this.pushStateWrapper&&window.history.pushState===this.pushStateWrapper;this.originalPushState&&(a?(window.history.pushState=this.originalPushState,this.mpInstance.Logger.verbose("mParticle APV: [teardown] restored original pushState")):this.mpInstance.Logger.verbose("mParticle APV: [teardown] pushState no longer ours; leaving in place, gating callback to no-op"),this.originalPushState=null,this.pushStateWrapper=null);var b=null!==this.replaceStateWrapper&&window.history.replaceState===this.replaceStateWrapper;this.originalReplaceState&&(b?(window.history.replaceState=this.originalReplaceState,this.mpInstance.Logger.verbose("mParticle APV: [teardown] restored original replaceState")):this.mpInstance.Logger.verbose("mParticle APV: [teardown] replaceState no longer ours; leaving in place, gating callback to no-op"),this.originalReplaceState=null,this.replaceStateWrapper=null),this.isActive=!1;},a}();
1667
+
1653
1668
  var Messages=Constants.Messages,HTTPCodes=Constants.HTTPCodes,FeatureFlags=Constants.FeatureFlags,CaptureIntegrationSpecificIdsV2Modes=Constants.CaptureIntegrationSpecificIdsV2Modes,ReportBatching=FeatureFlags.ReportBatching,CaptureIntegrationSpecificIds=FeatureFlags.CaptureIntegrationSpecificIds,CaptureIntegrationSpecificIdsV2=FeatureFlags.CaptureIntegrationSpecificIdsV2,StartingInitialization=Messages.InformationMessages.StartingInitialization;/**
1654
1669
  * <p>All of the following methods can be called on the primary mParticle class. In version 2.10.0, we introduced <a href="https://docs.mparticle.com/developers/sdk/web/multiple-instances/">multiple instances</a>. If you are using multiple instances (self hosted environments only), you should call these methods on each instance.</p>
1655
1670
  * <p>In current versions of mParticle, if your site has one instance, that instance name is 'default_instance'. Any methods called on mParticle on a site with one instance will be mapped to the `default_instance`.</p>
@@ -1827,7 +1842,7 @@ if((null===(d=c._APIClient)||void 0===d?void 0:d.uploader)&&c._APIClient.uploade
1827
1842
  var k=c.Identity.getCurrentUser(),l=k?k.getMPID():null,m=k?k.getUserIdentities().userIdentities:{};c._Store.SDKConfig.identifyRequest=c._Store.hasInvalidIdentifyRequest()?{userIdentities:m}:c._Store.SDKConfig.identifyRequest,i(ReportBatching)&&c._ForwardingStatsUploader.startForwardingStatsTimer();// https://go.mparticle.com/work/SQDSDKS-7639
1828
1843
  var n=i(CaptureIntegrationSpecificIds),o=i(CaptureIntegrationSpecificIdsV2),p=o&&o!==CaptureIntegrationSpecificIdsV2Modes.None||!0===n;if(p){var q;n||o===CaptureIntegrationSpecificIdsV2Modes.All?q="all":o===CaptureIntegrationSpecificIdsV2Modes.RoktOnly&&(q="roktonly"),c._IntegrationCapture=new IntegrationCapture(q),c._IntegrationCapture.capture();}// Configure Rokt Manager with user and filtered user
1829
1844
  var r=parseConfig(b,"Rokt",181);if(r){var s=null===(e=r.settings)||void 0===e?void 0:e.accountId;c._Store.setRoktAccountId(s);var t=r.userAttributeFilters,u=filteredMparticleUser(l,{userAttributeFilters:t},c),v={sandbox:null===b||void 0===b?void 0:b.isDevelopmentMode,launcherOptions:null===b||void 0===b?void 0:b.launcherOptions,domain:null===b||void 0===b?void 0:b.domain};// https://go.mparticle.com/work/SQDSDKS-7339
1830
- c._RoktManager.init(r,u,c.Identity,c._Store,c.Logger,v,c.captureTiming,c._ErrorReportingDispatcher,c._LoggingDispatcher,c._IntegrationCapture);}c._Forwarders.processForwarders(b,c._APIClient.prepareForwardingStats),c._Forwarders.processPixelConfigs(b),c._SessionManager.initialize(),c._Events.logAST(),i(j)&&c._Events.logPageView(),processIdentityCallback(c,k,l,m);}// We will continue to clear out the ready queue as part of the initial init flow
1845
+ c._RoktManager.init(r,u,c.Identity,c._Store,c.Logger,v,c.captureTiming,c._ErrorReportingDispatcher,c._LoggingDispatcher,c._IntegrationCapture);}c._Forwarders.processForwarders(b,c._APIClient.prepareForwardingStats),c._Forwarders.processPixelConfigs(b),c._SessionManager.initialize(),c._Events.logAST(),i(j)?(c._Events.logPageView(),!c._PageViewTracker&&(c._PageViewTracker=new PageViewTracker(c)),c._PageViewTracker.init()):c._PageViewTracker&&c._PageViewTracker.teardown(),processIdentityCallback(c,k,l,m);}// We will continue to clear out the ready queue as part of the initial init flow
1831
1846
  // if an identify request is unnecessary, such as if there is an existing session
1832
1847
  (c._Store.mpid&&!c._Store.identifyCalled||c._Store.webviewBridgeEnabled)&&(c._Store.isInitialized=!0,null===(f=c._CookieConsentManager)||void 0===f?void 0:f.syncNoTargetingAttribute(c.Identity.getCurrentUser()),c._preInit.readyQueue=processReadyQueue(c._preInit.readyQueue,c.Logger)),null===(g=c.processQueueOnNoFunctional)||void 0===g?void 0:g.call(c),c._Store.isFirstRun&&(c._Store.isFirstRun=!1);}// https://go.mparticle.com/work/SQDSDKS-7061
1833
1848
  function createKitBlocker(a,b){var c,d,e,f;/* There are three ways a data plan object for blocking can be passed to the SDK:
@@ -15,7 +15,7 @@ map:function map(a,b){var c,d,e;if(null===this)throw new TypeError(" this is nul
15
15
  filter:function filter(a/*, thisArg*/){if(void 0===this||null===this)throw new TypeError;var b=Object(this),c=b.length>>>0;if("function"!=typeof a)throw new TypeError;for(var d=[],e=2<=arguments.length?arguments[1]:void 0,f=0;f<c;f++)if(f in b){var g=b[f];a.call(e,g,f,b)&&d.push(g);}return d},// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
16
16
  isArray:function isArray(a){return "[object Array]"===Object.prototype.toString.call(a)},Base64:Base64$1};
17
17
 
18
- var version = "2.76.0";
18
+ var version = "2.78.0";
19
19
 
20
20
  var Constants={sdkVersion:version,sdkVendor:"mparticle",platform:"web",Messages:{DeprecationMessages:{MethodHasBeenDeprecated:"has been deprecated.",MethodMarkedForDeprecationPostfix:"is a deprecated method and will be removed in future releases.",AlternativeMethodPrefix:"Please use the alternate method:"},ErrorMessages:{NoToken:"A token must be specified.",EventNameInvalidType:"Event name must be a valid string value.",EventDataInvalidType:"Event data must be a valid object hash.",LoggingDisabled:"Event logging is currently disabled.",CookieParseError:"Could not parse cookie",EventEmpty:"Event object is null or undefined, cancelling send",APIRequestEmpty:"APIRequest is null or undefined, cancelling send",NoEventType:"Event type must be specified.",TransactionIdRequired:"Transaction ID is required",TransactionRequired:"A transaction attributes object is required",PromotionIdRequired:"Promotion ID is required",BadAttribute:"Attribute value cannot be object or array",BadKey:"Key value cannot be object or array",BadLogPurchase:"Transaction attributes and a product are both required to log a purchase, https://docs.mparticle.com/?javascript#measuring-transactions",AudienceAPINotEnabled:"Your workspace is not enabled to retrieve user audiences."},InformationMessages:{CookieSearch:"Searching for cookie",CookieFound:"Cookie found, parsing values",CookieNotFound:"Cookies not found",CookieSet:"Setting cookie",CookieSync:"Performing cookie sync",SendBegin:"Starting to send event",SendIdentityBegin:"Starting to send event to identity server",SendWindowsPhone:"Sending event to Windows Phone container",SendIOS:"Calling iOS path: ",SendAndroid:"Calling Android JS interface method: ",SendHttp:"Sending event to mParticle HTTP service",SendAliasHttp:"Sending alias request to mParticle HTTP service",SendIdentityHttp:"Sending event to mParticle HTTP service",StartingNewSession:"Starting new Session",StartingLogEvent:"Starting to log event",StartingLogOptOut:"Starting to log user opt in/out",StartingEndSession:"Starting to end session",StartingInitialization:"Starting to initialize",StartingLogCommerceEvent:"Starting to log commerce event",StartingAliasRequest:"Starting to Alias MPIDs",LoadingConfig:"Loading configuration options",AbandonLogEvent:"Cannot log event, logging disabled or developer token not set",AbandonAliasUsers:"Cannot Alias Users, logging disabled or developer token not set",AbandonStartSession:"Cannot start session, logging disabled or developer token not set",AbandonEndSession:"Cannot end session, logging disabled or developer token not set",NoSessionToEnd:"Cannot end session, no active session found"},ValidationMessages:{ModifyIdentityRequestUserIdentitiesPresent:"identityRequests to modify require userIdentities to be present. Request not sent to server. Please fix and try again",IdentityRequesetInvalidKey:"There is an invalid key on your identityRequest object. It can only contain a `userIdentities` object and a `onUserAlias` function. Request not sent to server. Please fix and try again.",OnUserAliasType:"The onUserAlias value must be a function.",UserIdentities:"The userIdentities key must be an object with keys of identityTypes and values of strings. Request not sent to server. Please fix and try again.",UserIdentitiesInvalidKey:"There is an invalid identity key on your `userIdentities` object within the identityRequest. Request not sent to server. Please fix and try again.",UserIdentitiesInvalidValues:"All user identity values must be strings or null. Request not sent to server. Please fix and try again.",AliasMissingMpid:"Alias Request must contain both a destinationMpid and a sourceMpid",AliasNonUniqueMpid:"Alias Request's destinationMpid and sourceMpid must be unique",AliasMissingTime:"Alias Request must have both a startTime and an endTime",AliasStartBeforeEndTime:"Alias Request's endTime must be later than its startTime"}},NativeSdkPaths:{LogEvent:"logEvent",SetUserTag:"setUserTag",RemoveUserTag:"removeUserTag",SetUserAttribute:"setUserAttribute",RemoveUserAttribute:"removeUserAttribute",SetSessionAttribute:"setSessionAttribute",AddToCart:"addToCart",RemoveFromCart:"removeFromCart",ClearCart:"clearCart",LogOut:"logOut",SetUserAttributeList:"setUserAttributeList",RemoveAllUserAttributes:"removeAllUserAttributes",GetUserAttributesLists:"getUserAttributesLists",GetAllUserAttributes:"getAllUserAttributes",Identify:"identify",Logout:"logout",Login:"login",Modify:"modify",Alias:"aliasUsers",Upload:"upload"},StorageNames:{localStorageName:"mprtcl-api",localStorageNameV3:"mprtcl-v3",cookieName:"mprtcl-api",cookieNameV2:"mprtcl-v2",cookieNameV3:"mprtcl-v3",localStorageNameV4:"mprtcl-v4",localStorageProductsV4:"mprtcl-prodv4",cookieNameV4:"mprtcl-v4",currentStorageName:"mprtcl-v4",currentStorageProductsName:"mprtcl-prodv4"},DefaultConfig:{cookieDomain:null,cookieExpiration:365,logLevel:null,timeout:300,sessionTimeout:30,maxProducts:20,forwarderStatsTimeout:5e3,integrationDelayTimeout:5e3,maxCookieSize:3e3,aliasMaxWindow:90,uploadInterval:0// Maximum milliseconds in between batch uploads, below 500 will mean immediate upload. The server returns this as a string, but we are using it as a number internally
21
21
  },DefaultBaseUrls:{v1SecureServiceUrl:"jssdks.mparticle.com/v1/JS/",v2SecureServiceUrl:"jssdks.mparticle.com/v2/JS/",v3SecureServiceUrl:"jssdks.mparticle.com/v3/JS/",configUrl:"jssdkcdns.mparticle.com/JS/v2/",identityUrl:"identity.mparticle.com/v1/",aliasUrl:"jssdks.mparticle.com/v1/identity/",userAudienceUrl:"nativesdks.mparticle.com/v1/"},// These are the paths that are used to construct the CNAME urls
@@ -147,7 +147,7 @@ var Messages$a=Constants.Messages,createCookieString=function(a){return replaceC
147
147
  // -1e11 -> //-100000000000,
148
148
  "".concat(1e7,"-").concat(1e3,"-").concat(4e3,"-").concat(8e3,"-").concat(1e11).replace(/[018]/g,// zeroes, ones, and eights with
149
149
  generateUniqueId// random hex digits
150
- )},getRampNumber=function(a){if(!a)return 100;var b=generateHash(a);return Math.abs(b%100)+1},isObject=function(a){var b=Object.prototype.toString.call(a);return "[object Object]"===b||"[object Error]"===b},parseNumber=function(a){if(isNaN(a)||!isFinite(a))return 0;var b=parseFloat(a);return isNaN(b)?0:b},parseSettingsString=function(a){try{return a?JSON.parse(a.replace(/&quot;/g,"\"")):[]}catch(a){throw new Error("Settings string contains invalid JSON")}},parseStringOrNumber=function(a){return isStringOrNumber(a)?a:null},replaceCommasWithPipes=function(a){return a.replace(/,/g,"|")},replacePipesWithCommas=function(a){return a.replace(/\|/g,",")},replaceApostrophesWithQuotes=function(a){return a.replace(/\'/g,"\"")},replaceQuotesWithApostrophes=function(a){return a.replace(/\"/g,"'")},replaceMPID=function(a,b){return a.replace("%%mpid%%",b)},replaceAmpWithAmpersand=function(a){return a.replace(/&amp;/g,"&")},createCookieSyncUrl=function(a,b,c,d){var e=replaceAmpWithAmpersand(b),f=c?replaceAmpWithAmpersand(c):null,g=replaceMPID(e,a),h=f?replaceMPID(f,a):"",i=g+encodeURIComponent(h);if(d){var j=i.includes("?")?"&":"?";i+="".concat(j,"domain=").concat(d);}return i},returnConvertedBoolean=function(a){return "false"!==a&&"0"!==a&&!!a},decoded=function(a){return decodeURIComponent(a.replace(/\+/g," "))},converted=function(a){return 0===a.indexOf("\"")&&(a=a.slice(1,-1).replace(/\\"/g,"\"").replace(/\\\\/g,"\\")),a},isString=function(a){return "string"==typeof a},isNumber=function(a){return "number"==typeof a},isBoolean=function(a){return "boolean"==typeof a},isFunction=function(a){return "function"==typeof a},isValidAttributeValue=function(a){return a!==void 0&&!isObject(a)&&!Array.isArray(a)},isValidCustomFlagProperty=function(a){return isNumber(a)||isString(a)||isBoolean(a)},toDataPlanSlug=function(a){// Make sure we are only acting on strings or numbers
150
+ )},getRampNumber=function(a){if(!a)return 100;var b=generateHash(a);return Math.abs(b%100)+1},isObject=function(a){var b=Object.prototype.toString.call(a);return "[object Object]"===b||"[object Error]"===b},parseNumber=function(a){if(isNaN(a)||!isFinite(a))return 0;var b=parseFloat(a);return isNaN(b)?0:b},parseSettingsString=function(a){try{return a?JSON.parse(a.replace(/&quot;/g,"\"")):[]}catch(a){throw new Error("Settings string contains invalid JSON")}},parseStringOrNumber=function(a){return isStringOrNumber(a)?a:null},replaceCommasWithPipes=function(a){return a.replace(/,/g,"|")},replacePipesWithCommas=function(a){return a.replace(/\|/g,",")},replaceApostrophesWithQuotes=function(a){return a.replace(/\'/g,"\"")},replaceQuotesWithApostrophes=function(a){return a.replace(/\"/g,"'")},replaceMPID=function(a,b){return a.replace("%%mpid%%",b)},replaceAmpWithAmpersand=function(a){return a.replace(/&amp;/g,"&")},createCookieSyncUrl=function(a,b,c,d,e){var f=replaceAmpWithAmpersand(b),g=c?replaceAmpWithAmpersand(c):null,h=replaceMPID(f,a),i=g?replaceMPID(g,a):"",j=h+encodeURIComponent(i);if(d){var k=j.includes("?")?"&":"?";j+="".concat(k,"domain=").concat(d);}if(e){var k=j.includes("?")?"&":"?";j+="".concat(k,"google_hm=").concat(e);}return j},WEB_SAFE_BASE64_REPLACEMENTS={"+":"-","/":"_","=":""},toWebSafeBase64=function(a){return btoa(a).replace(/[+/=]/g,function(a){return WEB_SAFE_BASE64_REPLACEMENTS[a]})},returnConvertedBoolean=function(a){return "false"!==a&&"0"!==a&&!!a},decoded=function(a){return decodeURIComponent(a.replace(/\+/g," "))},converted=function(a){return 0===a.indexOf("\"")&&(a=a.slice(1,-1).replace(/\\"/g,"\"").replace(/\\\\/g,"\\")),a},isString=function(a){return "string"==typeof a},isNumber=function(a){return "number"==typeof a},isBoolean=function(a){return "boolean"==typeof a},isFunction=function(a){return "function"==typeof a},isValidAttributeValue=function(a){return a!==void 0&&!isObject(a)&&!Array.isArray(a)},isValidCustomFlagProperty=function(a){return isNumber(a)||isString(a)||isBoolean(a)},toDataPlanSlug=function(a){// Make sure we are only acting on strings or numbers
151
151
  return isStringOrNumber(a)?a.toString().toLowerCase().replace(/[^0-9a-zA-Z]+/g,"_"):""},isDataPlanSlug=function(a){return a===toDataPlanSlug(a)},isStringOrNumber=function(a){return isString(a)||isNumber(a)},isEmpty=function(a){return null==a||!(Object.keys(a)||a).length},moveElementToEnd=function(a,b){return a.slice(0,b).concat(a.slice(b+1),a[b])},queryStringParser=function(a,b){void 0===b&&(b=[]);var c,d={},e={};if(!a)return d;if("undefined"!=typeof URL&&"undefined"!=typeof URLSearchParams){var f=new URL(a);c=new URLSearchParams(f.search);}else c=queryStringParserFallback(a);return (c.forEach(function(a,b){e[b.toLowerCase()]=a;}),isEmpty(b))?e:(b.forEach(function(a){var b=e[a.toLowerCase()];b&&(d[a]=b);}),d)},queryStringParserFallback=function(a){var b={},c=a.split("?")[1]||"",d=c.split("&");return d.forEach(function(a){var c=a.split("="),d=c[0],e=c.slice(1),f=e.join("=");if(d&&void 0!==f)try{b[d]=decodeURIComponent(f||"");}catch(a){console.error("Failed to decode value for key ".concat(d,": ").concat(a));}}),{get:function get(a){return b[a]},forEach:function forEach(a){for(var c in b)b.hasOwnProperty(c)&&a(b[c],c);}}},getCookies=function(a){// Helper function to parse cookies from document.cookie
152
152
  var b=function parseCookies(){try{return "undefined"==typeof window?[]:window.document.cookie.split(";").map(function(a){return a.trim()})}catch(a){return console.error("Unable to parse cookies",a),[]}}();// Helper function to filter cookies by keys
153
153
  // Parse cookies from document.cookie
@@ -158,7 +158,11 @@ if(Array.isArray(a))return a.map(function(a){return obfuscateData(a)});// Handle
158
158
  if(isObject(a)){var b={};for(var c in a)a.hasOwnProperty(c)&&(b[c]=obfuscateData(a[c]));return b}// For primitives and other types, return the type as a string
159
159
  return _typeof$1(a)},obfuscateDevData=function(a,b){return b?a:obfuscateData(a)};/**
160
160
  * Returns a value between 1-100 inclusive.
161
- */ // FIXME: REFACTOR for V3
161
+ */ // Google's cookie matching service requires `google_hm` values to be web-safe
162
+ // base64 (RFC 4648 §5) without padding, otherwise the write to the hosted
163
+ // match table fails: https://developers.google.com/authorized-buyers/rtb/cookie-guide
164
+ // (String#replaceAll is not available in our ES5 browser targets.)
165
+ // FIXME: REFACTOR for V3
162
166
  // only used in store.js to sanitize server-side formatting of
163
167
  // booleans when checking for `isDevelopmentMode`
164
168
  // Should be removed in v3
@@ -632,19 +636,23 @@ var PARTNER_MODULE_IDS={AdobeEventForwarder:11,DoubleclickDFP:41,AppNexus:50,Lot
632
636
  // Private
633
637
  this.attemptCookieSync=function(c,d){var e,f=a._Store,g=f.pixelConfigurations,h=f.webviewBridgeEnabled;if(c&&!h&&(null===(e=a._CookieConsentManager)||void 0===e||!e.getNoFunctional()))// When noFunctional is true, persistence is not saved, so we cannot track cookie sync
634
638
  // dates. Skip cookie sync to avoid running it on every page load.
635
- {var i=a._Persistence.getPersistence();isEmpty(i)||g.forEach(function(e){var f,g,h=!1,j=e.filteringConsentRuleValues,k=e.pixelUrl,l=e.redirectUrl,m=e.moduleId,// Tells you how often we should do a cookie sync (in days)
636
- n=e.frequencyCap,o=(j||{}).values;// set requiresConsent to false to start each additional pixel configuration
639
+ {var i=a._Persistence.getPersistence();isEmpty(i)||g.forEach(function(e){var f,g,h,j=!1,k=e.filteringConsentRuleValues,l=e.pixelUrl,m=e.redirectUrl,n=e.moduleId,o=e.settings,// Tells you how often we should do a cookie sync (in days)
640
+ p=e.frequencyCap,q=(k||{}).values;// set requiresConsent to false to start each additional pixel configuration
637
641
  // set to true only if filteringConsenRuleValues.values.length exists
638
642
  // Filtering rules as defined in UI
639
- if(!isEmpty(k)&&(isEmpty(o)||(h=!0),!(h&&d))&&!(m===PARTNER_MODULE_IDS.Rokt&&a._CookieConsentManager.getNoTargeting()))// For Rokt, block cookie sync when noTargeting privacy flag is true
643
+ if(!isEmpty(l)&&(isEmpty(q)||(j=!0),!(j&&d))&&!(n===PARTNER_MODULE_IDS.Rokt&&a._CookieConsentManager.getNoTargeting()))// For Rokt, block cookie sync when noTargeting privacy flag is true
640
644
  // If MPID is new to cookies, we should not try to perform the cookie sync
641
645
  // because a cookie sync can only occur once a user either consents or doesn't.
642
646
  // we should not check if it's enabled if the user has a blank consent
643
647
  // The Trade Desk requires a URL parameter for GDPR enabled users.
644
648
  // It is optional but to simplify the code, we add it for all Trade
645
649
  // // Desk cookie syncs.
646
- // Add domain parameter for Trade Desk
647
- {var p=a._Consent.isEnabledForUserConsent;if(p(j,a.Identity.getCurrentUser())){var q=null!==(g=null===(f=i[c])||void 0===f?void 0:f.csd)&&void 0!==g?g:{},r=q[m]||null;if(isLastSyncDateExpired(n,r)){var s=m===PARTNER_MODULE_IDS.TradeDesk?window.location.hostname:void 0,t=createCookieSyncUrl(c,k,l,s);b.performCookieSync(t,m.toString(),c,q);}}}});}},this.performCookieSync=function(b,c,d,e){var f=document.createElement("img");a.Logger.verbose(InformationMessages.CookieSync),f.onload=function(){e[c]=new Date().getTime(),a._Persistence.saveUserCookieSyncDatesToPersistence(d,e);},f.src=b;};}var isLastSyncDateExpired=function(a,b){// If there is no lastSyncDate, then there is no previous cookie sync, so we should sync the cookie
650
+ // Google Marketing Platform accepts a web-safe base64-encoded MPID via the
651
+ // `google_hm` query parameter, but only for accounts provisioned for Google
652
+ // Hosted Matching — Google returns errors otherwise. It is therefore gated
653
+ // behind the opt-in `enableHmTag` setting (off by default), which the server
654
+ // delivers as the string 'True' when the UI toggle is enabled.
655
+ {var r=a._Consent.isEnabledForUserConsent;if(r(k,a.Identity.getCurrentUser())){var s=null!==(g=null===(f=i[c])||void 0===f?void 0:f.csd)&&void 0!==g?g:{},t=s[n]||null;if(isLastSyncDateExpired(p,t)){var u=n===PARTNER_MODULE_IDS.TradeDesk?window.location.hostname:void 0,v="true"===(null===(h=null===o||void 0===o?void 0:o.enableHmTag)||void 0===h?void 0:h.toLowerCase()),w=n===PARTNER_MODULE_IDS.DoubleclickDFP,x=w&&v?toWebSafeBase64(c):void 0,y=createCookieSyncUrl(c,l,m,u,x);b.performCookieSync(y,n.toString(),c,s);}}}});}},this.performCookieSync=function(b,c,d,e){var f=document.createElement("img");a.Logger.verbose(InformationMessages.CookieSync),f.onload=function(){e[c]=new Date().getTime(),a._Persistence.saveUserCookieSyncDatesToPersistence(d,e);},f.src=b;};}var isLastSyncDateExpired=function(a,b){// If there is no lastSyncDate, then there is no previous cookie sync, so we should sync the cookie
648
656
  return !b||new Date().getTime()>new Date(b).getTime()+a*DAYS_IN_MILLISECONDS;// Otherwise, compare the last sync date to determine if it should do a cookie sync again
649
657
  };
650
658
 
@@ -1650,6 +1658,13 @@ var ErrorReportingDispatcher=/** @class */function(){function a(){this.services=
1650
1658
 
1651
1659
  var LoggingDispatcher=/** @class */function(){function a(){this.services=[];}return a.prototype.register=function(a){this.services.push(a);},a.prototype.log=function(a){var b=this;this.services.forEach(function(c){var d;try{c.log(a);}catch(a){null===(d=b.logger)||void 0===d?void 0:d.error("Error in LoggingService: "+a);}});},a}();
1652
1660
 
1661
+ var WRAPPED_MARKER="__mpApvWrapped__",PageViewTracker=/** @class */function(){function a(a){this.lastPath=null,this.isActive=!1,this.originalPushState=null,this.originalReplaceState=null,this.pushStateWrapper=null,this.replaceStateWrapper=null,this.popStateListener=null,this.mpInstance=a;}return a.prototype.isSupportedEnvironment=function(){return "undefined"!=typeof window&&"undefined"!=typeof window.history&&"function"==typeof window.history.pushState&&"function"==typeof window.addEventListener},a.prototype.init=function(){return this.mpInstance.Logger.verbose("mParticle APV: [init] PageViewTracker Init"),this.isSupportedEnvironment()?void(this.isActive&&(this.mpInstance.Logger.verbose("mParticle APV: [init] starting (teardown-first for idempotency)"),this.teardown()),this.isActive=!0,this.lastPath=this.getCurrentKey(),this.mpInstance.Logger.verbose("mParticle APV: [init] seeded lastPath: ".concat(this.lastPath)),this.patchHistoryMethods(),this.addNavigationListeners(),this.mpInstance.Logger.verbose("mParticle APV: [init] patched pushState/replaceState + listening for popstate")):void this.mpInstance.Logger.verbose("mParticle APV: [init] unsupported environment (no History API), not starting")},a.prototype.patchHistoryMethods=function(){var a=this,b=window.history.pushState;if(b[WRAPPED_MARKER])return void this.mpInstance.Logger.verbose("mParticle APV: [patch] history already wrapped, skipping to avoid double-wrap");var c=window.history.pushState,d=window.history.replaceState;this.originalPushState=c,this.originalReplaceState=d;var e=function(){for(var b=[],d=0;d<arguments.length;d++)b[d]=arguments[d];var e=c.apply(this,b);return a.safeHandleNavigation("pushState"),e},f=function(){for(var b=[],c=0;c<arguments.length;c++)b[c]=arguments[c];var e=d.apply(this,b);return a.safeHandleNavigation("replaceState"),e};Object.defineProperty(e,WRAPPED_MARKER,{value:!0,enumerable:!1}),Object.defineProperty(f,WRAPPED_MARKER,{value:!0,enumerable:!1}),this.pushStateWrapper=e,this.replaceStateWrapper=f;try{window.history.pushState=e,window.history.replaceState=f;}catch(a){this.mpInstance.Logger.verbose("mParticle APV: [error] failed to patch history methods (frozen/sealed), rolling back: ".concat(a));try{window.history.pushState===e&&(window.history.pushState=c),window.history.replaceState===f&&(window.history.replaceState=d);}catch(a){this.mpInstance.Logger.verbose("mParticle APV: [error] failed to restore history methods after patch failure: ".concat(a));}this.pushStateWrapper=null,this.replaceStateWrapper=null,this.originalPushState=null,this.originalReplaceState=null;}},a.prototype.addNavigationListeners=function(){var a=this;this.popStateListener=function(){return a.safeHandleNavigation("popstate")},window.addEventListener("popstate",this.popStateListener);},a.prototype.safeHandleNavigation=function(a){try{this.handleNavigation(a);}catch(b){this.mpInstance.Logger.verbose("mParticle APV: [error] navigation handler threw (".concat(a,"), page view skipped: ").concat(b));}},a.prototype.getCurrentKey=function(){return window.location.pathname},a.prototype.handleNavigation=function(a){var b=this,c=this.getCurrentKey();if(this.mpInstance.Logger.verbose("mParticle APV: [detect] navigation signal (source: ".concat(a,", candidatePath: ").concat(c,", lastPath: ").concat(this.lastPath,")")),c===this.lastPath)return void this.mpInstance.Logger.verbose("mParticle APV: [dedupe] pathname unchanged, skipping (source: ".concat(a,", path: ").concat(c,")"));this.mpInstance.Logger.verbose("mParticle APV: [accept] pathname changed, scheduling fire (source: ".concat(a,", from: ").concat(this.lastPath,", to: ").concat(c,")")),this.lastPath=c;// Snapshot the path now: it is already settled at this point, and a
1662
+ // same-tick navigation would otherwise overwrite window.location
1663
+ // before the deferred flush reads it. The title is intentionally read
1664
+ // later (in the deferred flush) so the router's post-navigation render
1665
+ // commit has a chance to update document.title first.
1666
+ setTimeout(function(){return b.isActive?void(b.mpInstance._SessionManager.resetSessionTimer(),b.firePageView(c)):void b.mpInstance.Logger.verbose("mParticle APV: [defer] fire aborted, tracker inactive (torn down before flush)")},0);},a.prototype.firePageView=function(a){var b=window.document.title;this.mpInstance.Logger.verbose("mParticle APV: [fire] deferred flush -> _Events.logEvent(PageView) (path: ".concat(a,", title: ").concat(b,")")),this.mpInstance._Events.logEvent({messageType:MessageType$1.PageView,name:"PageView",data:{hostname:window.location.hostname,title:b,path:a},eventType:EventType.Unknown});},a.prototype.teardown=function(){this.popStateListener&&(window.removeEventListener("popstate",this.popStateListener),this.popStateListener=null);var a=null!==this.pushStateWrapper&&window.history.pushState===this.pushStateWrapper;this.originalPushState&&(a?(window.history.pushState=this.originalPushState,this.mpInstance.Logger.verbose("mParticle APV: [teardown] restored original pushState")):this.mpInstance.Logger.verbose("mParticle APV: [teardown] pushState no longer ours; leaving in place, gating callback to no-op"),this.originalPushState=null,this.pushStateWrapper=null);var b=null!==this.replaceStateWrapper&&window.history.replaceState===this.replaceStateWrapper;this.originalReplaceState&&(b?(window.history.replaceState=this.originalReplaceState,this.mpInstance.Logger.verbose("mParticle APV: [teardown] restored original replaceState")):this.mpInstance.Logger.verbose("mParticle APV: [teardown] replaceState no longer ours; leaving in place, gating callback to no-op"),this.originalReplaceState=null,this.replaceStateWrapper=null),this.isActive=!1;},a}();
1667
+
1653
1668
  var Messages=Constants.Messages,HTTPCodes=Constants.HTTPCodes,FeatureFlags=Constants.FeatureFlags,CaptureIntegrationSpecificIdsV2Modes=Constants.CaptureIntegrationSpecificIdsV2Modes,ReportBatching=FeatureFlags.ReportBatching,CaptureIntegrationSpecificIds=FeatureFlags.CaptureIntegrationSpecificIds,CaptureIntegrationSpecificIdsV2=FeatureFlags.CaptureIntegrationSpecificIdsV2,StartingInitialization=Messages.InformationMessages.StartingInitialization;/**
1654
1669
  * <p>All of the following methods can be called on the primary mParticle class. In version 2.10.0, we introduced <a href="https://docs.mparticle.com/developers/sdk/web/multiple-instances/">multiple instances</a>. If you are using multiple instances (self hosted environments only), you should call these methods on each instance.</p>
1655
1670
  * <p>In current versions of mParticle, if your site has one instance, that instance name is 'default_instance'. Any methods called on mParticle on a site with one instance will be mapped to the `default_instance`.</p>
@@ -1827,7 +1842,7 @@ if((null===(d=c._APIClient)||void 0===d?void 0:d.uploader)&&c._APIClient.uploade
1827
1842
  var k=c.Identity.getCurrentUser(),l=k?k.getMPID():null,m=k?k.getUserIdentities().userIdentities:{};c._Store.SDKConfig.identifyRequest=c._Store.hasInvalidIdentifyRequest()?{userIdentities:m}:c._Store.SDKConfig.identifyRequest,i(ReportBatching)&&c._ForwardingStatsUploader.startForwardingStatsTimer();// https://go.mparticle.com/work/SQDSDKS-7639
1828
1843
  var n=i(CaptureIntegrationSpecificIds),o=i(CaptureIntegrationSpecificIdsV2),p=o&&o!==CaptureIntegrationSpecificIdsV2Modes.None||!0===n;if(p){var q;n||o===CaptureIntegrationSpecificIdsV2Modes.All?q="all":o===CaptureIntegrationSpecificIdsV2Modes.RoktOnly&&(q="roktonly"),c._IntegrationCapture=new IntegrationCapture(q),c._IntegrationCapture.capture();}// Configure Rokt Manager with user and filtered user
1829
1844
  var r=parseConfig(b,"Rokt",181);if(r){var s=null===(e=r.settings)||void 0===e?void 0:e.accountId;c._Store.setRoktAccountId(s);var t=r.userAttributeFilters,u=filteredMparticleUser(l,{userAttributeFilters:t},c),v={sandbox:null===b||void 0===b?void 0:b.isDevelopmentMode,launcherOptions:null===b||void 0===b?void 0:b.launcherOptions,domain:null===b||void 0===b?void 0:b.domain};// https://go.mparticle.com/work/SQDSDKS-7339
1830
- c._RoktManager.init(r,u,c.Identity,c._Store,c.Logger,v,c.captureTiming,c._ErrorReportingDispatcher,c._LoggingDispatcher,c._IntegrationCapture);}c._Forwarders.processForwarders(b,c._APIClient.prepareForwardingStats),c._Forwarders.processPixelConfigs(b),c._SessionManager.initialize(),c._Events.logAST(),i(j)&&c._Events.logPageView(),processIdentityCallback(c,k,l,m);}// We will continue to clear out the ready queue as part of the initial init flow
1845
+ c._RoktManager.init(r,u,c.Identity,c._Store,c.Logger,v,c.captureTiming,c._ErrorReportingDispatcher,c._LoggingDispatcher,c._IntegrationCapture);}c._Forwarders.processForwarders(b,c._APIClient.prepareForwardingStats),c._Forwarders.processPixelConfigs(b),c._SessionManager.initialize(),c._Events.logAST(),i(j)?(c._Events.logPageView(),!c._PageViewTracker&&(c._PageViewTracker=new PageViewTracker(c)),c._PageViewTracker.init()):c._PageViewTracker&&c._PageViewTracker.teardown(),processIdentityCallback(c,k,l,m);}// We will continue to clear out the ready queue as part of the initial init flow
1831
1846
  // if an identify request is unnecessary, such as if there is an existing session
1832
1847
  (c._Store.mpid&&!c._Store.identifyCalled||c._Store.webviewBridgeEnabled)&&(c._Store.isInitialized=!0,null===(f=c._CookieConsentManager)||void 0===f?void 0:f.syncNoTargetingAttribute(c.Identity.getCurrentUser()),c._preInit.readyQueue=processReadyQueue(c._preInit.readyQueue,c.Logger)),null===(g=c.processQueueOnNoFunctional)||void 0===g?void 0:g.call(c),c._Store.isFirstRun&&(c._Store.isFirstRun=!1);}// https://go.mparticle.com/work/SQDSDKS-7061
1833
1848
  function createKitBlocker(a,b){var c,d,e,f;/* There are three ways a data plan object for blocking can be passed to the SDK:
package/dist/mparticle.js CHANGED
@@ -203,7 +203,7 @@ var mParticle = (function () {
203
203
  Base64: Base64$1
204
204
  };
205
205
 
206
- var version = "2.76.0";
206
+ var version = "2.78.0";
207
207
 
208
208
  var Constants = {
209
209
  sdkVersion: version,
@@ -684,7 +684,7 @@ var mParticle = (function () {
684
684
  var replaceAmpWithAmpersand = function replaceAmpWithAmpersand(value) {
685
685
  return value.replace(/&amp;/g, '&');
686
686
  };
687
- var createCookieSyncUrl = function createCookieSyncUrl(mpid, pixelUrl, redirectUrl, domain) {
687
+ var createCookieSyncUrl = function createCookieSyncUrl(mpid, pixelUrl, redirectUrl, domain, base64Mpid) {
688
688
  var modifiedPixelUrl = replaceAmpWithAmpersand(pixelUrl);
689
689
  var modifiedDirectUrl = redirectUrl ? replaceAmpWithAmpersand(redirectUrl) : null;
690
690
  var url = replaceMPID(modifiedPixelUrl, mpid);
@@ -694,8 +694,26 @@ var mParticle = (function () {
694
694
  var separator = fullUrl.includes('?') ? '&' : '?';
695
695
  fullUrl += "".concat(separator, "domain=").concat(domain);
696
696
  }
697
+ if (base64Mpid) {
698
+ var separator = fullUrl.includes('?') ? '&' : '?';
699
+ fullUrl += "".concat(separator, "google_hm=").concat(base64Mpid);
700
+ }
697
701
  return fullUrl;
698
702
  };
703
+ // Google's cookie matching service requires `google_hm` values to be web-safe
704
+ // base64 (RFC 4648 §5) without padding, otherwise the write to the hosted
705
+ // match table fails: https://developers.google.com/authorized-buyers/rtb/cookie-guide
706
+ // (String#replaceAll is not available in our ES5 browser targets.)
707
+ var WEB_SAFE_BASE64_REPLACEMENTS = {
708
+ '+': '-',
709
+ '/': '_',
710
+ '=': ''
711
+ };
712
+ var toWebSafeBase64 = function toWebSafeBase64(value) {
713
+ return btoa(value).replace(/[+/=]/g, function (c) {
714
+ return WEB_SAFE_BASE64_REPLACEMENTS[c];
715
+ });
716
+ };
699
717
  // FIXME: REFACTOR for V3
700
718
  // only used in store.js to sanitize server-side formatting of
701
719
  // booleans when checking for `isDevelopmentMode`
@@ -4190,7 +4208,7 @@ var mParticle = (function () {
4190
4208
  return;
4191
4209
  }
4192
4210
  pixelConfigurations.forEach(function (pixelSettings) {
4193
- var _a, _b;
4211
+ var _a, _b, _c;
4194
4212
  // set requiresConsent to false to start each additional pixel configuration
4195
4213
  // set to true only if filteringConsenRuleValues.values.length exists
4196
4214
  var requiresConsent = false;
@@ -4199,6 +4217,7 @@ var mParticle = (function () {
4199
4217
  pixelUrl = pixelSettings.pixelUrl,
4200
4218
  redirectUrl = pixelSettings.redirectUrl,
4201
4219
  moduleId = pixelSettings.moduleId,
4220
+ settings = pixelSettings.settings,
4202
4221
  // Tells you how often we should do a cookie sync (in days)
4203
4222
  frequencyCap = pixelSettings.frequencyCap;
4204
4223
  var values = (filteringConsentRuleValues || {}).values;
@@ -4231,8 +4250,15 @@ var mParticle = (function () {
4231
4250
  // It is optional but to simplify the code, we add it for all Trade
4232
4251
  // // Desk cookie syncs.
4233
4252
  var domain = moduleId === PARTNER_MODULE_IDS.TradeDesk ? window.location.hostname : undefined;
4234
- // Add domain parameter for Trade Desk
4235
- var fullUrl = createCookieSyncUrl(mpid, pixelUrl, redirectUrl, domain);
4253
+ // Google Marketing Platform accepts a web-safe base64-encoded MPID via the
4254
+ // `google_hm` query parameter, but only for accounts provisioned for Google
4255
+ // Hosted Matching — Google returns errors otherwise. It is therefore gated
4256
+ // behind the opt-in `enableHmTag` setting (off by default), which the server
4257
+ // delivers as the string 'True' when the UI toggle is enabled.
4258
+ var enableHmTag = ((_c = settings === null || settings === void 0 ? void 0 : settings.enableHmTag) === null || _c === void 0 ? void 0 : _c.toLowerCase()) === 'true';
4259
+ var isDoubleClickModule = moduleId === PARTNER_MODULE_IDS.DoubleclickDFP;
4260
+ var base64Mpid = isDoubleClickModule && enableHmTag ? toWebSafeBase64(mpid) : undefined;
4261
+ var fullUrl = createCookieSyncUrl(mpid, pixelUrl, redirectUrl, domain, base64Mpid);
4236
4262
  self.performCookieSync(fullUrl, moduleId.toString(), mpid, cookieSyncDates);
4237
4263
  });
4238
4264
  };
@@ -11145,6 +11171,188 @@ var mParticle = (function () {
11145
11171
  return LoggingDispatcher;
11146
11172
  }();
11147
11173
 
11174
+ var WRAPPED_MARKER = '__mpApvWrapped__';
11175
+ var PageViewTracker = /** @class */function () {
11176
+ function PageViewTracker(mpInstance) {
11177
+ this.lastPath = null;
11178
+ this.isActive = false;
11179
+ this.originalPushState = null;
11180
+ this.originalReplaceState = null;
11181
+ this.pushStateWrapper = null;
11182
+ this.replaceStateWrapper = null;
11183
+ this.popStateListener = null;
11184
+ this.mpInstance = mpInstance;
11185
+ }
11186
+ PageViewTracker.prototype.isSupportedEnvironment = function () {
11187
+ return typeof window !== 'undefined' && typeof window.history !== 'undefined' && typeof window.history.pushState === 'function' && typeof window.addEventListener === 'function';
11188
+ };
11189
+ PageViewTracker.prototype.init = function () {
11190
+ this.mpInstance.Logger.verbose('mParticle APV: [init] PageViewTracker Init');
11191
+ if (!this.isSupportedEnvironment()) {
11192
+ this.mpInstance.Logger.verbose('mParticle APV: [init] unsupported environment (no History API), not starting');
11193
+ return;
11194
+ }
11195
+ if (this.isActive) {
11196
+ this.mpInstance.Logger.verbose('mParticle APV: [init] starting (teardown-first for idempotency)');
11197
+ this.teardown();
11198
+ }
11199
+ this.isActive = true;
11200
+ this.lastPath = this.getCurrentKey();
11201
+ this.mpInstance.Logger.verbose("mParticle APV: [init] seeded lastPath: ".concat(this.lastPath));
11202
+ this.patchHistoryMethods();
11203
+ this.addNavigationListeners();
11204
+ this.mpInstance.Logger.verbose('mParticle APV: [init] patched pushState/replaceState + listening for popstate');
11205
+ };
11206
+ PageViewTracker.prototype.patchHistoryMethods = function () {
11207
+ var self = this;
11208
+ var installed = window.history.pushState;
11209
+ if (installed[WRAPPED_MARKER]) {
11210
+ this.mpInstance.Logger.verbose('mParticle APV: [patch] history already wrapped, skipping to avoid double-wrap');
11211
+ return;
11212
+ }
11213
+ var originalPushState = window.history.pushState;
11214
+ var originalReplaceState = window.history.replaceState;
11215
+ this.originalPushState = originalPushState;
11216
+ this.originalReplaceState = originalReplaceState;
11217
+ var pushStateWrapper = function pushStateWrapper() {
11218
+ var args = [];
11219
+ for (var _i = 0; _i < arguments.length; _i++) {
11220
+ args[_i] = arguments[_i];
11221
+ }
11222
+ var result = originalPushState.apply(this, args);
11223
+ self.safeHandleNavigation('pushState');
11224
+ return result;
11225
+ };
11226
+ var replaceStateWrapper = function replaceStateWrapper() {
11227
+ var args = [];
11228
+ for (var _i = 0; _i < arguments.length; _i++) {
11229
+ args[_i] = arguments[_i];
11230
+ }
11231
+ var result = originalReplaceState.apply(this, args);
11232
+ self.safeHandleNavigation('replaceState');
11233
+ return result;
11234
+ };
11235
+ Object.defineProperty(pushStateWrapper, WRAPPED_MARKER, {
11236
+ value: true,
11237
+ enumerable: false
11238
+ });
11239
+ Object.defineProperty(replaceStateWrapper, WRAPPED_MARKER, {
11240
+ value: true,
11241
+ enumerable: false
11242
+ });
11243
+ this.pushStateWrapper = pushStateWrapper;
11244
+ this.replaceStateWrapper = replaceStateWrapper;
11245
+ try {
11246
+ window.history.pushState = pushStateWrapper;
11247
+ window.history.replaceState = replaceStateWrapper;
11248
+ } catch (e) {
11249
+ this.mpInstance.Logger.verbose("mParticle APV: [error] failed to patch history methods (frozen/sealed), rolling back: ".concat(e));
11250
+ try {
11251
+ if (window.history.pushState === pushStateWrapper) {
11252
+ window.history.pushState = originalPushState;
11253
+ }
11254
+ if (window.history.replaceState === replaceStateWrapper) {
11255
+ window.history.replaceState = originalReplaceState;
11256
+ }
11257
+ } catch (restoreError) {
11258
+ this.mpInstance.Logger.verbose("mParticle APV: [error] failed to restore history methods after patch failure: ".concat(restoreError));
11259
+ }
11260
+ this.pushStateWrapper = null;
11261
+ this.replaceStateWrapper = null;
11262
+ this.originalPushState = null;
11263
+ this.originalReplaceState = null;
11264
+ }
11265
+ };
11266
+ PageViewTracker.prototype.addNavigationListeners = function () {
11267
+ var _this = this;
11268
+ this.popStateListener = function () {
11269
+ return _this.safeHandleNavigation('popstate');
11270
+ };
11271
+ window.addEventListener('popstate', this.popStateListener);
11272
+ };
11273
+ PageViewTracker.prototype.safeHandleNavigation = function (source) {
11274
+ try {
11275
+ this.handleNavigation(source);
11276
+ } catch (e) {
11277
+ this.mpInstance.Logger.verbose("mParticle APV: [error] navigation handler threw (".concat(source, "), page view skipped: ").concat(e));
11278
+ }
11279
+ };
11280
+ PageViewTracker.prototype.getCurrentKey = function () {
11281
+ return window.location.pathname;
11282
+ };
11283
+ PageViewTracker.prototype.handleNavigation = function (source) {
11284
+ var _this = this;
11285
+ var candidatePath = this.getCurrentKey();
11286
+ this.mpInstance.Logger.verbose("mParticle APV: [detect] navigation signal (source: ".concat(source, ", candidatePath: ").concat(candidatePath, ", lastPath: ").concat(this.lastPath, ")"));
11287
+ if (candidatePath === this.lastPath) {
11288
+ this.mpInstance.Logger.verbose("mParticle APV: [dedupe] pathname unchanged, skipping (source: ".concat(source, ", path: ").concat(candidatePath, ")"));
11289
+ return;
11290
+ }
11291
+ this.mpInstance.Logger.verbose("mParticle APV: [accept] pathname changed, scheduling fire (source: ".concat(source, ", from: ").concat(this.lastPath, ", to: ").concat(candidatePath, ")"));
11292
+ this.lastPath = candidatePath;
11293
+ // Snapshot the path now: it is already settled at this point, and a
11294
+ // same-tick navigation would otherwise overwrite window.location
11295
+ // before the deferred flush reads it. The title is intentionally read
11296
+ // later (in the deferred flush) so the router's post-navigation render
11297
+ // commit has a chance to update document.title first.
11298
+ var capturedPath = candidatePath;
11299
+ setTimeout(function () {
11300
+ if (!_this.isActive) {
11301
+ _this.mpInstance.Logger.verbose('mParticle APV: [defer] fire aborted, tracker inactive (torn down before flush)');
11302
+ return;
11303
+ }
11304
+ _this.mpInstance._SessionManager.resetSessionTimer();
11305
+ _this.firePageView(capturedPath);
11306
+ }, 0);
11307
+ };
11308
+ // Mirrors the event shape of the public mParticle.logPageView(), but carries
11309
+ // the captured SPA path rather than reading the live location.
11310
+ PageViewTracker.prototype.firePageView = function (path) {
11311
+ var title = window.document.title;
11312
+ this.mpInstance.Logger.verbose("mParticle APV: [fire] deferred flush -> _Events.logEvent(PageView) (path: ".concat(path, ", title: ").concat(title, ")"));
11313
+ this.mpInstance._Events.logEvent({
11314
+ messageType: MessageType$1.PageView,
11315
+ name: 'PageView',
11316
+ data: {
11317
+ hostname: window.location.hostname,
11318
+ title: title,
11319
+ path: path
11320
+ },
11321
+ eventType: EventType.Unknown
11322
+ });
11323
+ };
11324
+ PageViewTracker.prototype.teardown = function () {
11325
+ if (this.popStateListener) {
11326
+ window.removeEventListener('popstate', this.popStateListener);
11327
+ this.popStateListener = null;
11328
+ }
11329
+ var pushStateStillOurs = this.pushStateWrapper !== null && window.history.pushState === this.pushStateWrapper;
11330
+ if (this.originalPushState) {
11331
+ if (pushStateStillOurs) {
11332
+ window.history.pushState = this.originalPushState;
11333
+ this.mpInstance.Logger.verbose('mParticle APV: [teardown] restored original pushState');
11334
+ } else {
11335
+ this.mpInstance.Logger.verbose('mParticle APV: [teardown] pushState no longer ours; leaving in place, gating callback to no-op');
11336
+ }
11337
+ this.originalPushState = null;
11338
+ this.pushStateWrapper = null;
11339
+ }
11340
+ var replaceStateStillOurs = this.replaceStateWrapper !== null && window.history.replaceState === this.replaceStateWrapper;
11341
+ if (this.originalReplaceState) {
11342
+ if (replaceStateStillOurs) {
11343
+ window.history.replaceState = this.originalReplaceState;
11344
+ this.mpInstance.Logger.verbose('mParticle APV: [teardown] restored original replaceState');
11345
+ } else {
11346
+ this.mpInstance.Logger.verbose('mParticle APV: [teardown] replaceState no longer ours; leaving in place, gating callback to no-op');
11347
+ }
11348
+ this.originalReplaceState = null;
11349
+ this.replaceStateWrapper = null;
11350
+ }
11351
+ this.isActive = false;
11352
+ };
11353
+ return PageViewTracker;
11354
+ }();
11355
+
11148
11356
  var Messages = Constants.Messages,
11149
11357
  HTTPCodes = Constants.HTTPCodes,
11150
11358
  FeatureFlags = Constants.FeatureFlags,
@@ -12225,6 +12433,12 @@ var mParticle = (function () {
12225
12433
  mpInstance._Events.logAST();
12226
12434
  if (getFeatureFlag(AutoLogPageView)) {
12227
12435
  mpInstance._Events.logPageView();
12436
+ if (!mpInstance._PageViewTracker) {
12437
+ mpInstance._PageViewTracker = new PageViewTracker(mpInstance);
12438
+ }
12439
+ mpInstance._PageViewTracker.init();
12440
+ } else if (mpInstance._PageViewTracker) {
12441
+ mpInstance._PageViewTracker.teardown();
12228
12442
  }
12229
12443
  processIdentityCallback(mpInstance, currentUser, currentUserMPID, currentUserIdentities);
12230
12444
  }
@@ -20,6 +20,7 @@ import RoktManager from './roktManager';
20
20
  import { ICookieConsentManager } from './cookieConsentManager';
21
21
  import { ErrorReportingDispatcher } from './reporting/errorReportingDispatcher';
22
22
  import { LoggingDispatcher } from './reporting/loggingDispatcher';
23
+ import { PageViewTracker } from './pageViewTracker';
23
24
  export interface IErrorLogMessage {
24
25
  message?: string;
25
26
  name?: string;
@@ -44,6 +45,7 @@ export interface IMParticleWebSDKInstance extends MParticleWebSDK {
44
45
  _IdentityAPIClient: typeof IdentityAPIClient;
45
46
  _IntegrationCapture: IntegrationCapture;
46
47
  _NativeSdkHelpers: INativeSdkHelpers;
48
+ _PageViewTracker?: PageViewTracker;
47
49
  _Persistence: IPersistence;
48
50
  _CookieConsentManager: ICookieConsentManager;
49
51
  _ErrorReportingDispatcher: ErrorReportingDispatcher;
@@ -0,0 +1,21 @@
1
+ import { IMParticleWebSDKInstance } from './mp-instance';
2
+ export declare class PageViewTracker {
3
+ mpInstance: IMParticleWebSDKInstance;
4
+ private lastPath;
5
+ private isActive;
6
+ private originalPushState;
7
+ private originalReplaceState;
8
+ private pushStateWrapper;
9
+ private replaceStateWrapper;
10
+ private popStateListener;
11
+ constructor(mpInstance: IMParticleWebSDKInstance);
12
+ private isSupportedEnvironment;
13
+ init(): void;
14
+ private patchHistoryMethods;
15
+ private addNavigationListeners;
16
+ private safeHandleNavigation;
17
+ private getCurrentKey;
18
+ private handleNavigation;
19
+ private firePageView;
20
+ teardown(): void;
21
+ }
@@ -27,7 +27,8 @@ declare const replaceApostrophesWithQuotes: (value: string) => string;
27
27
  declare const replaceQuotesWithApostrophes: (value: string) => string;
28
28
  declare const replaceMPID: (value: string, mpid: MPID) => string;
29
29
  declare const replaceAmpWithAmpersand: (value: string) => string;
30
- declare const createCookieSyncUrl: (mpid: MPID, pixelUrl: string, redirectUrl?: string, domain?: string) => string;
30
+ declare const createCookieSyncUrl: (mpid: MPID, pixelUrl: string, redirectUrl?: string, domain?: string, base64Mpid?: string) => string;
31
+ declare const toWebSafeBase64: (value: string) => string;
31
32
  declare const returnConvertedBoolean: (data: string | boolean | number) => boolean;
32
33
  declare const decoded: (s: string) => string;
33
34
  declare const converted: (s: string) => string;
@@ -81,4 +82,4 @@ declare const obfuscateData: (value: any) => any;
81
82
  declare const obfuscateDevData: (data: any, isDevelopmentMode?: boolean) => any;
82
83
  declare function extend(...args: any[]): any;
83
84
  declare const getErrorMessage: (e: unknown, fallback?: string) => string;
84
- export { createCookieString, revertCookieString, createCookieSyncUrl, valueof, AttributeValue, converted, decoded, obfuscateDevData, filterDictionaryWithHash, findKeyInObject, generateDeprecationMessage, generateHash, generateUniqueId, getErrorMessage, getRampNumber, inArray, isObject, isStringOrNumber, obfuscateData, parseConfig, parseNumber, parseSettingsString, parseStringOrNumber, replaceCommasWithPipes, replacePipesWithCommas, replaceApostrophesWithQuotes, replaceQuotesWithApostrophes, returnConvertedBoolean, toDataPlanSlug, isString, isNumber, isFunction, isDataPlanSlug, isEmpty, isValidAttributeValue, isValidCustomFlagProperty, mergeObjects, moveElementToEnd, queryStringParser, getCookies, getHref, replaceMPID, replaceAmpWithAmpersand, extend, };
85
+ export { createCookieString, revertCookieString, createCookieSyncUrl, toWebSafeBase64, valueof, AttributeValue, converted, decoded, obfuscateDevData, filterDictionaryWithHash, findKeyInObject, generateDeprecationMessage, generateHash, generateUniqueId, getErrorMessage, getRampNumber, inArray, isObject, isStringOrNumber, obfuscateData, parseConfig, parseNumber, parseSettingsString, parseStringOrNumber, replaceCommasWithPipes, replacePipesWithCommas, replaceApostrophesWithQuotes, replaceQuotesWithApostrophes, returnConvertedBoolean, toDataPlanSlug, isString, isNumber, isFunction, isDataPlanSlug, isEmpty, isValidAttributeValue, isValidCustomFlagProperty, mergeObjects, moveElementToEnd, queryStringParser, getCookies, getHref, replaceMPID, replaceAmpWithAmpersand, extend, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mparticle/web-sdk",
3
- "version": "2.76.0",
3
+ "version": "2.78.0",
4
4
  "description": "mParticle core SDK for web applications",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -2,6 +2,7 @@ import {
2
2
  Dictionary,
3
3
  isEmpty,
4
4
  createCookieSyncUrl,
5
+ toWebSafeBase64,
5
6
  } from './utils';
6
7
  import Constants from './constants';
7
8
  import { MPID } from '@mparticle/web-sdk';
@@ -95,6 +96,7 @@ export default function CookieSyncManager(
95
96
  pixelUrl,
96
97
  redirectUrl,
97
98
  moduleId,
99
+ settings,
98
100
  // Tells you how often we should do a cookie sync (in days)
99
101
  frequencyCap,
100
102
  } = pixelSettings;
@@ -137,8 +139,17 @@ export default function CookieSyncManager(
137
139
  // It is optional but to simplify the code, we add it for all Trade
138
140
  // // Desk cookie syncs.
139
141
  const domain = moduleId === PARTNER_MODULE_IDS.TradeDesk ? window.location.hostname : undefined;
140
- // Add domain parameter for Trade Desk
141
- const fullUrl = createCookieSyncUrl(mpid, pixelUrl, redirectUrl, domain);
142
+
143
+ // Google Marketing Platform accepts a web-safe base64-encoded MPID via the
144
+ // `google_hm` query parameter, but only for accounts provisioned for Google
145
+ // Hosted Matching — Google returns errors otherwise. It is therefore gated
146
+ // behind the opt-in `enableHmTag` setting (off by default), which the server
147
+ // delivers as the string 'True' when the UI toggle is enabled.
148
+ const enableHmTag = settings?.enableHmTag?.toLowerCase() === 'true';
149
+ const isDoubleClickModule = moduleId === PARTNER_MODULE_IDS.DoubleclickDFP;
150
+ const base64Mpid = isDoubleClickModule && enableHmTag ? toWebSafeBase64(mpid) : undefined;
151
+
152
+ const fullUrl = createCookieSyncUrl(mpid, pixelUrl, redirectUrl, domain, base64Mpid);
142
153
 
143
154
  self.performCookieSync(
144
155
  fullUrl,
@@ -57,6 +57,7 @@ import { LoggingDispatcher } from './reporting/loggingDispatcher';
57
57
  import { IErrorReportingService, ILoggingService } from './reporting/types';
58
58
  import { logDeprecatedMethodUsage } from './reporting/deprecatedMethodLogger';
59
59
  import { normalizeRoktLauncherOptions } from './roktLauncherOptions';
60
+ import { PageViewTracker } from './pageViewTracker';
60
61
 
61
62
  export interface IErrorLogMessage {
62
63
  message?: string;
@@ -87,6 +88,7 @@ export interface IMParticleWebSDKInstance extends MParticleWebSDK {
87
88
  _IdentityAPIClient: typeof IdentityAPIClient;
88
89
  _IntegrationCapture: IntegrationCapture;
89
90
  _NativeSdkHelpers: INativeSdkHelpers;
91
+ _PageViewTracker?: PageViewTracker;
90
92
  _Persistence: IPersistence;
91
93
  _CookieConsentManager: ICookieConsentManager;
92
94
  _ErrorReportingDispatcher: ErrorReportingDispatcher;
@@ -1585,6 +1587,13 @@ function completeSDKInitialization(apiKey, config, mpInstance) {
1585
1587
 
1586
1588
  if (getFeatureFlag(AutoLogPageView)) {
1587
1589
  mpInstance._Events.logPageView();
1590
+
1591
+ if (!mpInstance._PageViewTracker) {
1592
+ mpInstance._PageViewTracker = new PageViewTracker(mpInstance);
1593
+ }
1594
+ mpInstance._PageViewTracker.init();
1595
+ } else if (mpInstance._PageViewTracker) {
1596
+ mpInstance._PageViewTracker.teardown();
1588
1597
  }
1589
1598
 
1590
1599
  processIdentityCallback(
@@ -0,0 +1,267 @@
1
+ import { IMParticleWebSDKInstance } from './mp-instance';
2
+ import { EventType, MessageType } from './types';
3
+
4
+ type HistoryStateMethod = History['pushState'];
5
+
6
+ const WRAPPED_MARKER = '__mpApvWrapped__';
7
+
8
+ type MarkedHistoryMethod = HistoryStateMethod & {
9
+ [WRAPPED_MARKER]?: boolean;
10
+ };
11
+
12
+ export class PageViewTracker {
13
+ mpInstance: IMParticleWebSDKInstance;
14
+
15
+ private lastPath: string | null = null;
16
+ private isActive = false;
17
+
18
+ private originalPushState: HistoryStateMethod | null = null;
19
+ private originalReplaceState: HistoryStateMethod | null = null;
20
+
21
+ private pushStateWrapper: HistoryStateMethod | null = null;
22
+ private replaceStateWrapper: HistoryStateMethod | null = null;
23
+
24
+ private popStateListener: (() => void) | null = null;
25
+
26
+ constructor(mpInstance: IMParticleWebSDKInstance) {
27
+ this.mpInstance = mpInstance;
28
+ }
29
+
30
+ private isSupportedEnvironment(): boolean {
31
+ return (
32
+ typeof window !== 'undefined' &&
33
+ typeof window.history !== 'undefined' &&
34
+ typeof window.history.pushState === 'function' &&
35
+ typeof window.addEventListener === 'function'
36
+ );
37
+ }
38
+
39
+ public init(): void {
40
+ this.mpInstance.Logger.verbose(
41
+ 'mParticle APV: [init] PageViewTracker Init'
42
+ );
43
+ if (!this.isSupportedEnvironment()) {
44
+ this.mpInstance.Logger.verbose(
45
+ 'mParticle APV: [init] unsupported environment (no History API), not starting'
46
+ );
47
+ return;
48
+ }
49
+
50
+ if (this.isActive) {
51
+ this.mpInstance.Logger.verbose(
52
+ 'mParticle APV: [init] starting (teardown-first for idempotency)'
53
+ );
54
+ this.teardown();
55
+ }
56
+
57
+ this.isActive = true;
58
+
59
+ this.lastPath = this.getCurrentKey();
60
+
61
+ this.mpInstance.Logger.verbose(
62
+ `mParticle APV: [init] seeded lastPath: ${this.lastPath}`
63
+ );
64
+
65
+ this.patchHistoryMethods();
66
+ this.addNavigationListeners();
67
+
68
+ this.mpInstance.Logger.verbose(
69
+ 'mParticle APV: [init] patched pushState/replaceState + listening for popstate'
70
+ );
71
+ }
72
+
73
+ private patchHistoryMethods(): void {
74
+ const self = this;
75
+
76
+ const installed = window.history.pushState as MarkedHistoryMethod;
77
+ if (installed[WRAPPED_MARKER]) {
78
+ this.mpInstance.Logger.verbose(
79
+ 'mParticle APV: [patch] history already wrapped, skipping to avoid double-wrap'
80
+ );
81
+ return;
82
+ }
83
+
84
+ const originalPushState = window.history.pushState;
85
+ const originalReplaceState = window.history.replaceState;
86
+ this.originalPushState = originalPushState;
87
+ this.originalReplaceState = originalReplaceState;
88
+
89
+ const pushStateWrapper = function(
90
+ this: History,
91
+ ...args: Parameters<HistoryStateMethod>
92
+ ): void {
93
+ const result = originalPushState.apply(this, args);
94
+ self.safeHandleNavigation('pushState');
95
+ return result;
96
+ };
97
+
98
+ const replaceStateWrapper = function(
99
+ this: History,
100
+ ...args: Parameters<HistoryStateMethod>
101
+ ): void {
102
+ const result = originalReplaceState.apply(this, args);
103
+ self.safeHandleNavigation('replaceState');
104
+ return result;
105
+ };
106
+
107
+ Object.defineProperty(pushStateWrapper, WRAPPED_MARKER, {
108
+ value: true,
109
+ enumerable: false,
110
+ });
111
+ Object.defineProperty(replaceStateWrapper, WRAPPED_MARKER, {
112
+ value: true,
113
+ enumerable: false,
114
+ });
115
+
116
+ this.pushStateWrapper = pushStateWrapper;
117
+ this.replaceStateWrapper = replaceStateWrapper;
118
+
119
+ try {
120
+ window.history.pushState = pushStateWrapper;
121
+ window.history.replaceState = replaceStateWrapper;
122
+ } catch (e) {
123
+ this.mpInstance.Logger.verbose(
124
+ `mParticle APV: [error] failed to patch history methods (frozen/sealed), rolling back: ${e}`
125
+ );
126
+ try {
127
+ if (window.history.pushState === pushStateWrapper) {
128
+ window.history.pushState = originalPushState;
129
+ }
130
+ if (window.history.replaceState === replaceStateWrapper) {
131
+ window.history.replaceState = originalReplaceState;
132
+ }
133
+ } catch (restoreError) {
134
+ this.mpInstance.Logger.verbose(
135
+ `mParticle APV: [error] failed to restore history methods after patch failure: ${restoreError}`
136
+ );
137
+ }
138
+ this.pushStateWrapper = null;
139
+ this.replaceStateWrapper = null;
140
+ this.originalPushState = null;
141
+ this.originalReplaceState = null;
142
+ }
143
+ }
144
+
145
+ private addNavigationListeners(): void {
146
+ this.popStateListener = () => this.safeHandleNavigation('popstate');
147
+ window.addEventListener('popstate', this.popStateListener);
148
+ }
149
+
150
+ private safeHandleNavigation(source: string): void {
151
+ try {
152
+ this.handleNavigation(source);
153
+ } catch (e) {
154
+ this.mpInstance.Logger.verbose(
155
+ `mParticle APV: [error] navigation handler threw (${source}), page view skipped: ${e}`
156
+ );
157
+ }
158
+ }
159
+
160
+ private getCurrentKey(): string {
161
+ return window.location.pathname;
162
+ }
163
+
164
+ private handleNavigation(source: string): void {
165
+ const candidatePath = this.getCurrentKey();
166
+
167
+ this.mpInstance.Logger.verbose(
168
+ `mParticle APV: [detect] navigation signal (source: ${source}, candidatePath: ${candidatePath}, lastPath: ${this.lastPath})`
169
+ );
170
+
171
+ if (candidatePath === this.lastPath) {
172
+ this.mpInstance.Logger.verbose(
173
+ `mParticle APV: [dedupe] pathname unchanged, skipping (source: ${source}, path: ${candidatePath})`
174
+ );
175
+ return;
176
+ }
177
+
178
+ this.mpInstance.Logger.verbose(
179
+ `mParticle APV: [accept] pathname changed, scheduling fire (source: ${source}, from: ${this.lastPath}, to: ${candidatePath})`
180
+ );
181
+ this.lastPath = candidatePath;
182
+
183
+ // Snapshot the path now: it is already settled at this point, and a
184
+ // same-tick navigation would otherwise overwrite window.location
185
+ // before the deferred flush reads it. The title is intentionally read
186
+ // later (in the deferred flush) so the router's post-navigation render
187
+ // commit has a chance to update document.title first.
188
+ const capturedPath = candidatePath;
189
+
190
+ setTimeout(() => {
191
+ if (!this.isActive) {
192
+ this.mpInstance.Logger.verbose(
193
+ 'mParticle APV: [defer] fire aborted, tracker inactive (torn down before flush)'
194
+ );
195
+ return;
196
+ }
197
+
198
+ this.mpInstance._SessionManager.resetSessionTimer();
199
+ this.firePageView(capturedPath);
200
+ }, 0);
201
+ }
202
+
203
+ // Mirrors the event shape of the public mParticle.logPageView(), but carries
204
+ // the captured SPA path rather than reading the live location.
205
+ private firePageView(path: string): void {
206
+ const title = window.document.title;
207
+
208
+ this.mpInstance.Logger.verbose(
209
+ `mParticle APV: [fire] deferred flush -> _Events.logEvent(PageView) (path: ${path}, title: ${title})`
210
+ );
211
+
212
+ this.mpInstance._Events.logEvent({
213
+ messageType: MessageType.PageView,
214
+ name: 'PageView',
215
+ data: {
216
+ hostname: window.location.hostname,
217
+ title,
218
+ path,
219
+ },
220
+ eventType: EventType.Unknown,
221
+ });
222
+ }
223
+
224
+ public teardown(): void {
225
+ if (this.popStateListener) {
226
+ window.removeEventListener('popstate', this.popStateListener);
227
+ this.popStateListener = null;
228
+ }
229
+ const pushStateStillOurs =
230
+ this.pushStateWrapper !== null &&
231
+ window.history.pushState === this.pushStateWrapper;
232
+ if (this.originalPushState) {
233
+ if (pushStateStillOurs) {
234
+ window.history.pushState = this.originalPushState;
235
+ this.mpInstance.Logger.verbose(
236
+ 'mParticle APV: [teardown] restored original pushState'
237
+ );
238
+ } else {
239
+ this.mpInstance.Logger.verbose(
240
+ 'mParticle APV: [teardown] pushState no longer ours; leaving in place, gating callback to no-op'
241
+ );
242
+ }
243
+ this.originalPushState = null;
244
+ this.pushStateWrapper = null;
245
+ }
246
+
247
+ const replaceStateStillOurs =
248
+ this.replaceStateWrapper !== null &&
249
+ window.history.replaceState === this.replaceStateWrapper;
250
+ if (this.originalReplaceState) {
251
+ if (replaceStateStillOurs) {
252
+ window.history.replaceState = this.originalReplaceState;
253
+ this.mpInstance.Logger.verbose(
254
+ 'mParticle APV: [teardown] restored original replaceState'
255
+ );
256
+ } else {
257
+ this.mpInstance.Logger.verbose(
258
+ 'mParticle APV: [teardown] replaceState no longer ours; leaving in place, gating callback to no-op'
259
+ );
260
+ }
261
+ this.originalReplaceState = null;
262
+ this.replaceStateWrapper = null;
263
+ }
264
+
265
+ this.isActive = false;
266
+ }
267
+ }
package/src/utils.ts CHANGED
@@ -207,7 +207,8 @@ const createCookieSyncUrl = (
207
207
  mpid: MPID,
208
208
  pixelUrl: string,
209
209
  redirectUrl?: string,
210
- domain?: string
210
+ domain?: string,
211
+ base64Mpid?: string
211
212
  ): string => {
212
213
  const modifiedPixelUrl = replaceAmpWithAmpersand(pixelUrl);
213
214
  const modifiedDirectUrl = redirectUrl
@@ -215,21 +216,39 @@ const createCookieSyncUrl = (
215
216
  : null;
216
217
 
217
218
  let url = replaceMPID(modifiedPixelUrl, mpid);
218
-
219
+
219
220
  const redirect = modifiedDirectUrl
220
221
  ? replaceMPID(modifiedDirectUrl, mpid)
221
222
  : '';
222
-
223
+
223
224
  let fullUrl = url + encodeURIComponent(redirect);
224
-
225
+
225
226
  if (domain) {
226
227
  const separator = fullUrl.includes('?') ? '&' : '?';
227
228
  fullUrl += `${separator}domain=${domain}`;
228
229
  }
229
-
230
+
231
+ if (base64Mpid) {
232
+ const separator = fullUrl.includes('?') ? '&' : '?';
233
+ fullUrl += `${separator}google_hm=${base64Mpid}`;
234
+ }
235
+
230
236
  return fullUrl;
231
237
  };
232
238
 
239
+ // Google's cookie matching service requires `google_hm` values to be web-safe
240
+ // base64 (RFC 4648 §5) without padding, otherwise the write to the hosted
241
+ // match table fails: https://developers.google.com/authorized-buyers/rtb/cookie-guide
242
+ // (String#replaceAll is not available in our ES5 browser targets.)
243
+ const WEB_SAFE_BASE64_REPLACEMENTS: Dictionary<string> = {
244
+ '+': '-',
245
+ '/': '_',
246
+ '=': '',
247
+ };
248
+
249
+ const toWebSafeBase64 = (value: string): string =>
250
+ btoa(value).replace(/[+/=]/g, c => WEB_SAFE_BASE64_REPLACEMENTS[c]);
251
+
233
252
  // FIXME: REFACTOR for V3
234
253
  // only used in store.js to sanitize server-side formatting of
235
254
  // booleans when checking for `isDevelopmentMode`
@@ -650,6 +669,7 @@ export {
650
669
  createCookieString,
651
670
  revertCookieString,
652
671
  createCookieSyncUrl,
672
+ toWebSafeBase64,
653
673
  valueof,
654
674
  AttributeValue,
655
675
  converted,