diff --git a/CHANGELOG.md b/CHANGELOG.md index 317eb37b4..b55276323 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## [2.50.1](https://github.com/mParticle/mparticle-web-sdk/compare/v2.50.0...v2.50.1) (2025-11-20) + # [2.50.0](https://github.com/mParticle/mparticle-web-sdk/compare/v2.49.0...v2.50.0) (2025-11-13) diff --git a/dist/mparticle.common.js b/dist/mparticle.common.js index 0fa140b4e..af3406f14 100644 --- a/dist/mparticle.common.js +++ b/dist/mparticle.common.js @@ -15,7 +15,7 @@ map:function map(a,b){var c,d,e;if(null===this)throw new TypeError(" this is nul 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=a.MINIMUM_INTERVAL_MILLIS,this.uploadIntervalMillis=a.MINIMUM_INTERVAL_MILLIS,this.uploadIntervalMillisc?void d.currentSessionMPIDs.push(a):void(0<=c&&(d.currentSessionMPIDs=moveElementToEnd(d.currentSessionMPIDs,c)))},this.nullifySession=function(){d.sessionId=null,d.dateLastEventSent=null,d.sessionAttributes={},d.localSessionAttributes={},b._Persistence.update();},this.processConfig=function(a){var g,h=a.workspaceToken,i=a.requiredWebviewBridgeName;d.SDKConfig.flags=processFlags(a);var j=processBaseUrls(a,d.SDKConfig.flags,c);for(var k in j)d.SDKConfig[k]=j[k];var l=null!==(g=null===a||void 0===a?void 0:a.launcherOptions)&&void 0!==g?g:{},m=l.noFunctional,n=l.noTargeting;null!=m&&d.setNoFunctional(m),null!=n&&d.setNoTargeting(n),h?(d.SDKConfig.workspaceToken=h,!d.getPrivacyFlag("TimeOnSite")&&(b._timeOnSiteTimer=new ForegroundTimeTracker(h))):b.Logger.warning("You should have a workspaceToken on your config object for security purposes."),d.storageName=e(h),d.SDKConfig.requiredWebviewBridgeName=i||h,d.webviewBridgeEnabled=f(d.SDKConfig.requiredWebviewBridgeName,d.SDKConfig.minWebviewBridgeVersion),d.configurationLoaded=!0;};}// https://go.mparticle.com/work/SQDSDKS-6317 +(c||null===c)&&d._setPersistence(a,"con",e(c));},this.getDeviceId=function(){return d.deviceId},this.setDeviceId=function(a){d.deviceId=a,d.persistenceData.gs.das=a,b._Persistence.update();},this.getFirstSeenTime=function(a){return d._getFromPersistence(a,"fst")},this.setFirstSeenTime=function(a,b){if(a){var c=b||new Date().getTime();d._setPersistence(a,"fst",c);}},this.getLastSeenTime=function(a){if(!a)return null;// https://go.mparticle.com/work/SQDSDKS-6315 +var c=b.Identity.getCurrentUser();return a===(null===c||void 0===c?void 0:c.getMPID())?new Date().getTime():d._getFromPersistence(a,"lst")},this.setLastSeenTime=function(a,b){if(a){var c=b||new Date().getTime();d._setPersistence(a,"lst",c);}},this.getLocalSessionAttributes=function(){return d.localSessionAttributes||{}},this.setLocalSessionAttribute=function(a,c){var e;d.localSessionAttributes[a]=c,d.persistenceData.gs.lsa=__assign(__assign({},d.persistenceData.gs.lsa||{}),(e={},e[a]=c,e)),b._Persistence.savePersistence(d.persistenceData);},this.syncPersistenceData=function(){var a=b._Persistence.getPersistence();d.persistenceData=b._Helpers.extend({},d.persistenceData,a);},this.getUserAttributes=function(a){return d._getFromPersistence(a,"ua")||{}},this.setUserAttributes=function(a,b){return d._setPersistence(a,"ua",b)},this.getUserIdentities=function(a){return d._getFromPersistence(a,"ui")||{}},this.setUserIdentities=function(a,b){d._setPersistence(a,"ui",b);},this.addMpidToSessionHistory=function(a,b){var c=d.currentSessionMPIDs.indexOf(a);return a&&b!==a&&0>c?void d.currentSessionMPIDs.push(a):void(0<=c&&(d.currentSessionMPIDs=moveElementToEnd(d.currentSessionMPIDs,c)))},this.nullifySession=function(){d.sessionId=null,d.dateLastEventSent=null,d.sessionAttributes={},d.localSessionAttributes={},b._Persistence.update();},this.processConfig=function(a){var g=a.workspaceToken,h=a.requiredWebviewBridgeName;// We should reprocess the flags and baseUrls in case they have changed when we request an updated config +// such as if the SDK is being self-hosted and the flags are different on the server config +// https://go.mparticle.com/work/SQDSDKS-6317 +d.SDKConfig.flags=processFlags(a);var i=processBaseUrls(a,d.SDKConfig.flags,c);for(var j in i)d.SDKConfig[j]=i[j];g?(d.SDKConfig.workspaceToken=g,b._timeOnSiteTimer=new ForegroundTimeTracker(g)):b.Logger.warning("You should have a workspaceToken on your config object for security purposes."),d.storageName=e(g),d.SDKConfig.requiredWebviewBridgeName=h||g,d.webviewBridgeEnabled=f(d.SDKConfig.requiredWebviewBridgeName,d.SDKConfig.minWebviewBridgeVersion),d.configurationLoaded=!0;};}// https://go.mparticle.com/work/SQDSDKS-6317 function processFlags(a){var b={},c=Constants.FeatureFlags,d=c.ReportBatching,e=c.EventBatchingIntervalMillis,f=c.OfflineStorage,g=c.DirectUrlRouting,h=c.CacheIdentity,i=c.AudienceAPI,j=c.CaptureIntegrationSpecificIds,k=c.CaptureIntegrationSpecificIdsV2,l=c.AstBackgroundEvents;return a.flags?(b[d]=a.flags[d]||!1,b[e]=parseNumber(a.flags[e])||Constants.DefaultConfig.uploadInterval,b[f]=a.flags[f]||"0",b[g]="True"===a.flags[g],b[h]="True"===a.flags[h],b[i]="True"===a.flags[i],b[j]="True"===a.flags[j],b[k]=a.flags[k]||"none",b[l]="True"===a.flags[l],b):{};// https://go.mparticle.com/work/SQDSDKS-6317 // Passed in config flags take priority over defaults }function processBaseUrls(a,b,c){// an API key is not present in a webview only mode. In this case, no baseUrls are needed @@ -629,16 +632,15 @@ var Base64=Polyfill.Base64,Messages$4=Constants.Messages,Base64CookieKeys=Consta // Forwarder Batching Code this.useLocalStorage=function(){return !a._Store.SDKConfig.useCookieStorage&&a._Store.isLocalStorageAvailable},this.initializeStorage=function(){try{var b,c,d=f.getLocalStorage(),e=f.getCookie();// https://go.mparticle.com/work/SQDSDKS-6045 // Determine if there is any data in cookies or localStorage to figure out if it is the first time the browser is loading mParticle -if(d||e?a._Store.isFirstRun=!1:(a._Store.isFirstRun=!0,a._Store.mpid=0),a._Store.getPrivacyFlag("SDKState"))return;// https://go.mparticle.com/work/SQDSDKS-6045 // https://go.mparticle.com/work/SQDSDKS-6046 // Stores all non-current user MPID information into the store -for(var g in a._Store.isLocalStorageAvailable||(a._Store.SDKConfig.useCookieStorage=!0),a._Store.isLocalStorageAvailable?(b=window.localStorage,a._Store.SDKConfig.useCookieStorage?(d?(c=e?a._Helpers.extend(!1,d,e):d,b.removeItem(a._Store.storageName)):e&&(c=e),f.storeDataInMemory(c)):e?(c=d?a._Helpers.extend(!1,d,e):e,f.storeDataInMemory(c),f.expireCookies(a._Store.storageName)):f.storeDataInMemory(d)):f.storeDataInMemory(e),c)c.hasOwnProperty(g)&&(SDKv2NonMPIDCookieKeys[g]||(a._Store.nonCurrentUserMPIDs[g]=c[g]));f.update();}catch(b){f.useLocalStorage()&&a._Store.isLocalStorageAvailable?localStorage.removeItem(a._Store.storageName):f.expireCookies(a._Store.storageName),a.Logger.error("Error initializing storage: "+b);}},this.update=function(){a._Store.webviewBridgeEnabled||a._Store.getPrivacyFlag("SDKState")||(a._Store.SDKConfig.useCookieStorage&&f.setCookie(),f.setLocalStorage());},this.storeDataInMemory=function(b,c){try{b?(a._Store.mpid=c?c:b.cu||0,b.gs=b.gs||{},a._Store.sessionId=b.gs.sid||a._Store.sessionId,a._Store.isEnabled="undefined"==typeof b.gs.ie?a._Store.isEnabled:b.gs.ie,a._Store.sessionAttributes=b.gs.sa||a._Store.sessionAttributes,a._Store.localSessionAttributes=b.gs.lsa||a._Store.localSessionAttributes,a._Store.serverSettings=b.gs.ss||a._Store.serverSettings,a._Store.devToken=a._Store.devToken||b.gs.dt,a._Store.SDKConfig.appVersion=a._Store.SDKConfig.appVersion||b.gs.av,a._Store.clientId=b.gs.cgid||a._Store.clientId||a._Helpers.generateUniqueId(),a._Store.deviceId=a._Store.deviceId||b.gs.das||a._Helpers.generateUniqueId(),a._Store.integrationAttributes=b.gs.ia||{},a._Store.context=b.gs.c||a._Store.context,a._Store.currentSessionMPIDs=b.gs.csm||a._Store.currentSessionMPIDs,a._Store.isLoggedIn=!0===b.l,b.gs.les&&(a._Store.dateLastEventSent=new Date(b.gs.les)),a._Store.sessionStartDate=b.gs.ssd?new Date(b.gs.ssd):new Date,b=c?b[c]:b[b.cu]):(a.Logger.verbose(Messages$4.InformationMessages.CookieNotFound),a._Store.clientId=a._Store.clientId||a._Helpers.generateUniqueId(),a._Store.deviceId=a._Store.deviceId||a._Helpers.generateUniqueId());}catch(b){a.Logger.error(Messages$4.ErrorMessages.CookieParseError);}},this.determineLocalStorageAvailability=function(a){var b;window.mParticle&&window.mParticle._forceNoLocalStorage&&(a=void 0);try{return a.setItem("mparticle","test"),b="test"===a.getItem("mparticle"),a.removeItem("mparticle"),b&&a}catch(a){return !1}},this.setLocalStorage=function(){if(a._Store.isLocalStorageAvailable){var c=a._Store.storageName,d=f.getLocalStorage()||{},e=a.Identity.getCurrentUser(),g=e?e.getMPID():null;if(!a._Store.SDKConfig.useCookieStorage){d.gs=d.gs||{},d.l=a._Store.isLoggedIn?1:0,a._Store.sessionId&&(d.gs.csm=a._Store.currentSessionMPIDs),d.gs.ie=a._Store.isEnabled,g&&(d.cu=g),Object.keys(a._Store.nonCurrentUserMPIDs).length&&(d=a._Helpers.extend({},d,a._Store.nonCurrentUserMPIDs),a._Store.nonCurrentUserMPIDs={}),d=b(d);try{window.localStorage.setItem(encodeURIComponent(c),f.encodePersistence(JSON.stringify(d)));}catch(b){a.Logger.error("Error with setting localStorage item.");}}}},this.getLocalStorage=function(){if(!a._Store.isLocalStorageAvailable)return null;var b,c=a._Store.storageName,d=f.decodePersistence(window.localStorage.getItem(c)),e={};if(d)for(b in d=JSON.parse(d),d)d.hasOwnProperty(b)&&(e[b]=d[b]);return Object.keys(e).length?e:null},this.expireCookies=function(a){var b,c,d,e=new Date;d=f.getCookieDomain(),c=""===d?"":";domain="+d,e.setTime(e.getTime()-86400000),b="; expires="+e.toUTCString(),document.cookie=a+"="+b+"; path=/"+c;},this.getCookie=function(){var b,c,d,e,g,h,j=a._Store.storageName,k=j?void 0:{};a.Logger.verbose(Messages$4.InformationMessages.CookieSearch);try{b=window.document.cookie.split("; ");}catch(b){return a.Logger.verbose("Unable to parse undefined cookie"),null}for(c=0,d=b.length;cf&&!SDKv2NonMPIDCookieKeys[j]&&j!==b.cu&&delete b[j]);else {// Comment 2 above - First create an object of all MPIDs on the cookie var k={};for(var l in b)b.hasOwnProperty(l)&&(SDKv2NonMPIDCookieKeys[l]||l===b.cu||(k[l]=1));// Comment 2a above if(Object.keys(k).length)for(var m in k)g=d(b,c,e),g.length>f&&k.hasOwnProperty(m)&&-1===h.indexOf(m)&&delete b[m];// Comment 2b above for(var n,o=0;of);o++)n=h[o],b[n]?(a.Logger.verbose("Size of new encoded cookie is larger than maxCookieSize setting of "+f+". Removing from cookie the earliest logged in MPID containing: "+JSON.stringify(b[n],0,2)),delete b[n]):a.Logger.error("Unable to save MPID data to cookies because the resulting encoded cookie is larger than the maxCookieSize setting of "+f+". We recommend using a maxCookieSize of 1500.");}return g},this.findPrevCookiesBasedOnUI=function(b){var c,d=a._Persistence.getPersistence();if(b)for(var e in b.userIdentities)if(d&&Object.keys(d).length)for(var g in d)// any value in persistence that has an MPID key will be an MPID to search through // other keys on the cookie are currentSessionMPIDs and currentMPID which should not be searched -if(d[g].mpid){var h=d[g].ui;for(var i in h)if(e===i&&b.userIdentities[e]===h[i]){c=g;break}}c&&f.storeDataInMemory(d,c);},this.encodePersistence=function(b){for(var c in b=JSON.parse(b),b.gs)b.gs.hasOwnProperty(c)&&(Base64CookieKeys[c]?b.gs[c]?Array.isArray(b.gs[c])&&b.gs[c].length||a._Helpers.isObject(b.gs[c])&&Object.keys(b.gs[c]).length?b.gs[c]=Base64.encode(JSON.stringify(b.gs[c])):delete b.gs[c]:delete b.gs[c]:"ie"===c?b.gs[c]=b.gs[c]?1:0:!b.gs[c]&&delete b.gs[c]);for(var d in b)if(b.hasOwnProperty(d)&&!SDKv2NonMPIDCookieKeys[d])for(c in b[d])b[d].hasOwnProperty(c)&&Base64CookieKeys[c]&&(a._Helpers.isObject(b[d][c])&&Object.keys(b[d][c]).length?b[d][c]=Base64.encode(JSON.stringify(b[d][c])):delete b[d][c]);return createCookieString(JSON.stringify(b))},this.decodePersistence=function(b){try{if(b){if(b=JSON.parse(revertCookieString(b)),a._Helpers.isObject(b)&&Object.keys(b).length){for(var c in b.gs)b.gs.hasOwnProperty(c)&&(Base64CookieKeys[c]?b.gs[c]=JSON.parse(Base64.decode(b.gs[c])):"ie"===c&&(b.gs[c]=!!b.gs[c]));for(var d in b)if(b.hasOwnProperty(d))if(!SDKv2NonMPIDCookieKeys[d])for(c in b[d])b[d].hasOwnProperty(c)&&Base64CookieKeys[c]&&b[d][c].length&&(b[d][c]=JSON.parse(Base64.decode(b[d][c])));else "l"===d&&(b[d]=!!b[d]);}return JSON.stringify(b)}}catch(b){a.Logger.error("Problem with decoding cookie",b);}},this.getCookieDomain=function(){if(a._Store.SDKConfig.cookieDomain)return a._Store.SDKConfig.cookieDomain;var b=f.getDomain(document,location.hostname);return ""===b?"":"."+b},this.getDomain=function(a,b){var c,d,e=b.split(".");for(c=e.length-1;0<=c;c--)if(d=e.slice(c).join("."),a.cookie="mptest=cookie;domain=."+d+";",-1>>0;if("function"!=typeof a)throw new TypeError;for(var d=[],e=2<=arguments.length?arguments[1]:void 0,f=0;f=a.MINIMUM_INTERVAL_MILLIS,this.uploadIntervalMillis=a.MINIMUM_INTERVAL_MILLIS,this.uploadIntervalMillisc?void d.currentSessionMPIDs.push(a):void(0<=c&&(d.currentSessionMPIDs=moveElementToEnd(d.currentSessionMPIDs,c)))},this.nullifySession=function(){d.sessionId=null,d.dateLastEventSent=null,d.sessionAttributes={},d.localSessionAttributes={},b._Persistence.update();},this.processConfig=function(a){var g,h=a.workspaceToken,i=a.requiredWebviewBridgeName;d.SDKConfig.flags=processFlags(a);var j=processBaseUrls(a,d.SDKConfig.flags,c);for(var k in j)d.SDKConfig[k]=j[k];var l=null!==(g=null===a||void 0===a?void 0:a.launcherOptions)&&void 0!==g?g:{},m=l.noFunctional,n=l.noTargeting;null!=m&&d.setNoFunctional(m),null!=n&&d.setNoTargeting(n),h?(d.SDKConfig.workspaceToken=h,!d.getPrivacyFlag("TimeOnSite")&&(b._timeOnSiteTimer=new ForegroundTimeTracker(h))):b.Logger.warning("You should have a workspaceToken on your config object for security purposes."),d.storageName=e(h),d.SDKConfig.requiredWebviewBridgeName=i||h,d.webviewBridgeEnabled=f(d.SDKConfig.requiredWebviewBridgeName,d.SDKConfig.minWebviewBridgeVersion),d.configurationLoaded=!0;};}// https://go.mparticle.com/work/SQDSDKS-6317 +(c||null===c)&&d._setPersistence(a,"con",e(c));},this.getDeviceId=function(){return d.deviceId},this.setDeviceId=function(a){d.deviceId=a,d.persistenceData.gs.das=a,b._Persistence.update();},this.getFirstSeenTime=function(a){return d._getFromPersistence(a,"fst")},this.setFirstSeenTime=function(a,b){if(a){var c=b||new Date().getTime();d._setPersistence(a,"fst",c);}},this.getLastSeenTime=function(a){if(!a)return null;// https://go.mparticle.com/work/SQDSDKS-6315 +var c=b.Identity.getCurrentUser();return a===(null===c||void 0===c?void 0:c.getMPID())?new Date().getTime():d._getFromPersistence(a,"lst")},this.setLastSeenTime=function(a,b){if(a){var c=b||new Date().getTime();d._setPersistence(a,"lst",c);}},this.getLocalSessionAttributes=function(){return d.localSessionAttributes||{}},this.setLocalSessionAttribute=function(a,c){var e;d.localSessionAttributes[a]=c,d.persistenceData.gs.lsa=__assign(__assign({},d.persistenceData.gs.lsa||{}),(e={},e[a]=c,e)),b._Persistence.savePersistence(d.persistenceData);},this.syncPersistenceData=function(){var a=b._Persistence.getPersistence();d.persistenceData=b._Helpers.extend({},d.persistenceData,a);},this.getUserAttributes=function(a){return d._getFromPersistence(a,"ua")||{}},this.setUserAttributes=function(a,b){return d._setPersistence(a,"ua",b)},this.getUserIdentities=function(a){return d._getFromPersistence(a,"ui")||{}},this.setUserIdentities=function(a,b){d._setPersistence(a,"ui",b);},this.addMpidToSessionHistory=function(a,b){var c=d.currentSessionMPIDs.indexOf(a);return a&&b!==a&&0>c?void d.currentSessionMPIDs.push(a):void(0<=c&&(d.currentSessionMPIDs=moveElementToEnd(d.currentSessionMPIDs,c)))},this.nullifySession=function(){d.sessionId=null,d.dateLastEventSent=null,d.sessionAttributes={},d.localSessionAttributes={},b._Persistence.update();},this.processConfig=function(a){var g=a.workspaceToken,h=a.requiredWebviewBridgeName;// We should reprocess the flags and baseUrls in case they have changed when we request an updated config +// such as if the SDK is being self-hosted and the flags are different on the server config +// https://go.mparticle.com/work/SQDSDKS-6317 +d.SDKConfig.flags=processFlags(a);var i=processBaseUrls(a,d.SDKConfig.flags,c);for(var j in i)d.SDKConfig[j]=i[j];g?(d.SDKConfig.workspaceToken=g,b._timeOnSiteTimer=new ForegroundTimeTracker(g)):b.Logger.warning("You should have a workspaceToken on your config object for security purposes."),d.storageName=e(g),d.SDKConfig.requiredWebviewBridgeName=h||g,d.webviewBridgeEnabled=f(d.SDKConfig.requiredWebviewBridgeName,d.SDKConfig.minWebviewBridgeVersion),d.configurationLoaded=!0;};}// https://go.mparticle.com/work/SQDSDKS-6317 function processFlags(a){var b={},c=Constants.FeatureFlags,d=c.ReportBatching,e=c.EventBatchingIntervalMillis,f=c.OfflineStorage,g=c.DirectUrlRouting,h=c.CacheIdentity,i=c.AudienceAPI,j=c.CaptureIntegrationSpecificIds,k=c.CaptureIntegrationSpecificIdsV2,l=c.AstBackgroundEvents;return a.flags?(b[d]=a.flags[d]||!1,b[e]=parseNumber(a.flags[e])||Constants.DefaultConfig.uploadInterval,b[f]=a.flags[f]||"0",b[g]="True"===a.flags[g],b[h]="True"===a.flags[h],b[i]="True"===a.flags[i],b[j]="True"===a.flags[j],b[k]=a.flags[k]||"none",b[l]="True"===a.flags[l],b):{};// https://go.mparticle.com/work/SQDSDKS-6317 // Passed in config flags take priority over defaults }function processBaseUrls(a,b,c){// an API key is not present in a webview only mode. In this case, no baseUrls are needed @@ -629,16 +632,15 @@ var Base64=Polyfill.Base64,Messages$4=Constants.Messages,Base64CookieKeys=Consta // Forwarder Batching Code this.useLocalStorage=function(){return !a._Store.SDKConfig.useCookieStorage&&a._Store.isLocalStorageAvailable},this.initializeStorage=function(){try{var b,c,d=f.getLocalStorage(),e=f.getCookie();// https://go.mparticle.com/work/SQDSDKS-6045 // Determine if there is any data in cookies or localStorage to figure out if it is the first time the browser is loading mParticle -if(d||e?a._Store.isFirstRun=!1:(a._Store.isFirstRun=!0,a._Store.mpid=0),a._Store.getPrivacyFlag("SDKState"))return;// https://go.mparticle.com/work/SQDSDKS-6045 // https://go.mparticle.com/work/SQDSDKS-6046 // Stores all non-current user MPID information into the store -for(var g in a._Store.isLocalStorageAvailable||(a._Store.SDKConfig.useCookieStorage=!0),a._Store.isLocalStorageAvailable?(b=window.localStorage,a._Store.SDKConfig.useCookieStorage?(d?(c=e?a._Helpers.extend(!1,d,e):d,b.removeItem(a._Store.storageName)):e&&(c=e),f.storeDataInMemory(c)):e?(c=d?a._Helpers.extend(!1,d,e):e,f.storeDataInMemory(c),f.expireCookies(a._Store.storageName)):f.storeDataInMemory(d)):f.storeDataInMemory(e),c)c.hasOwnProperty(g)&&(SDKv2NonMPIDCookieKeys[g]||(a._Store.nonCurrentUserMPIDs[g]=c[g]));f.update();}catch(b){f.useLocalStorage()&&a._Store.isLocalStorageAvailable?localStorage.removeItem(a._Store.storageName):f.expireCookies(a._Store.storageName),a.Logger.error("Error initializing storage: "+b);}},this.update=function(){a._Store.webviewBridgeEnabled||a._Store.getPrivacyFlag("SDKState")||(a._Store.SDKConfig.useCookieStorage&&f.setCookie(),f.setLocalStorage());},this.storeDataInMemory=function(b,c){try{b?(a._Store.mpid=c?c:b.cu||0,b.gs=b.gs||{},a._Store.sessionId=b.gs.sid||a._Store.sessionId,a._Store.isEnabled="undefined"==typeof b.gs.ie?a._Store.isEnabled:b.gs.ie,a._Store.sessionAttributes=b.gs.sa||a._Store.sessionAttributes,a._Store.localSessionAttributes=b.gs.lsa||a._Store.localSessionAttributes,a._Store.serverSettings=b.gs.ss||a._Store.serverSettings,a._Store.devToken=a._Store.devToken||b.gs.dt,a._Store.SDKConfig.appVersion=a._Store.SDKConfig.appVersion||b.gs.av,a._Store.clientId=b.gs.cgid||a._Store.clientId||a._Helpers.generateUniqueId(),a._Store.deviceId=a._Store.deviceId||b.gs.das||a._Helpers.generateUniqueId(),a._Store.integrationAttributes=b.gs.ia||{},a._Store.context=b.gs.c||a._Store.context,a._Store.currentSessionMPIDs=b.gs.csm||a._Store.currentSessionMPIDs,a._Store.isLoggedIn=!0===b.l,b.gs.les&&(a._Store.dateLastEventSent=new Date(b.gs.les)),a._Store.sessionStartDate=b.gs.ssd?new Date(b.gs.ssd):new Date,b=c?b[c]:b[b.cu]):(a.Logger.verbose(Messages$4.InformationMessages.CookieNotFound),a._Store.clientId=a._Store.clientId||a._Helpers.generateUniqueId(),a._Store.deviceId=a._Store.deviceId||a._Helpers.generateUniqueId());}catch(b){a.Logger.error(Messages$4.ErrorMessages.CookieParseError);}},this.determineLocalStorageAvailability=function(a){var b;window.mParticle&&window.mParticle._forceNoLocalStorage&&(a=void 0);try{return a.setItem("mparticle","test"),b="test"===a.getItem("mparticle"),a.removeItem("mparticle"),b&&a}catch(a){return !1}},this.setLocalStorage=function(){if(a._Store.isLocalStorageAvailable){var c=a._Store.storageName,d=f.getLocalStorage()||{},e=a.Identity.getCurrentUser(),g=e?e.getMPID():null;if(!a._Store.SDKConfig.useCookieStorage){d.gs=d.gs||{},d.l=a._Store.isLoggedIn?1:0,a._Store.sessionId&&(d.gs.csm=a._Store.currentSessionMPIDs),d.gs.ie=a._Store.isEnabled,g&&(d.cu=g),Object.keys(a._Store.nonCurrentUserMPIDs).length&&(d=a._Helpers.extend({},d,a._Store.nonCurrentUserMPIDs),a._Store.nonCurrentUserMPIDs={}),d=b(d);try{window.localStorage.setItem(encodeURIComponent(c),f.encodePersistence(JSON.stringify(d)));}catch(b){a.Logger.error("Error with setting localStorage item.");}}}},this.getLocalStorage=function(){if(!a._Store.isLocalStorageAvailable)return null;var b,c=a._Store.storageName,d=f.decodePersistence(window.localStorage.getItem(c)),e={};if(d)for(b in d=JSON.parse(d),d)d.hasOwnProperty(b)&&(e[b]=d[b]);return Object.keys(e).length?e:null},this.expireCookies=function(a){var b,c,d,e=new Date;d=f.getCookieDomain(),c=""===d?"":";domain="+d,e.setTime(e.getTime()-86400000),b="; expires="+e.toUTCString(),document.cookie=a+"="+b+"; path=/"+c;},this.getCookie=function(){var b,c,d,e,g,h,j=a._Store.storageName,k=j?void 0:{};a.Logger.verbose(Messages$4.InformationMessages.CookieSearch);try{b=window.document.cookie.split("; ");}catch(b){return a.Logger.verbose("Unable to parse undefined cookie"),null}for(c=0,d=b.length;cf&&!SDKv2NonMPIDCookieKeys[j]&&j!==b.cu&&delete b[j]);else {// Comment 2 above - First create an object of all MPIDs on the cookie var k={};for(var l in b)b.hasOwnProperty(l)&&(SDKv2NonMPIDCookieKeys[l]||l===b.cu||(k[l]=1));// Comment 2a above if(Object.keys(k).length)for(var m in k)g=d(b,c,e),g.length>f&&k.hasOwnProperty(m)&&-1===h.indexOf(m)&&delete b[m];// Comment 2b above for(var n,o=0;of);o++)n=h[o],b[n]?(a.Logger.verbose("Size of new encoded cookie is larger than maxCookieSize setting of "+f+". Removing from cookie the earliest logged in MPID containing: "+JSON.stringify(b[n],0,2)),delete b[n]):a.Logger.error("Unable to save MPID data to cookies because the resulting encoded cookie is larger than the maxCookieSize setting of "+f+". We recommend using a maxCookieSize of 1500.");}return g},this.findPrevCookiesBasedOnUI=function(b){var c,d=a._Persistence.getPersistence();if(b)for(var e in b.userIdentities)if(d&&Object.keys(d).length)for(var g in d)// any value in persistence that has an MPID key will be an MPID to search through // other keys on the cookie are currentSessionMPIDs and currentMPID which should not be searched -if(d[g].mpid){var h=d[g].ui;for(var i in h)if(e===i&&b.userIdentities[e]===h[i]){c=g;break}}c&&f.storeDataInMemory(d,c);},this.encodePersistence=function(b){for(var c in b=JSON.parse(b),b.gs)b.gs.hasOwnProperty(c)&&(Base64CookieKeys[c]?b.gs[c]?Array.isArray(b.gs[c])&&b.gs[c].length||a._Helpers.isObject(b.gs[c])&&Object.keys(b.gs[c]).length?b.gs[c]=Base64.encode(JSON.stringify(b.gs[c])):delete b.gs[c]:delete b.gs[c]:"ie"===c?b.gs[c]=b.gs[c]?1:0:!b.gs[c]&&delete b.gs[c]);for(var d in b)if(b.hasOwnProperty(d)&&!SDKv2NonMPIDCookieKeys[d])for(c in b[d])b[d].hasOwnProperty(c)&&Base64CookieKeys[c]&&(a._Helpers.isObject(b[d][c])&&Object.keys(b[d][c]).length?b[d][c]=Base64.encode(JSON.stringify(b[d][c])):delete b[d][c]);return createCookieString(JSON.stringify(b))},this.decodePersistence=function(b){try{if(b){if(b=JSON.parse(revertCookieString(b)),a._Helpers.isObject(b)&&Object.keys(b).length){for(var c in b.gs)b.gs.hasOwnProperty(c)&&(Base64CookieKeys[c]?b.gs[c]=JSON.parse(Base64.decode(b.gs[c])):"ie"===c&&(b.gs[c]=!!b.gs[c]));for(var d in b)if(b.hasOwnProperty(d))if(!SDKv2NonMPIDCookieKeys[d])for(c in b[d])b[d].hasOwnProperty(c)&&Base64CookieKeys[c]&&b[d][c].length&&(b[d][c]=JSON.parse(Base64.decode(b[d][c])));else "l"===d&&(b[d]=!!b[d]);}return JSON.stringify(b)}}catch(b){a.Logger.error("Problem with decoding cookie",b);}},this.getCookieDomain=function(){if(a._Store.SDKConfig.cookieDomain)return a._Store.SDKConfig.cookieDomain;var b=f.getDomain(document,location.hostname);return ""===b?"":"."+b},this.getDomain=function(a,b){var c,d,e=b.split(".");for(c=e.length-1;0<=c;c--)if(d=e.slice(c).join("."),a.cookie="mptest=cookie;domain=."+d+";",-1 void; + sendLogToServer: (log: LogRequest) => void; } export interface IForwardingStatsData { @@ -231,4 +233,26 @@ export default function APIClient( } } }; + + this.sendLogToServer = function(logRequest: LogRequest) { + const baseUrl = mpInstance._Helpers.createServiceUrl( + mpInstance._Store.SDKConfig.v2SecureServiceUrl, + mpInstance._Store.devToken + ); + const uploadUrl = `apps.stage.rokt.com/v1/log/v1/log`; + // const uploadUrl = `${baseUrl}/v1/log`; + + const uploader = window.fetch + ? new FetchUploader(uploadUrl) + : new XHRUploader(uploadUrl); + + uploader.upload({ + method: 'POST', + headers: { + Accept: 'text/plain;charset=UTF-8', + 'Content-Type': 'text/plain;charset=UTF-8', + }, + body: JSON.stringify(logRequest), + }); + }; } diff --git a/src/batchUploader.ts b/src/batchUploader.ts index b9b598d9d..b4a5233b7 100644 --- a/src/batchUploader.ts +++ b/src/batchUploader.ts @@ -72,9 +72,7 @@ export class BatchUploader { // Cache Offline Storage Availability boolean // so that we don't have to check it every time - this.offlineStorageEnabled = - this.isOfflineStorageAvailable() && - !mpInstance._Store.getPrivacyFlag('OfflineEvents'); + this.offlineStorageEnabled = this.isOfflineStorageAvailable(); if (this.offlineStorageEnabled) { this.eventVault = new SessionStorageVault( diff --git a/src/constants.ts b/src/constants.ts index 43a697a27..bf513a5d1 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -233,14 +233,3 @@ export const HTTP_UNAUTHORIZED = 401 as const; export const HTTP_FORBIDDEN = 403 as const; export const HTTP_NOT_FOUND = 404 as const; export const HTTP_SERVER_ERROR = 500 as const; - -export type PrivacyControl = 'functional' | 'targeting'; - -export type StorageTypes = 'SDKState' | 'OfflineEvents' | 'IdentityCache' | 'TimeOnSite'; - -export const StoragePrivacyMap: Record = { - SDKState: 'functional', - OfflineEvents: 'functional', - IdentityCache: 'functional', - TimeOnSite: 'targeting', -}; diff --git a/src/logging/errorCodes.ts b/src/logging/errorCodes.ts new file mode 100644 index 000000000..c45d2ef75 --- /dev/null +++ b/src/logging/errorCodes.ts @@ -0,0 +1,5 @@ +export type ErrorCodes = (typeof ErrorCodes)[keyof typeof ErrorCodes]; + +export const ErrorCodes = { + UNHANDLED_EXCEPTION: 'UNHANDLED_EXCEPTION', +} as const; \ No newline at end of file diff --git a/src/logging/logMessage.ts b/src/logging/logMessage.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/logging/logRequest.ts b/src/logging/logRequest.ts new file mode 100644 index 000000000..8cb248e14 --- /dev/null +++ b/src/logging/logRequest.ts @@ -0,0 +1,21 @@ +import { ErrorCodes } from "./errorCodes"; + +export enum LogRequestSeverity { + Error = 'error', + Warning = 'warning', + Info = 'info', +} + +export interface LogRequest { + additionalInformation: { + message: string; + version: string; + }; + severity: LogRequestSeverity; + code: ErrorCodes; + url: string; + deviceInfo: string; + stackTrace: string; + reporter: string; + integration: string; +} \ No newline at end of file diff --git a/src/logging/reportingLogger.ts b/src/logging/reportingLogger.ts new file mode 100644 index 000000000..d30a74ee1 --- /dev/null +++ b/src/logging/reportingLogger.ts @@ -0,0 +1,128 @@ +import { LogLevelType, SDKLoggerApi } from "../sdkRuntimeModels"; +import { IAPIClient } from "../apiClient"; +import { ErrorCodes } from "./errorCodes"; +import { LogRequest, LogRequestSeverity } from "./logRequest"; + +export interface IReportingLogger { + error(msg: string, code?: ErrorCodes, stackTrace?: string): void; + warning(msg: string, code?: ErrorCodes): void; +} + +export class ReportingLogger implements IReportingLogger { + private readonly isEnabled: boolean; + private readonly apiClient: IAPIClient; + private readonly reporter: string = 'mp-wsdk'; + private readonly integration: string = 'mp-wsdk'; + private readonly rateLimiter: RateLimiter = new RateLimiter(); + + constructor( + apiClient: IAPIClient, + private readonly sdkVersion: string, + ) { + this.isEnabled = this.isReportingEnabled(); + this.apiClient = apiClient; + this.rateLimiter = new RateLimiter(); + } + + public error(msg: string, code?: ErrorCodes, stackTrace?: string) { + this.sendLog(LogRequestSeverity.Error, msg, code ?? ErrorCodes.UNHANDLED_EXCEPTION, stackTrace); + }; + + public warning(msg: string, code?: ErrorCodes) { + this.sendLog(LogRequestSeverity.Warning, msg, code ?? ErrorCodes.UNHANDLED_EXCEPTION); + }; + + private sendLog( + severity: LogRequestSeverity, + msg: string, + code: ErrorCodes, + stackTrace?: string + ): void { + if(!this.canSendLog(severity)) + return; + + const logRequest: LogRequest = { + additionalInformation: { + message: msg, + version: this.sdkVersion, + }, + severity: severity, + code: code, + url: this.getUrl(), + deviceInfo: this.getUserAgent(), + stackTrace: stackTrace ?? '', + reporter: this.reporter, + integration: this.integration, + }; + + this.apiClient.sendLogToServer(logRequest); + } + + private isReportingEnabled() { + return ( + this.isRoktDomainPresent() && + (this.isFeatureFlagEnabled() || + this.isDebugModeEnabled()) + ); + } + + private isRoktDomainPresent() { + return window['ROKT_DOMAIN']; + } + + private isFeatureFlagEnabled() { + return window. + mParticle?. + config?. + isWebSdkLoggingEnabled ?? false; + } + + private isDebugModeEnabled() { + return ( + window. + location?. + search?. + toLowerCase()?. + includes('mp_enable_logging=true') ?? false + ); + } + + private canSendLog(severity: LogRequestSeverity): boolean { + return this.isEnabled && !this.isRateLimited(severity); + } + + private isRateLimited(severity: LogRequestSeverity): boolean { + return this.rateLimiter.incrementAndCheck(severity); + } + + private getUrl(): string { + return window.location.href; + } + + private getUserAgent(): string { + return window.navigator.userAgent; + } +} + +export interface IRateLimiter { + incrementAndCheck(severity: LogRequestSeverity): boolean; +} + +export class RateLimiter implements IRateLimiter { + private readonly rateLimits: Map = new Map([ + [LogRequestSeverity.Error, 10], + [LogRequestSeverity.Warning, 10], + [LogRequestSeverity.Info, 10], + ]); + private logCount: Map = new Map(); + + public incrementAndCheck(severity: LogRequestSeverity): boolean { + const count = this.logCount.get(severity) || 0; + const limit = this.rateLimits.get(severity) || 10; + + const newCount = count + 1; + this.logCount.set(severity, newCount); + + return newCount > limit; + } +} \ No newline at end of file diff --git a/src/mp-instance.ts b/src/mp-instance.ts index 1448e4941..7394673ae 100644 --- a/src/mp-instance.ts +++ b/src/mp-instance.ts @@ -37,7 +37,7 @@ import KitBlocker from './kitBlocking'; import ConfigAPIClient, { IKitConfigs } from './configAPIClient'; import IdentityAPIClient from './identityApiClient'; import { isFunction, parseConfig, valueof, generateDeprecationMessage } from './utils'; -import { DisabledVault, LocalStorageVault } from './vault'; +import { LocalStorageVault } from './vault'; import { removeExpiredIdentityCacheDates } from './identity-utils'; import IntegrationCapture from './integrationCapture'; import { IPreInit, processReadyQueue } from './pre-init-utils'; @@ -162,7 +162,10 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan ); } - runPreConfigFetchInitialization(this, apiKey, config); + const kitBlocker = createKitBlocker(config, this); + runPreConfigFetchInitialization(this, apiKey, config, kitBlocker); + debugger; + this.Logger.error('gt error test'); // config code - Fetch config when requestConfig = true, otherwise, proceed with SDKInitialization // Since fetching the configuration is asynchronous, we must pass completeSDKInitialization @@ -185,10 +188,10 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan result ); - completeSDKInitialization(apiKey, mergedConfig, this); + completeSDKInitialization(apiKey, mergedConfig, this, kitBlocker); }); } else { - completeSDKInitialization(apiKey, config, this); + completeSDKInitialization(apiKey, config, this, kitBlocker); } } else { console.error( @@ -1361,11 +1364,9 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan } // Some (server) config settings need to be returned before they are set on SDKConfig in a self hosted environment -function completeSDKInitialization(apiKey, config, mpInstance) { - const kitBlocker = createKitBlocker(config, mpInstance); +function completeSDKInitialization(apiKey, config, mpInstance, kitBlocker: KitBlocker) { const { getFeatureFlag } = mpInstance._Helpers; - mpInstance._APIClient = new APIClient(mpInstance, kitBlocker); mpInstance._Forwarders = new Forwarders(mpInstance, kitBlocker); mpInstance._Store.processConfig(config); @@ -1544,15 +1545,14 @@ function createKitBlocker(config, mpInstance) { } function createIdentityCache(mpInstance) { - const cacheKey = `${mpInstance._Store.storageName}-id-cache`; - if (mpInstance._Store.getPrivacyFlag('IdentityCache')) { - return new DisabledVault(cacheKey, { logger: mpInstance.Logger }); - } - return new LocalStorageVault(cacheKey, { logger: mpInstance.Logger }); + return new LocalStorageVault(`${mpInstance._Store.storageName}-id-cache`, { + logger: mpInstance.Logger, + }); } -function runPreConfigFetchInitialization(mpInstance, apiKey, config) { - mpInstance.Logger = new Logger(config); +function runPreConfigFetchInitialization(mpInstance, apiKey, config, kitBlocker: KitBlocker) { + mpInstance._APIClient = new APIClient(mpInstance, kitBlocker); + mpInstance.Logger = new Logger(config, mpInstance._APIClient); mpInstance._Store = new Store(config, mpInstance, apiKey); window.mParticle.Store = mpInstance._Store; mpInstance.Logger.verbose(StartingInitialization); diff --git a/src/persistence.js b/src/persistence.js index fb0aec710..7a30d754c 100644 --- a/src/persistence.js +++ b/src/persistence.js @@ -35,10 +35,6 @@ export default function _Persistence(mpInstance) { mpInstance._Store.isFirstRun = false; } - if (mpInstance._Store.getPrivacyFlag('SDKState')) { - return; - } - // https://go.mparticle.com/work/SQDSDKS-6045 if (!mpInstance._Store.isLocalStorageAvailable) { mpInstance._Store.SDKConfig.useCookieStorage = true; @@ -117,10 +113,7 @@ export default function _Persistence(mpInstance) { }; this.update = function() { - if ( - !mpInstance._Store.webviewBridgeEnabled && - !mpInstance._Store.getPrivacyFlag('SDKState') - ) { + if (!mpInstance._Store.webviewBridgeEnabled) { if (mpInstance._Store.SDKConfig.useCookieStorage) { self.setCookie(); } @@ -810,9 +803,6 @@ export default function _Persistence(mpInstance) { // https://go.mparticle.com/work/SQDSDKS-6021 this.savePersistence = function(persistence) { - if (mpInstance._Store.getPrivacyFlag('SDKState')) { - return; - } var encodedPersistence = self.encodePersistence( JSON.stringify(persistence) ), @@ -857,9 +847,6 @@ export default function _Persistence(mpInstance) { }; this.getPersistence = function() { - if (mpInstance._Store.getPrivacyFlag('SDKState')) { - return null; - } var persistence = this.useLocalStorage() ? this.getLocalStorage() : this.getCookie(); diff --git a/src/sdkRuntimeModels.ts b/src/sdkRuntimeModels.ts index 579c9832e..0b9b77394 100644 --- a/src/sdkRuntimeModels.ts +++ b/src/sdkRuntimeModels.ts @@ -278,9 +278,6 @@ export interface SDKInitConfig extends Omit { dataPlan?: DataPlanConfig | KitBlockerDataPlan; // TODO: These should be eventually split into two different attributes logLevel?: LogLevelType; - - noFunctional?: boolean; - noTargeting?: boolean; kitConfigs?: IKitConfigs[]; kits?: Dictionary; @@ -314,6 +311,7 @@ export interface SDKInitConfig identityCallback?: IdentityCallback; launcherOptions?: IRoktLauncherOptions; + isWebSdkLoggingEnabled?: boolean; rq?: Function[] | any[]; logger?: IConsoleLogger; diff --git a/src/store.ts b/src/store.ts index 43e1b4134..64d560dc8 100644 --- a/src/store.ts +++ b/src/store.ts @@ -8,7 +8,7 @@ import { UserIdentities, } from '@mparticle/web-sdk'; import { IKitConfigs } from './configAPIClient'; -import Constants, { PrivacyControl, StoragePrivacyMap, StorageTypes } from './constants'; +import Constants from './constants'; import { DataPlanResult, KitBlockerOptions, @@ -153,8 +153,6 @@ export interface IFeatureFlags { export interface IStore { isEnabled: boolean; isInitialized: boolean; - noFunctional: boolean; - noTargeting: boolean; // Session Attributes are persistent attributes that are tied to the current session and // are uploaded then cleared when the session ends. @@ -211,12 +209,6 @@ export interface IStore { _getFromPersistence?(mpid: MPID, key: string): T; _setPersistence?(mpid: MPID, key: string, value: T): void; - getNoFunctional?(): boolean; - setNoFunctional?(noFunctional: boolean): void; - getNoTargeting?(): boolean; - setNoTargeting?(noTargeting: boolean): void; - getPrivacyFlag?(storageType: StorageTypes): boolean; - getDeviceId?(): string; setDeviceId?(deviceId: string): void; getFirstSeenTime?(mpid: MPID): number; @@ -252,8 +244,6 @@ export default function Store( const defaultStore: Partial = { isEnabled: true, - noFunctional: false, - noTargeting: false, sessionAttributes: {}, localSessionAttributes: {}, currentSessionMPIDs: [], @@ -596,27 +586,6 @@ export default function Store( } }; - this.getNoFunctional = (): boolean => this.noFunctional; - this.setNoFunctional = (noFunctional: boolean): void => { - this.noFunctional = noFunctional; - }; - - this.getNoTargeting = (): boolean => this.noTargeting; - this.setNoTargeting = (noTargeting: boolean): void => { - this.noTargeting = noTargeting; - }; - - this.getPrivacyFlag = (storageType: StorageTypes): boolean => { - const privacyControl: PrivacyControl = StoragePrivacyMap[storageType]; - if (privacyControl === 'functional') { - return this.getNoFunctional(); - } - if (privacyControl === 'targeting') { - return this.getNoTargeting(); - } - return false; - }; - this.getDeviceId = () => this.deviceId; this.setDeviceId = (deviceId: string) => { this.deviceId = deviceId; @@ -737,21 +706,9 @@ export default function Store( this.SDKConfig[baseUrlKeys] = baseUrls[baseUrlKeys]; } - const { noFunctional, noTargeting } = config?.launcherOptions ?? {}; - - if (noFunctional != null) { - this.setNoFunctional(noFunctional); - } - - if (noTargeting != null) { - this.setNoTargeting(noTargeting); - } - if (workspaceToken) { this.SDKConfig.workspaceToken = workspaceToken; - if (!this.getPrivacyFlag('TimeOnSite')) { - mpInstance._timeOnSiteTimer = new ForegroundTimer(workspaceToken); - } + mpInstance._timeOnSiteTimer = new ForegroundTimer(workspaceToken); } else { mpInstance.Logger.warning( 'You should have a workspaceToken on your config object for security purposes.' diff --git a/src/vault.ts b/src/vault.ts index eb0bcafbd..57d2a6dc1 100644 --- a/src/vault.ts +++ b/src/vault.ts @@ -102,20 +102,4 @@ export class SessionStorageVault extends BaseVault { constructor(storageKey: string, options?: IVaultOptions) { super(storageKey, window.sessionStorage, options); } -} - -// DisabledVault is used when persistence is disabled by privacy flags. -export class DisabledVault extends BaseVault { - constructor(storageKey: string, options?: IVaultOptions) { - super(storageKey, window.localStorage, options); - this.contents = null; - } - - public store(_item: StorableItem): void { - this.contents = null; - } - - public retrieve(): StorableItem | null { - return this.contents; - } } \ No newline at end of file diff --git a/test/jest/batchUploader.spec.ts b/test/jest/batchUploader.spec.ts index 35f35c3f0..03a8542df 100644 --- a/test/jest/batchUploader.spec.ts +++ b/test/jest/batchUploader.spec.ts @@ -1,8 +1,5 @@ import { BatchUploader } from '../../src/batchUploader'; import { IMParticleWebSDKInstance } from '../../src/mp-instance'; -import { IStore } from '../../src/store'; -import { StoragePrivacyMap, StorageTypes } from '../../src/constants'; -import { SDKEvent } from '../../src/sdkRuntimeModels'; describe('BatchUploader', () => { let batchUploader: BatchUploader; @@ -21,39 +18,18 @@ describe('BatchUploader', () => { // Create a mock mParticle instance with mocked methods for instantiating a BatchUploader mockMPInstance = { _Store: { - storageName: 'mprtcl-v4_abcdef', - getNoFunctional: function(this: IStore) { return this.noFunctional; }, - getNoTargeting: function(this: IStore) { return this.noTargeting; }, - getPrivacyFlag: function(this: IStore, storageType: StorageTypes) { - const privacyControl = StoragePrivacyMap[storageType]; - if (privacyControl === 'functional') { - return this.getNoFunctional(); - } - if (privacyControl === 'targeting') { - return this.getNoTargeting(); - } - return false; - }, - deviceId: 'device-1', SDKConfig: { flags: {} } }, _Helpers: { - getFeatureFlag: jest.fn().mockReturnValue('100'), + getFeatureFlag: jest.fn().mockReturnValue(false), createServiceUrl: jest.fn().mockReturnValue('https://mock-url.com'), - generateUniqueId: jest.fn().mockReturnValue('req-1'), }, Identity: { getCurrentUser: jest.fn().mockReturnValue({ - getMPID: () => 'test-mpid', - getConsentState: jest.fn().mockReturnValue(null), + getMPID: () => 'test-mpid' }) - }, - Logger: { - verbose: jest.fn(), - error: jest.fn(), - warning: jest.fn(), } } as unknown as IMParticleWebSDKInstance; @@ -132,50 +108,4 @@ describe('BatchUploader', () => { expect(secondCallTime).toBe(firstCallTime); }); }); - - describe('noFunctional', () => { - beforeEach(() => { - localStorage.clear(); - sessionStorage.clear(); - }); - - it('should disable offline storage when noFunctional is true', () => { - mockMPInstance._Store.noFunctional = true; - - const uploader = new BatchUploader(mockMPInstance, 1000); - expect(uploader['offlineStorageEnabled']).toBe(false); - - uploader.queueEvent({ EventDataType: 4 } as SDKEvent); - expect(sessionStorage.getItem('mprtcl-v4_abcdef-events')).toBeNull(); - expect(localStorage.getItem('mprtcl-v4_abcdef-batches')).toBeNull(); - }); - - it('should enable offline storage when noFunctional is false by default', async () => { - const uploader = new BatchUploader(mockMPInstance, 1000); - - expect(uploader['offlineStorageEnabled']).toBe(true); - - uploader.queueEvent({ EventDataType: 4 } as SDKEvent); - expect(sessionStorage.getItem('mprtcl-v4_abcdef-events')).not.toBeNull(); - - jest.advanceTimersByTime(1000); - await Promise.resolve(); - - expect(localStorage.getItem('mprtcl-v4_abcdef-batches')).not.toBeNull(); - }); - - it('should enable offline storage when noFunctional is false', async () => { - mockMPInstance._Store.noFunctional = false; - - const uploader = new BatchUploader(mockMPInstance, 1000); - - uploader.queueEvent({ EventDataType: 4 } as SDKEvent); - expect(sessionStorage.getItem('mprtcl-v4_abcdef-events')).not.toBeNull(); - - jest.advanceTimersByTime(1000); - await Promise.resolve(); - - expect(localStorage.getItem('mprtcl-v4_abcdef-batches')).not.toBeNull(); - }); - }); }); \ No newline at end of file diff --git a/test/jest/foregroundTimeTracker.spec.ts b/test/jest/foregroundTimeTracker.spec.ts index c4941bb9c..7f4ab1ee7 100644 --- a/test/jest/foregroundTimeTracker.spec.ts +++ b/test/jest/foregroundTimeTracker.spec.ts @@ -1,7 +1,4 @@ import ForegroundTimeTracker from '../../src/foregroundTimeTracker'; -import Store, { IStore } from '../../src/store'; -import { SDKInitConfig } from '../../src/sdkRuntimeModels'; -import { IMParticleWebSDKInstance } from '../../src/mp-instance'; describe('ForegroundTimeTracker', () => { let foregroundTimeTracker: ForegroundTimeTracker; @@ -493,65 +490,4 @@ describe('ForegroundTimeTracker', () => { expect(updatePersistenceSpy).toHaveBeenCalled(); }); }); - - describe('#privacy flags', () => { - const workspaceToken = 'abcdef'; - const tosKey = `mprtcl-tos-${workspaceToken}`; - let mockMPInstance: IMParticleWebSDKInstance; - let store: IStore; - - beforeEach(() => { - jest.useFakeTimers({ now: Date.now(), advanceTimers: true }); - localStorage.clear(); - - mockMPInstance = { - _Helpers: { - createMainStorageName: () => `mprtcl-v4_${workspaceToken}`, - createProductStorageName: () => `mprtcl-prodv4_${workspaceToken}`, - Validators: { isFunction: () => true }, - extend: Object.assign, - }, - _NativeSdkHelpers: { isWebviewEnabled: () => false }, - _Persistence: { - update: jest.fn(), - savePersistence: jest.fn(), - getPersistence: () => ({ gs: {} }), - }, - Logger: { verbose: jest.fn(), warning: jest.fn(), error: jest.fn() }, - Identity: { getCurrentUser: () => ({ getMPID: () => 'mpid' }) }, - _timeOnSiteTimer: undefined as any, - } as unknown as IMParticleWebSDKInstance; - - store = {} as IStore; - (Store as any).call(store, {} as SDKInitConfig, mockMPInstance, 'apikey'); - mockMPInstance._Store = store; - }); - - afterEach(() => { - jest.useRealTimers(); - localStorage.clear(); - }); - - it('should track time on site when noTargeting is false by default', () => { - store.processConfig({ workspaceToken } as SDKInitConfig); - expect(mockMPInstance._timeOnSiteTimer).toBeDefined(); - jest.advanceTimersByTime(1000); - mockMPInstance._timeOnSiteTimer.getTimeInForeground(); - expect(localStorage.getItem(tosKey)).not.toBeNull(); - }); - - it('should NOT track time on site when noTargeting is true', () => { - store.processConfig({ workspaceToken, launcherOptions: { noTargeting: true } } as SDKInitConfig); - expect(mockMPInstance._timeOnSiteTimer).toBeUndefined(); - expect(localStorage.getItem(tosKey)).toBeNull(); - }); - - it('should track time on site when noTargeting is false', () => { - store.processConfig({ workspaceToken, launcherOptions: { noTargeting: false } } as SDKInitConfig); - expect(mockMPInstance._timeOnSiteTimer).toBeDefined(); - jest.advanceTimersByTime(1000); - mockMPInstance._timeOnSiteTimer.getTimeInForeground(); - expect(localStorage.getItem(tosKey)).not.toBeNull(); - }); - }); }); \ No newline at end of file diff --git a/test/jest/persistence.spec.ts b/test/jest/persistence.spec.ts index c1f7e8610..4e825008e 100644 --- a/test/jest/persistence.spec.ts +++ b/test/jest/persistence.spec.ts @@ -39,105 +39,33 @@ describe('Persistence', () => { }); describe('#update', () => { - describe('noFunctional privacy flag set to true', () => { - beforeEach(() => { - store.setNoFunctional(true); - store.webviewBridgeEnabled = false; - }); - - it('should NOT write to cookie and localStorage when useCookieStorage is true', () => { - store.SDKConfig.useCookieStorage = true; - - const setCookieSpy = jest.spyOn(persistence, 'setCookie'); - const setLocalStorageSpy = jest.spyOn(persistence, 'setLocalStorage'); - - persistence.update(); - - expect(setCookieSpy).not.toHaveBeenCalled(); - expect(setLocalStorageSpy).not.toHaveBeenCalled(); - }); - - it('should NOT write to localStorage when useCookieStorage is false', () => { - store.SDKConfig.useCookieStorage = false; - - const setCookieSpy = jest.spyOn(persistence, 'setCookie'); - const setLocalStorageSpy = jest.spyOn(persistence, 'setLocalStorage'); - - persistence.update(); - - expect(setCookieSpy).not.toHaveBeenCalled(); - expect(setLocalStorageSpy).not.toHaveBeenCalled(); - }); - }); - - describe('noFunctional privacy flag set to false', () => { - beforeEach(() => { - store.setNoFunctional(false); - store.webviewBridgeEnabled = false; - }); - - it('should write to cookie and localStorage when useCookieStorage is true', () => { - store.SDKConfig.useCookieStorage = true; - - jest.spyOn(persistence, 'getCookie').mockReturnValue(null); - - const setCookieSpy = jest.spyOn(persistence, 'setCookie'); - const setLocalStorageSpy = jest.spyOn(persistence, 'setLocalStorage'); - - persistence.update(); - - expect(setCookieSpy).toHaveBeenCalled(); - expect(setLocalStorageSpy).toHaveBeenCalled(); - }); + it('should write to cookie and localStorage by default when useCookieStorage is true', () => { + store.SDKConfig.useCookieStorage = true; - it('should write to localStorage when useCookieStorage is false', () => { - store.SDKConfig.useCookieStorage = false; + jest.spyOn(persistence, 'getCookie').mockReturnValue(null); - const setCookieSpy = jest.spyOn(persistence, 'setCookie'); - const setLocalStorageSpy = jest.spyOn(persistence, 'setLocalStorage'); + const setCookieSpy = jest.spyOn(persistence, 'setCookie'); + const setLocalStorageSpy = jest.spyOn(persistence, 'setLocalStorage'); - persistence.update(); + persistence.update(); - expect(setCookieSpy).not.toHaveBeenCalled(); - expect(setLocalStorageSpy).toHaveBeenCalled(); - }); + expect(setCookieSpy).toHaveBeenCalled(); + expect(setLocalStorageSpy).toHaveBeenCalled(); }); - describe('noFunctional privacy flag set to false by default', () => { - beforeEach(() => { - // default is false - store.webviewBridgeEnabled = false; - }); - - it('should write to cookie and localStorage by default when useCookieStorage is true', () => { - store.SDKConfig.useCookieStorage = true; - - jest.spyOn(persistence, 'getCookie').mockReturnValue(null); - - const setCookieSpy = jest.spyOn(persistence, 'setCookie'); - const setLocalStorageSpy = jest.spyOn(persistence, 'setLocalStorage'); + it('should write to localStorage by default when useCookieStorage is false', () => { + store.SDKConfig.useCookieStorage = false; - persistence.update(); - - expect(setCookieSpy).toHaveBeenCalled(); - expect(setLocalStorageSpy).toHaveBeenCalled(); - }); - - it('should write to localStorage by default when useCookieStorage is false', () => { - store.SDKConfig.useCookieStorage = false; - - const setCookieSpy = jest.spyOn(persistence, 'setCookie'); - const setLocalStorageSpy = jest.spyOn(persistence, 'setLocalStorage'); + const setCookieSpy = jest.spyOn(persistence, 'setCookie'); + const setLocalStorageSpy = jest.spyOn(persistence, 'setLocalStorage'); - persistence.update(); + persistence.update(); - expect(setCookieSpy).not.toHaveBeenCalled(); - expect(setLocalStorageSpy).toHaveBeenCalled(); - }); + expect(setCookieSpy).not.toHaveBeenCalled(); + expect(setLocalStorageSpy).toHaveBeenCalled(); }); it('should NOT write to storage when webviewBridgeEnabled is true', () => { - store.setNoFunctional(false); store.webviewBridgeEnabled = true; store.SDKConfig.useCookieStorage = true; diff --git a/test/jest/reportingLogger.spec.ts b/test/jest/reportingLogger.spec.ts new file mode 100644 index 000000000..7d531662b --- /dev/null +++ b/test/jest/reportingLogger.spec.ts @@ -0,0 +1,116 @@ +import { RateLimiter, ReportingLogger } from '../../src/logging/reportingLogger'; +import { LogRequestSeverity } from '../../src/logging/logRequest'; +import { ErrorCodes } from '../../src/logging/errorCodes'; + +describe('ReportingLogger', () => { + let apiClient: any; + let logger: ReportingLogger; + const sdkVersion = '1.2.3'; + + beforeEach(() => { + apiClient = { sendLogToServer: jest.fn() }; + + // Mock location object to allow modifying search property + delete (window as any).location; + (window as any).location = { + href: 'https://e.com', + search: '' + }; + + Object.assign(window, { + navigator: { userAgent: 'ua' }, + mParticle: { config: { isWebSdkLoggingEnabled: true } }, + ROKT_DOMAIN: 'set' + }); + logger = new ReportingLogger(apiClient, sdkVersion); + }); + + afterEach(() => { + jest.clearAllMocks(); + delete (window as any).ROKT_DOMAIN; + delete (window as any).mParticle; + }); + + it('sends error logs with correct params', () => { + logger.error('msg', ErrorCodes.UNHANDLED_EXCEPTION, 'stack'); + expect(apiClient.sendLogToServer).toHaveBeenCalledWith(expect.objectContaining({ + severity: LogRequestSeverity.Error, + code: ErrorCodes.UNHANDLED_EXCEPTION, + stackTrace: 'stack' + })); + }); + + it('sends warning logs with correct params', () => { + logger.warning('warn'); + expect(apiClient.sendLogToServer).toHaveBeenCalledWith(expect.objectContaining({ + severity: LogRequestSeverity.Warning + })); + }); + + it('does not log if ROKT_DOMAIN missing', () => { + delete (window as any).ROKT_DOMAIN; + logger = new ReportingLogger(apiClient, sdkVersion); + logger.error('x'); + expect(apiClient.sendLogToServer).not.toHaveBeenCalled(); + }); + + it('does not log if feature flag and debug mode off', () => { + window.mParticle.config.isWebSdkLoggingEnabled = false; + window.location.search = ''; + logger = new ReportingLogger(apiClient, sdkVersion); + logger.error('x'); + expect(apiClient.sendLogToServer).not.toHaveBeenCalled(); + }); + + it('logs if debug mode on even if feature flag off', () => { + window.mParticle.config.isWebSdkLoggingEnabled = false; + window.location.search = '?mp_enable_logging=true'; + logger = new ReportingLogger(apiClient, sdkVersion); + logger.error('x'); + expect(apiClient.sendLogToServer).toHaveBeenCalled(); + }); + + it('rate limits after 10 errors', () => { + for (let i = 0; i < 12; i++) logger.error('err'); + expect(apiClient.sendLogToServer).toHaveBeenCalledTimes(10); + }); +}); + +describe('RateLimiter', () => { + let rateLimiter: RateLimiter; + beforeEach(() => { + rateLimiter = new RateLimiter(); + }); + + it('allows up to 10 error logs then rate limits', () => { + for (let i = 0; i < 10; i++) { + expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Error)).toBe(false); + } + expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Error)).toBe(true); + expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Error)).toBe(true); + }); + + it('allows up to 10 warning logs then rate limits', () => { + for (let i = 0; i < 10; i++) { + expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Warning)).toBe(false); + } + expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Warning)).toBe(true); + expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Warning)).toBe(true); + }); + + it('allows up to 10 info logs then rate limits', () => { + for (let i = 0; i < 10; i++) { + expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Info)).toBe(false); + } + expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Info)).toBe(true); + expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Info)).toBe(true); + }); + + it('tracks rate limits independently per severity', () => { + for (let i = 0; i < 10; i++) { + rateLimiter.incrementAndCheck(LogRequestSeverity.Error); + } + expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Error)).toBe(true); + expect(rateLimiter.incrementAndCheck(LogRequestSeverity.Warning)).toBe(false); + }); +}); diff --git a/test/jest/roktManager.spec.ts b/test/jest/roktManager.spec.ts index 69085d045..452342183 100644 --- a/test/jest/roktManager.spec.ts +++ b/test/jest/roktManager.spec.ts @@ -310,8 +310,6 @@ describe('RoktManager', () => { it('should initialize the manager with launcher options from options', () => { const launcherOptions = { integrationName: 'customName', - noFunctional: true, - noTargeting: true }; roktManager.init( diff --git a/test/jest/store.flags.spec.ts b/test/jest/store.flags.spec.ts deleted file mode 100644 index ce383ce5b..000000000 --- a/test/jest/store.flags.spec.ts +++ /dev/null @@ -1,82 +0,0 @@ -import Store, { IStore } from '../../src/store'; -import { IMParticleWebSDKInstance } from '../../src/mp-instance'; -import { SDKInitConfig } from '../../src/sdkRuntimeModels'; - -describe('Store privacy flags', () => { - let store: IStore & Record; - let mockMPInstance: IMParticleWebSDKInstance & Record; - - beforeEach(() => { - mockMPInstance = { - _Helpers: { - createMainStorageName: () => 'mprtcl-v4', - createProductStorageName: () => 'mprtcl-prodv4', - Validators: { isFunction: () => true }, - extend: Object.assign, - }, - _NativeSdkHelpers: { - isWebviewEnabled: () => false, - }, - _Persistence: { - update: () => {}, - savePersistence: () => {}, - getPersistence: () => ({ gs: {} }), - }, - Logger: { - verbose: () => {}, - warning: () => {}, - error: () => {}, - }, - Identity: { - getCurrentUser: () => ({ getMPID: () => 'mpid' }), - }, - } as any; - - store = {} as any; - // Construct Store with our mock 'this' - (Store as any).call(store, {} as SDKInitConfig, mockMPInstance, 'apikey'); - }); - - it('should default noFunctional and noTargeting to false when not provided', () => { - const cfg: SDKInitConfig = { flags: {} } as any; - store.processConfig(cfg); - expect(store.getNoFunctional()).toBe(false); - expect(store.getNoTargeting()).toBe(false); - }); - - it('should set noFunctional as true and noTargeting as true from config', () => { - const cfg: SDKInitConfig = { - launcherOptions: { noFunctional: true, noTargeting: true }, - flags: {}, - } as any; - - store.processConfig(cfg); - - expect(store.getNoFunctional()).toBe(true); - expect(store.getNoTargeting()).toBe(true); - }); - - it('should set noFunctional as true and noTargeting as false from config', () => { - const cfg: SDKInitConfig = { - launcherOptions: { noFunctional: true, noTargeting: false }, - flags: {}, - } as any; - - store.processConfig(cfg); - - expect(store.getNoFunctional()).toBe(true); - expect(store.getNoTargeting()).toBe(false); - }); - - it('should set noFunctional as false and noTargeting as true from config', () => { - const cfg: SDKInitConfig = { - launcherOptions: { noFunctional: false, noTargeting: true }, - flags: {}, - } as any; - - store.processConfig(cfg); - - expect(store.getNoFunctional()).toBe(false); - expect(store.getNoTargeting()).toBe(true); - }); -}); \ No newline at end of file diff --git a/test/src/tests-batchUploader.ts b/test/src/tests-batchUploader.ts index 7d1a9819c..16938d92a 100644 --- a/test/src/tests-batchUploader.ts +++ b/test/src/tests-batchUploader.ts @@ -1381,94 +1381,5 @@ describe('batch uploader', () => { 'Batch 4: AST' ).to.equal('application_state_transition'); }); - - describe('noFunctional', () => { - const eventStorageKey = 'mprtcl-v4_abcdef-events'; - const batchStorageKey = 'mprtcl-v4_abcdef-batches'; - - it('should store batches in session storage when noFunctional is false by default', async () => { - window.mParticle.config.flags = { - offlineStorage: '100', - ...enableBatchingConfigFlags, - }; - window.mParticle.init(apiKey, window.mParticle.config); - await waitForCondition(hasIdentifyReturned); - const mpInstance = window.mParticle.getInstance(); - const uploader = mpInstance._APIClient.uploader; - uploader.queueEvent(event0); - expect(window.sessionStorage.getItem(eventStorageKey)).to.not.equal(null); - }); - - it('should NOT store events in session storage when noFunctional is true', async () => { - window.mParticle.config.flags = { - offlineStorage: '100', - ...enableBatchingConfigFlags, - }; - window.mParticle.config.launcherOptions = { noFunctional: true }; - window.mParticle.init(apiKey, window.mParticle.config); - await waitForCondition(hasIdentifyReturned); - const mpInstance = window.mParticle.getInstance(); - const uploader = mpInstance._APIClient.uploader; - uploader.queueEvent(event0); - expect(window.sessionStorage.getItem(eventStorageKey)).to.equal(null); - }); - - it('should store events in session storage when noFunctional is false', async () => { - window.mParticle.config.flags = { - offlineStorage: '100', - ...enableBatchingConfigFlags, - }; - window.mParticle.config.launcherOptions = { noFunctional: false }; - window.mParticle.init(apiKey, window.mParticle.config); - await waitForCondition(hasIdentifyReturned); - const mpInstance = window.mParticle.getInstance(); - const uploader = mpInstance._APIClient.uploader; - uploader.queueEvent(event0); - expect(window.sessionStorage.getItem(eventStorageKey)).to.not.equal(null); - }); - - it('should store batches in local storage when noFunctional is false by default', async () => { - window.mParticle.init(apiKey, window.mParticle.config); - await waitForCondition(hasIdentifyReturned); - const mpInstance = window.mParticle.getInstance(); - const uploader = mpInstance._APIClient.uploader; - fetchMock.post(urls.events, 500, { overwriteRoutes: true }); - uploader.queueEvent(event0); - await window.mParticle.getInstance()._APIClient.uploader.prepareAndUpload(); - expect(window.localStorage.getItem(batchStorageKey)).to.not.equal(null); - }); - - it('should NOT store batches in local storage when noFunctional is true', async () => { - window.mParticle.config.flags = { - offlineStorage: '100', - ...enableBatchingConfigFlags, - }; - window.mParticle.config.launcherOptions = { noFunctional: true }; - window.mParticle.init(apiKey, window.mParticle.config); - await waitForCondition(hasIdentifyReturned); - const mpInstance = window.mParticle.getInstance(); - const uploader = mpInstance._APIClient.uploader; - uploader.queueEvent(event0); - await window.mParticle.getInstance()._APIClient.uploader.prepareAndUpload(); - expect(window.localStorage.getItem(batchStorageKey)).to.equal(null); - }); - - it('should store batches in local storage when noFunctional is false', async () => { - window.mParticle.config.flags = { - offlineStorage: '100', - ...enableBatchingConfigFlags, - }; - window.mParticle.config.launcherOptions = { noFunctional: false }; - window.mParticle.init(apiKey, window.mParticle.config); - await waitForCondition(hasIdentifyReturned); - const mpInstance = window.mParticle.getInstance(); - const uploader = mpInstance._APIClient.uploader; - fetchMock.post(urls.events, 500, { overwriteRoutes: true }); - uploader.queueEvent(event0); - await window.mParticle.getInstance()._APIClient.uploader.prepareAndUpload(); - expect(window.localStorage.getItem(batchStorageKey)).to.not.equal(null); - }); - - }); }); }); \ No newline at end of file diff --git a/test/src/tests-identity.ts b/test/src/tests-identity.ts index ad52b9cbb..48143d7c3 100644 --- a/test/src/tests-identity.ts +++ b/test/src/tests-identity.ts @@ -643,35 +643,6 @@ describe('identity', function() { }); }); - describe('privacy flags', function() { - beforeEach(function() { - mParticle.config.flags.cacheIdentity = 'True'; - localStorage.clear(); - }); - - describe('#createIdentityCache', function() { - it('should save id cache to local storage when noFunctional is false by default', async () => { - mParticle.init(apiKey, window.mParticle.config); - await waitForCondition(hasIdentifyReturned); - expect(localStorage.getItem('mprtcl-v4_abcdef-id-cache')).to.be.ok; - }); - - it('should NOT save id cache to local storage when noFunctional is true', async () => { - mParticle.config.launcherOptions = { noFunctional: true }; - mParticle.init(apiKey, window.mParticle.config); - await waitForCondition(hasIdentifyReturned); - expect(localStorage.getItem('mprtcl-v4_abcdef-id-cache')).not.to.be.ok; - }); - - it('should save id cache to local storage when noFunctional is false', async () => { - mParticle.config.launcherOptions = { noFunctional: false }; - mParticle.init(apiKey, window.mParticle.config); - await waitForCondition(hasIdentifyReturned); - expect(localStorage.getItem('mprtcl-v4_abcdef-id-cache')).to.be.ok; - }); - }); - }); - it('should respect consent rules on consent-change', async () => { await waitForCondition(hasIdentityCallInflightReturned); mParticle._resetForTests(MPConfig); diff --git a/test/src/tests-persistence.ts b/test/src/tests-persistence.ts index 045ff7f71..97516200f 100644 --- a/test/src/tests-persistence.ts +++ b/test/src/tests-persistence.ts @@ -1876,86 +1876,4 @@ describe('persistence', () => { user2.getAllUserAttributes()['ua-list'][1].should.equal(''); user2.getAllUserAttributes()['ua-1'].should.equal('a'); }); - - describe('noFunctional privacy flag', () => { - describe('set to true', () => { - beforeEach(() => { - mParticle.config.launcherOptions = { noFunctional: true }; - }); - - it('should NOT store cookie when useCookieStorage = true', async () => { - mParticle.config.useCookieStorage = true; - - mParticle.init(apiKey, mParticle.config); - await waitForCondition(hasIdentifyReturned); - - mParticle.getInstance()._Persistence.update(); - - expect(findCookie()).to.not.be.ok; - }); - - it('should NOT write localStorage when useCookieStorage = false', async () => { - mParticle.config.useCookieStorage = false; - - mParticle.init(apiKey, mParticle.config); - await waitForCondition(hasIdentifyReturned); - - mParticle.getInstance()._Persistence.update(); - - expect(getLocalStorage()).to.not.be.ok; - }); - }); - - describe('set to false', () => { - beforeEach(() => { - mParticle.config.launcherOptions = { noFunctional: false }; - }); - - it('should store cookie when useCookieStorage = true', async () => { - mParticle.config.useCookieStorage = true; - - mParticle.init(apiKey, mParticle.config); - await waitForCondition(hasIdentifyReturned); - - mParticle.getInstance()._Persistence.update(); - - expect(findCookie()).to.be.ok; - }); - - it('should store localStorage when useCookieStorage = false', async () => { - mParticle.config.useCookieStorage = false; - - mParticle.init(apiKey, mParticle.config); - await waitForCondition(hasIdentifyReturned); - - mParticle.getInstance()._Persistence.update(); - - expect(getLocalStorage()).to.be.ok; - }); - }); - - describe('is false by default', () => { - it('should store cookie when useCookieStorage = true', async () => { - mParticle.config.useCookieStorage = true; - - mParticle.init(apiKey, mParticle.config); - await waitForCondition(hasIdentifyReturned); - - mParticle.getInstance()._Persistence.update(); - - expect(findCookie()).to.be.ok; - }); - - it('should store localStorage when useCookieStorage = false', async () => { - mParticle.config.useCookieStorage = false; - - mParticle.init(apiKey, mParticle.config); - await waitForCondition(hasIdentifyReturned); - - mParticle.getInstance()._Persistence.update(); - - expect(getLocalStorage()).to.be.ok; - }); - }); - }); }); \ No newline at end of file diff --git a/test/src/tests-store.ts b/test/src/tests-store.ts index 418b54999..774d08222 100644 --- a/test/src/tests-store.ts +++ b/test/src/tests-store.ts @@ -1805,25 +1805,4 @@ describe('Store', () => { }); }); }); - - describe('#privacy flags', () => { - it('should set noFunctional and noTargeting to false when not provided', () => { - const inst = window.mParticle.getInstance(); - const store = (inst as any)._Store; - expect(store.getNoFunctional()).to.equal(false); - expect(store.getNoTargeting()).to.equal(false); - }); - - it('should set noFunctional and noTargeting from init config', () => { - window.mParticle.config = window.mParticle.config || {}; - window.mParticle.config.launcherOptions = { noFunctional: true, noTargeting: true }; - - window.mParticle.init(apiKey, window.mParticle.config); - - const inst = window.mParticle.getInstance(); - const store = (inst as any)._Store; - expect(store.getNoFunctional()).to.equal(true); - expect(store.getNoTargeting()).to.equal(true); - }); - }); }); \ No newline at end of file diff --git a/test/src/vault.spec.ts b/test/src/vault.spec.ts index d264327ce..c9ee53085 100644 --- a/test/src/vault.spec.ts +++ b/test/src/vault.spec.ts @@ -1,7 +1,7 @@ import { Batch } from '@mparticle/event-models'; import { expect } from 'chai'; import { Dictionary } from '../../src/utils'; -import { SessionStorageVault, LocalStorageVault, DisabledVault } from '../../src/vault'; +import { SessionStorageVault, LocalStorageVault } from '../../src/vault'; const testObject: Dictionary> = { foo: { foo: 'bar', buzz: 'bazz' }, @@ -419,76 +419,4 @@ describe('Vault', () => { ); }); }); - - describe('DisabledVault', () => { - afterEach(() => { - window.localStorage.clear(); - }); - - describe('#store', () => { - it('should NOT write to localStorage', () => { - const storageKey = 'test-disabled-store-empty'; - const vault = new DisabledVault(storageKey); - - vault.store('testString'); - - expect(vault.contents).to.equal(null); - expect(window.localStorage.getItem(storageKey)).to.equal(null); - }); - - it('should NOT overwrite existing localStorage value and keep contents null', () => { - const storageKey = 'test-disabled-store-existing'; - window.localStorage.setItem(storageKey, 'existingItem'); - - const vault = new DisabledVault(storageKey); - - vault.store('newValue'); - - expect(vault.contents).to.equal(null); - expect(window.localStorage.getItem(storageKey)).to.equal('existingItem'); - }); - }); - - describe('#retrieve', () => { - it('should return null when nothing is stored', () => { - const storageKey = 'test-disabled-retrieve-empty'; - const vault = new DisabledVault(storageKey); - const retrievedItem = vault.retrieve(); - expect(retrievedItem).to.equal(null); - }); - - it('should return null even if localStorage has a value', () => { - const storageKey = 'test-disabled-retrieve-existing'; - window.localStorage.setItem(storageKey, 'existingItem'); - - const vault = new DisabledVault(storageKey); - const retrievedItem = vault.retrieve(); - expect(retrievedItem).to.equal(null); - expect(window.localStorage.getItem(storageKey)).to.equal('existingItem'); - }); - }); - - describe('#purge', () => { - it('should keep contents null when purging', () => { - const storageKey = 'test-disabled-purge-existing'; - window.localStorage.setItem(storageKey, 'existing'); - - const vault = new DisabledVault(storageKey); - - vault.purge(); - - expect(vault.contents).to.equal(null); - expect(window.localStorage.getItem(storageKey)).to.equal(null); - }); - - it('should keep contents null when purging an empty key', () => { - const storageKey = 'test-disabled-purge-empty'; - const vault = new DisabledVault(storageKey); - - vault.purge(); - expect(vault.contents).to.equal(null); - expect(window.localStorage.getItem(storageKey)).to.equal(null); - }); - }); - }); }); \ No newline at end of file