@mparticle/web-sdk 2.49.0 → 2.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mparticle.common.js +6 -5
- package/dist/mparticle.esm.js +6 -5
- package/dist/mparticle.js +54 -13
- package/package.json +1 -1
- package/src/roktManager.ts +56 -7
package/dist/mparticle.common.js
CHANGED
|
@@ -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.
|
|
18
|
+
var version = "2.50.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
|
|
@@ -1438,15 +1438,16 @@ var RoktManager=/** @class */function(){function a(){this.kit=null,this.filters=
|
|
|
1438
1438
|
* @param {IRoktOptions} options - Options for the RoktManager
|
|
1439
1439
|
*
|
|
1440
1440
|
* @throws Logs error to console if placementAttributesMapping parsing fails
|
|
1441
|
-
*/return a.prototype.init=function(a,b,c,d,e,f){var g=a||{},h
|
|
1441
|
+
*/return a.prototype.init=function(a,b,c,d,e,f){var g,h=a||{},i=h.userAttributeFilters,j=h.settings,k=j||{},l=k.placementAttributesMapping,m=k.hashedEmailUserIdentityType;this.mappedEmailShaIdentityType=null!==(g=null===m||void 0===m?void 0:m.toLowerCase())&&void 0!==g?g:null,this.identityService=c,this.store=d,this.logger=e,this.filters={userAttributeFilters:i,filterUserAttributes:KitFilterHelper.filterUserAttributes,filteredUser:b};try{this.placementAttributesMapping=parseSettingsString(l);}catch(a){this.logger.error("Error parsing placement attributes mapping from config: "+a);}// This is the global setting for sandbox mode
|
|
1442
1442
|
// It is set here and passed in to the createLauncher method in the Rokt Kit
|
|
1443
1443
|
// This is not to be confused for the `sandbox` flag in the selectPlacements attributes
|
|
1444
1444
|
// as that is independent of this setting, though they share the same name.
|
|
1445
|
-
var
|
|
1445
|
+
var n=(null===f||void 0===f?void 0:f.sandbox)||!1;// Launcher options are set here for the kit to pick up and pass through
|
|
1446
1446
|
// to the Rokt Launcher.
|
|
1447
|
-
this.launcherOptions=__assign({sandbox:
|
|
1447
|
+
this.launcherOptions=__assign({sandbox:n},null===f||void 0===f?void 0:f.launcherOptions),(null===f||void 0===f?void 0:f.domain)&&(this.domain=f.domain);},a.prototype.attachKit=function(a){this.kit=a,this.processMessageQueue();},a.prototype.selectPlacements=function(a){var b,c;return __awaiter(this,void 0,void 0,function(){var d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s=this;return __generator(this,function(t){switch(t.label){case 0:if(!this.isReady())return [2/*return*/,this.deferredCall("selectPlacements",a)];t.label=1;case 1:if(t.trys.push([1,6,,7]),d=a.attributes,e=(null===d||void 0===d?void 0:d.sandbox)||null,f=this.mapPlacementAttributes(d,this.placementAttributesMapping),this.currentUser=this.identityService.getCurrentUser(),g=(null===(c=null===(b=this.currentUser)||void 0===b?void 0:b.getUserIdentities())||void 0===c?void 0:c.userIdentities)||{},h=g.email,i=f.email,j=void 0,k=void 0,this.mappedEmailShaIdentityType&&!1!==IdentityType.getIdentityType(this.mappedEmailShaIdentityType)&&(j=g[this.mappedEmailShaIdentityType],k=f.emailsha256||f[this.mappedEmailShaIdentityType]||void 0),l=this.hasIdentityChanged(h,i),m=this.hasIdentityChanged(j,k),n={},l&&(n.email=i,i&&this.logger.warning("Email mismatch detected. Current email differs from email passed to selectPlacements call. Proceeding to call identify with email from selectPlacements call. Please verify your implementation.")),m&&(n[this.mappedEmailShaIdentityType]=k,this.logger.warning("emailsha256 mismatch detected. Current mParticle hashedEmail differs from hashedEmail passed to selectPlacements call. Proceeding to call identify with hashedEmail from selectPlacements call. Please verify your implementation.")),!!isEmpty(n))return [3/*break*/,5];t.label=2;case 2:return t.trys.push([2,4,,5]),[4/*yield*/,new Promise(function(a){s.identityService.identify({userIdentities:__assign(__assign({},g),n)},function(){a();});})];case 3:return t.sent(),[3/*break*/,5];case 4:return o=t.sent(),this.logger.error("Failed to identify user with new email: "+JSON.stringify(o)),[3/*break*/,5];case 5:return this.setUserAttributes(f),p=__assign(__assign({},f),null===e?{}:{sandbox:e}),q=__assign(__assign({},a),{attributes:p}),[2/*return*/,this.kit.selectPlacements(q)];case 6:return r=t.sent(),[2/*return*/,Promise.reject(r instanceof Error?r:new Error("Unknown error occurred"))];case 7:return [2/*return*/]}})})},a.prototype.hashAttributes=function(a){if(!this.isReady())return this.deferredCall("hashAttributes",a);try{return this.kit.hashAttributes(a)}catch(a){return Promise.reject(a instanceof Error?a:new Error("Unknown error occurred"))}},a.prototype.setExtensionData=function(a){if(!this.isReady())return void this.deferredCall("setExtensionData",a);try{this.kit.setExtensionData(a);}catch(a){var b=a instanceof Error?a.message:a+"";throw new Error("Error setting extension data: "+b)}},a.prototype.use=function(a){if(!this.isReady())return this.deferredCall("use",a);try{return this.kit.use(a)}catch(b){return Promise.reject(b instanceof Error?b:new Error("Error using extension: "+a))}},a.prototype.getLocalSessionAttributes=function(){return this.store.getLocalSessionAttributes()},a.prototype.setLocalSessionAttribute=function(a,b){this.store.setLocalSessionAttribute(a,b);},a.prototype.isReady=function(){// The Rokt Manager is ready when a kit is attached and has a launcher
|
|
1448
1448
|
return !!(this.kit&&this.kit.launcher)},a.prototype.setUserAttributes=function(a){var b=["sandbox"],c={};for(var d in a)a.hasOwnProperty(d)&&-1===b.indexOf(d)&&(c[d]=a[d]);try{this.currentUser.setUserAttributes(c);}catch(a){this.logger.error("Error setting user attributes: "+a);}},a.prototype.mapPlacementAttributes=function(a,b){for(var c,d={},e=0,f=b;e<f.length;e++)c=f[e],d[c.map]=c.value;var g={};for(var h in a)if(a.hasOwnProperty(h)){var i=d[h]||h;g[i]=a[h];}return g},a.prototype.processMessageQueue=function(){var a,b=this;this.isReady()&&0!==this.messageQueue.size&&(// Clear the queue after processing all messages
|
|
1449
|
-
null===(a=this.logger)||void 0===a?void 0:a.verbose("RoktManager: Processing ".concat(this.messageQueue.size," queued messages")),this.messageQueue.forEach(function(a){var c,d,e;if(!(a.methodName in b)||!isFunction(b[a.methodName]))return void(null===(c=b.logger)||void 0===c?void 0:c.error("RoktManager: Method ".concat(a.methodName," not found")));null===(d=b.logger)||void 0===d?void 0:d.verbose("RoktManager: Processing queued message: ".concat(a.methodName," with payload: ").concat(JSON.stringify(a.payload)));try{var f=b[a.methodName](a.payload);b.completePendingPromise(a.messageId,f);}catch(c){var g=c instanceof Error?c.message:c+"";null===(e=b.logger)||void 0===e?void 0:e.error("RoktManager: Error processing message '".concat(a.methodName,"': ").concat(g)),b.completePendingPromise(a.messageId,Promise.reject(c));}}),this.messageQueue.clear());},a.prototype.queueMessage=function(a){this.messageQueue.set(a.messageId,a);},a.prototype.deferredCall=function(a,b){var c=this;return new Promise(function(d,e){var f="".concat(a,"_").concat(generateUniqueId());c.queueMessage({messageId:f,methodName:a,payload:b,resolve:d,reject:e});})},a.prototype.completePendingPromise=function(a,b){if(a&&this.messageQueue.has(a)){var c=this.messageQueue.get(a);c.resolve&&Promise.resolve(b).then(function(a){return c.resolve(a)})["catch"](function(a){return c.reject(a)}),this.messageQueue["delete"](a);}},a
|
|
1449
|
+
null===(a=this.logger)||void 0===a?void 0:a.verbose("RoktManager: Processing ".concat(this.messageQueue.size," queued messages")),this.messageQueue.forEach(function(a){var c,d,e;if(!(a.methodName in b)||!isFunction(b[a.methodName]))return void(null===(c=b.logger)||void 0===c?void 0:c.error("RoktManager: Method ".concat(a.methodName," not found")));null===(d=b.logger)||void 0===d?void 0:d.verbose("RoktManager: Processing queued message: ".concat(a.methodName," with payload: ").concat(JSON.stringify(a.payload)));try{var f=b[a.methodName](a.payload);b.completePendingPromise(a.messageId,f);}catch(c){var g=c instanceof Error?c.message:c+"";null===(e=b.logger)||void 0===e?void 0:e.error("RoktManager: Error processing message '".concat(a.methodName,"': ").concat(g)),b.completePendingPromise(a.messageId,Promise.reject(c));}}),this.messageQueue.clear());},a.prototype.queueMessage=function(a){this.messageQueue.set(a.messageId,a);},a.prototype.deferredCall=function(a,b){var c=this;return new Promise(function(d,e){var f="".concat(a,"_").concat(generateUniqueId());c.queueMessage({messageId:f,methodName:a,payload:b,resolve:d,reject:e});})},a.prototype.completePendingPromise=function(a,b){if(a&&this.messageQueue.has(a)){var c=this.messageQueue.get(a);c.resolve&&Promise.resolve(b).then(function(a){return c.resolve(a)})["catch"](function(a){return c.reject(a)}),this.messageQueue["delete"](a);}},a.prototype.hasIdentityChanged=function(a,b){return !!b&&(!a||a!==b);// Values are the same
|
|
1450
|
+
},a}();
|
|
1450
1451
|
|
|
1451
1452
|
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;/**
|
|
1452
1453
|
* <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>
|
package/dist/mparticle.esm.js
CHANGED
|
@@ -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.
|
|
18
|
+
var version = "2.50.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
|
|
@@ -1438,15 +1438,16 @@ var RoktManager=/** @class */function(){function a(){this.kit=null,this.filters=
|
|
|
1438
1438
|
* @param {IRoktOptions} options - Options for the RoktManager
|
|
1439
1439
|
*
|
|
1440
1440
|
* @throws Logs error to console if placementAttributesMapping parsing fails
|
|
1441
|
-
*/return a.prototype.init=function(a,b,c,d,e,f){var g=a||{},h
|
|
1441
|
+
*/return a.prototype.init=function(a,b,c,d,e,f){var g,h=a||{},i=h.userAttributeFilters,j=h.settings,k=j||{},l=k.placementAttributesMapping,m=k.hashedEmailUserIdentityType;this.mappedEmailShaIdentityType=null!==(g=null===m||void 0===m?void 0:m.toLowerCase())&&void 0!==g?g:null,this.identityService=c,this.store=d,this.logger=e,this.filters={userAttributeFilters:i,filterUserAttributes:KitFilterHelper.filterUserAttributes,filteredUser:b};try{this.placementAttributesMapping=parseSettingsString(l);}catch(a){this.logger.error("Error parsing placement attributes mapping from config: "+a);}// This is the global setting for sandbox mode
|
|
1442
1442
|
// It is set here and passed in to the createLauncher method in the Rokt Kit
|
|
1443
1443
|
// This is not to be confused for the `sandbox` flag in the selectPlacements attributes
|
|
1444
1444
|
// as that is independent of this setting, though they share the same name.
|
|
1445
|
-
var
|
|
1445
|
+
var n=(null===f||void 0===f?void 0:f.sandbox)||!1;// Launcher options are set here for the kit to pick up and pass through
|
|
1446
1446
|
// to the Rokt Launcher.
|
|
1447
|
-
this.launcherOptions=__assign({sandbox:
|
|
1447
|
+
this.launcherOptions=__assign({sandbox:n},null===f||void 0===f?void 0:f.launcherOptions),(null===f||void 0===f?void 0:f.domain)&&(this.domain=f.domain);},a.prototype.attachKit=function(a){this.kit=a,this.processMessageQueue();},a.prototype.selectPlacements=function(a){var b,c;return __awaiter(this,void 0,void 0,function(){var d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s=this;return __generator(this,function(t){switch(t.label){case 0:if(!this.isReady())return [2/*return*/,this.deferredCall("selectPlacements",a)];t.label=1;case 1:if(t.trys.push([1,6,,7]),d=a.attributes,e=(null===d||void 0===d?void 0:d.sandbox)||null,f=this.mapPlacementAttributes(d,this.placementAttributesMapping),this.currentUser=this.identityService.getCurrentUser(),g=(null===(c=null===(b=this.currentUser)||void 0===b?void 0:b.getUserIdentities())||void 0===c?void 0:c.userIdentities)||{},h=g.email,i=f.email,j=void 0,k=void 0,this.mappedEmailShaIdentityType&&!1!==IdentityType.getIdentityType(this.mappedEmailShaIdentityType)&&(j=g[this.mappedEmailShaIdentityType],k=f.emailsha256||f[this.mappedEmailShaIdentityType]||void 0),l=this.hasIdentityChanged(h,i),m=this.hasIdentityChanged(j,k),n={},l&&(n.email=i,i&&this.logger.warning("Email mismatch detected. Current email differs from email passed to selectPlacements call. Proceeding to call identify with email from selectPlacements call. Please verify your implementation.")),m&&(n[this.mappedEmailShaIdentityType]=k,this.logger.warning("emailsha256 mismatch detected. Current mParticle hashedEmail differs from hashedEmail passed to selectPlacements call. Proceeding to call identify with hashedEmail from selectPlacements call. Please verify your implementation.")),!!isEmpty(n))return [3/*break*/,5];t.label=2;case 2:return t.trys.push([2,4,,5]),[4/*yield*/,new Promise(function(a){s.identityService.identify({userIdentities:__assign(__assign({},g),n)},function(){a();});})];case 3:return t.sent(),[3/*break*/,5];case 4:return o=t.sent(),this.logger.error("Failed to identify user with new email: "+JSON.stringify(o)),[3/*break*/,5];case 5:return this.setUserAttributes(f),p=__assign(__assign({},f),null===e?{}:{sandbox:e}),q=__assign(__assign({},a),{attributes:p}),[2/*return*/,this.kit.selectPlacements(q)];case 6:return r=t.sent(),[2/*return*/,Promise.reject(r instanceof Error?r:new Error("Unknown error occurred"))];case 7:return [2/*return*/]}})})},a.prototype.hashAttributes=function(a){if(!this.isReady())return this.deferredCall("hashAttributes",a);try{return this.kit.hashAttributes(a)}catch(a){return Promise.reject(a instanceof Error?a:new Error("Unknown error occurred"))}},a.prototype.setExtensionData=function(a){if(!this.isReady())return void this.deferredCall("setExtensionData",a);try{this.kit.setExtensionData(a);}catch(a){var b=a instanceof Error?a.message:a+"";throw new Error("Error setting extension data: "+b)}},a.prototype.use=function(a){if(!this.isReady())return this.deferredCall("use",a);try{return this.kit.use(a)}catch(b){return Promise.reject(b instanceof Error?b:new Error("Error using extension: "+a))}},a.prototype.getLocalSessionAttributes=function(){return this.store.getLocalSessionAttributes()},a.prototype.setLocalSessionAttribute=function(a,b){this.store.setLocalSessionAttribute(a,b);},a.prototype.isReady=function(){// The Rokt Manager is ready when a kit is attached and has a launcher
|
|
1448
1448
|
return !!(this.kit&&this.kit.launcher)},a.prototype.setUserAttributes=function(a){var b=["sandbox"],c={};for(var d in a)a.hasOwnProperty(d)&&-1===b.indexOf(d)&&(c[d]=a[d]);try{this.currentUser.setUserAttributes(c);}catch(a){this.logger.error("Error setting user attributes: "+a);}},a.prototype.mapPlacementAttributes=function(a,b){for(var c,d={},e=0,f=b;e<f.length;e++)c=f[e],d[c.map]=c.value;var g={};for(var h in a)if(a.hasOwnProperty(h)){var i=d[h]||h;g[i]=a[h];}return g},a.prototype.processMessageQueue=function(){var a,b=this;this.isReady()&&0!==this.messageQueue.size&&(// Clear the queue after processing all messages
|
|
1449
|
-
null===(a=this.logger)||void 0===a?void 0:a.verbose("RoktManager: Processing ".concat(this.messageQueue.size," queued messages")),this.messageQueue.forEach(function(a){var c,d,e;if(!(a.methodName in b)||!isFunction(b[a.methodName]))return void(null===(c=b.logger)||void 0===c?void 0:c.error("RoktManager: Method ".concat(a.methodName," not found")));null===(d=b.logger)||void 0===d?void 0:d.verbose("RoktManager: Processing queued message: ".concat(a.methodName," with payload: ").concat(JSON.stringify(a.payload)));try{var f=b[a.methodName](a.payload);b.completePendingPromise(a.messageId,f);}catch(c){var g=c instanceof Error?c.message:c+"";null===(e=b.logger)||void 0===e?void 0:e.error("RoktManager: Error processing message '".concat(a.methodName,"': ").concat(g)),b.completePendingPromise(a.messageId,Promise.reject(c));}}),this.messageQueue.clear());},a.prototype.queueMessage=function(a){this.messageQueue.set(a.messageId,a);},a.prototype.deferredCall=function(a,b){var c=this;return new Promise(function(d,e){var f="".concat(a,"_").concat(generateUniqueId());c.queueMessage({messageId:f,methodName:a,payload:b,resolve:d,reject:e});})},a.prototype.completePendingPromise=function(a,b){if(a&&this.messageQueue.has(a)){var c=this.messageQueue.get(a);c.resolve&&Promise.resolve(b).then(function(a){return c.resolve(a)})["catch"](function(a){return c.reject(a)}),this.messageQueue["delete"](a);}},a
|
|
1449
|
+
null===(a=this.logger)||void 0===a?void 0:a.verbose("RoktManager: Processing ".concat(this.messageQueue.size," queued messages")),this.messageQueue.forEach(function(a){var c,d,e;if(!(a.methodName in b)||!isFunction(b[a.methodName]))return void(null===(c=b.logger)||void 0===c?void 0:c.error("RoktManager: Method ".concat(a.methodName," not found")));null===(d=b.logger)||void 0===d?void 0:d.verbose("RoktManager: Processing queued message: ".concat(a.methodName," with payload: ").concat(JSON.stringify(a.payload)));try{var f=b[a.methodName](a.payload);b.completePendingPromise(a.messageId,f);}catch(c){var g=c instanceof Error?c.message:c+"";null===(e=b.logger)||void 0===e?void 0:e.error("RoktManager: Error processing message '".concat(a.methodName,"': ").concat(g)),b.completePendingPromise(a.messageId,Promise.reject(c));}}),this.messageQueue.clear());},a.prototype.queueMessage=function(a){this.messageQueue.set(a.messageId,a);},a.prototype.deferredCall=function(a,b){var c=this;return new Promise(function(d,e){var f="".concat(a,"_").concat(generateUniqueId());c.queueMessage({messageId:f,methodName:a,payload:b,resolve:d,reject:e});})},a.prototype.completePendingPromise=function(a,b){if(a&&this.messageQueue.has(a)){var c=this.messageQueue.get(a);c.resolve&&Promise.resolve(b).then(function(a){return c.resolve(a)})["catch"](function(a){return c.reject(a)}),this.messageQueue["delete"](a);}},a.prototype.hasIdentityChanged=function(a,b){return !!b&&(!a||a!==b);// Values are the same
|
|
1450
|
+
},a}();
|
|
1450
1451
|
|
|
1451
1452
|
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;/**
|
|
1452
1453
|
* <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>
|
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.
|
|
206
|
+
var version = "2.50.0";
|
|
207
207
|
|
|
208
208
|
var Constants = {
|
|
209
209
|
sdkVersion: version,
|
|
@@ -9709,10 +9709,14 @@ var mParticle = (function () {
|
|
|
9709
9709
|
* @throws Logs error to console if placementAttributesMapping parsing fails
|
|
9710
9710
|
*/
|
|
9711
9711
|
RoktManager.prototype.init = function (roktConfig, filteredUser, identityService, store, logger, options) {
|
|
9712
|
-
var _a
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9712
|
+
var _a;
|
|
9713
|
+
var _b = roktConfig || {},
|
|
9714
|
+
userAttributeFilters = _b.userAttributeFilters,
|
|
9715
|
+
settings = _b.settings;
|
|
9716
|
+
var _c = settings || {},
|
|
9717
|
+
placementAttributesMapping = _c.placementAttributesMapping,
|
|
9718
|
+
hashedEmailUserIdentityType = _c.hashedEmailUserIdentityType;
|
|
9719
|
+
this.mappedEmailShaIdentityType = (_a = hashedEmailUserIdentityType === null || hashedEmailUserIdentityType === void 0 ? void 0 : hashedEmailUserIdentityType.toLowerCase()) !== null && _a !== void 0 ? _a : null;
|
|
9716
9720
|
this.identityService = identityService;
|
|
9717
9721
|
this.store = store;
|
|
9718
9722
|
this.logger = logger;
|
|
@@ -9762,7 +9766,7 @@ var mParticle = (function () {
|
|
|
9762
9766
|
RoktManager.prototype.selectPlacements = function (options) {
|
|
9763
9767
|
var _a, _b;
|
|
9764
9768
|
return __awaiter(this, void 0, void 0, function () {
|
|
9765
|
-
var attributes, sandboxValue, mappedAttributes, currentUserIdentities_1, currentEmail,
|
|
9769
|
+
var attributes, sandboxValue, mappedAttributes, currentUserIdentities_1, currentEmail, newEmail, currentHashedEmail, newHashedEmail, emailChanged, hashedEmailChanged, newIdentities_1, error_1, enrichedAttributes, enrichedOptions, error_2;
|
|
9766
9770
|
var _this = this;
|
|
9767
9771
|
return __generator(this, function (_c) {
|
|
9768
9772
|
switch (_c.label) {
|
|
@@ -9780,19 +9784,34 @@ var mParticle = (function () {
|
|
|
9780
9784
|
this.currentUser = this.identityService.getCurrentUser();
|
|
9781
9785
|
currentUserIdentities_1 = ((_b = (_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.getUserIdentities()) === null || _b === void 0 ? void 0 : _b.userIdentities) || {};
|
|
9782
9786
|
currentEmail = currentUserIdentities_1.email;
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
9786
|
-
|
|
9787
|
+
newEmail = mappedAttributes.email;
|
|
9788
|
+
currentHashedEmail = void 0;
|
|
9789
|
+
newHashedEmail = void 0;
|
|
9790
|
+
// Hashed email identity is valid if it is set to Other-Other10
|
|
9791
|
+
if (this.mappedEmailShaIdentityType && IdentityType.getIdentityType(this.mappedEmailShaIdentityType) !== false) {
|
|
9792
|
+
currentHashedEmail = currentUserIdentities_1[this.mappedEmailShaIdentityType];
|
|
9793
|
+
newHashedEmail = mappedAttributes['emailsha256'] || mappedAttributes[this.mappedEmailShaIdentityType] || undefined;
|
|
9794
|
+
}
|
|
9795
|
+
emailChanged = this.hasIdentityChanged(currentEmail, newEmail);
|
|
9796
|
+
hashedEmailChanged = this.hasIdentityChanged(currentHashedEmail, newHashedEmail);
|
|
9797
|
+
newIdentities_1 = {};
|
|
9798
|
+
if (emailChanged) {
|
|
9799
|
+
newIdentities_1.email = newEmail;
|
|
9800
|
+
if (newEmail) {
|
|
9801
|
+
this.logger.warning("Email mismatch detected. Current email differs from email passed to selectPlacements call. Proceeding to call identify with email from selectPlacements call. Please verify your implementation.");
|
|
9802
|
+
}
|
|
9803
|
+
}
|
|
9804
|
+
if (hashedEmailChanged) {
|
|
9805
|
+
newIdentities_1[this.mappedEmailShaIdentityType] = newHashedEmail;
|
|
9806
|
+
this.logger.warning("emailsha256 mismatch detected. Current mParticle hashedEmail differs from hashedEmail passed to selectPlacements call. Proceeding to call identify with hashedEmail from selectPlacements call. Please verify your implementation.");
|
|
9787
9807
|
}
|
|
9808
|
+
if (!!isEmpty(newIdentities_1)) return [3 /*break*/, 5];
|
|
9788
9809
|
_c.label = 2;
|
|
9789
9810
|
case 2:
|
|
9790
9811
|
_c.trys.push([2, 4,, 5]);
|
|
9791
9812
|
return [4 /*yield*/, new Promise(function (resolve, reject) {
|
|
9792
9813
|
_this.identityService.identify({
|
|
9793
|
-
userIdentities: __assign(__assign({}, currentUserIdentities_1),
|
|
9794
|
-
email: newEmail_1
|
|
9795
|
-
})
|
|
9814
|
+
userIdentities: __assign(__assign({}, currentUserIdentities_1), newIdentities_1)
|
|
9796
9815
|
}, function () {
|
|
9797
9816
|
resolve();
|
|
9798
9817
|
});
|
|
@@ -9950,6 +9969,28 @@ var mParticle = (function () {
|
|
|
9950
9969
|
}
|
|
9951
9970
|
this.messageQueue["delete"](messageId);
|
|
9952
9971
|
};
|
|
9972
|
+
/**
|
|
9973
|
+
* Checks if an identity value has changed by comparing current and new values
|
|
9974
|
+
*
|
|
9975
|
+
* @param {string | undefined} currentValue - The current identity value
|
|
9976
|
+
* @param {string | undefined} newValue - The new identity value to compare against
|
|
9977
|
+
* @returns {boolean} True if the identity has changed (new value exists and differs from current), false otherwise
|
|
9978
|
+
*/
|
|
9979
|
+
RoktManager.prototype.hasIdentityChanged = function (currentValue, newValue) {
|
|
9980
|
+
if (!newValue) {
|
|
9981
|
+
return false;
|
|
9982
|
+
}
|
|
9983
|
+
if (!currentValue) {
|
|
9984
|
+
return true; // New value exists but no current value
|
|
9985
|
+
}
|
|
9986
|
+
|
|
9987
|
+
if (currentValue !== newValue) {
|
|
9988
|
+
return true; // Values are different
|
|
9989
|
+
}
|
|
9990
|
+
|
|
9991
|
+
return false; // Values are the same
|
|
9992
|
+
};
|
|
9993
|
+
|
|
9953
9994
|
return RoktManager;
|
|
9954
9995
|
}();
|
|
9955
9996
|
|
package/package.json
CHANGED
package/src/roktManager.ts
CHANGED
|
@@ -8,10 +8,13 @@ import {
|
|
|
8
8
|
generateUniqueId,
|
|
9
9
|
isFunction,
|
|
10
10
|
AttributeValue,
|
|
11
|
+
isEmpty,
|
|
11
12
|
} from "./utils";
|
|
12
13
|
import { SDKIdentityApi } from "./identity.interfaces";
|
|
13
14
|
import { SDKLoggerApi } from "./sdkRuntimeModels";
|
|
14
15
|
import { IStore, LocalSessionAttributes } from "./store";
|
|
16
|
+
import { UserIdentities } from "@mparticle/web-sdk";
|
|
17
|
+
import { IdentityType } from "./types";
|
|
15
18
|
|
|
16
19
|
// https://docs.rokt.com/developers/integration-guides/web/library/attributes
|
|
17
20
|
export interface IRoktPartnerAttributes {
|
|
@@ -96,6 +99,7 @@ export default class RoktManager {
|
|
|
96
99
|
private launcherOptions?: IRoktLauncherOptions;
|
|
97
100
|
private logger: SDKLoggerApi;
|
|
98
101
|
private domain?: string;
|
|
102
|
+
private mappedEmailShaIdentityType?: string | null;
|
|
99
103
|
/**
|
|
100
104
|
* Initializes the RoktManager with configuration settings and user data.
|
|
101
105
|
*
|
|
@@ -116,7 +120,8 @@ export default class RoktManager {
|
|
|
116
120
|
options?: IRoktOptions
|
|
117
121
|
): void {
|
|
118
122
|
const { userAttributeFilters, settings } = roktConfig || {};
|
|
119
|
-
const { placementAttributesMapping } = settings || {};
|
|
123
|
+
const { placementAttributesMapping, hashedEmailUserIdentityType } = settings || {};
|
|
124
|
+
this.mappedEmailShaIdentityType = hashedEmailUserIdentityType?.toLowerCase() ?? null;
|
|
120
125
|
|
|
121
126
|
this.identityService = identityService;
|
|
122
127
|
this.store = store;
|
|
@@ -182,23 +187,43 @@ export default class RoktManager {
|
|
|
182
187
|
// Get current user identities
|
|
183
188
|
this.currentUser = this.identityService.getCurrentUser();
|
|
184
189
|
const currentUserIdentities = this.currentUser?.getUserIdentities()?.userIdentities || {};
|
|
190
|
+
|
|
185
191
|
const currentEmail = currentUserIdentities.email;
|
|
186
192
|
const newEmail = mappedAttributes.email as string;
|
|
187
193
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
194
|
+
let currentHashedEmail: string | undefined;
|
|
195
|
+
let newHashedEmail: string | undefined;
|
|
196
|
+
|
|
197
|
+
// Hashed email identity is valid if it is set to Other-Other10
|
|
198
|
+
if(this.mappedEmailShaIdentityType && IdentityType.getIdentityType(this.mappedEmailShaIdentityType) !== false) {
|
|
199
|
+
currentHashedEmail = currentUserIdentities[this.mappedEmailShaIdentityType];
|
|
200
|
+
newHashedEmail = mappedAttributes['emailsha256'] as string || mappedAttributes[this.mappedEmailShaIdentityType] as string || undefined;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const emailChanged = this.hasIdentityChanged(currentEmail, newEmail);
|
|
204
|
+
const hashedEmailChanged = this.hasIdentityChanged(currentHashedEmail, newHashedEmail);
|
|
205
|
+
|
|
206
|
+
const newIdentities: UserIdentities = {};
|
|
207
|
+
if (emailChanged) {
|
|
208
|
+
newIdentities.email = newEmail;
|
|
209
|
+
if (newEmail) {
|
|
210
|
+
this.logger.warning(`Email mismatch detected. Current email differs from email passed to selectPlacements call. Proceeding to call identify with email from selectPlacements call. Please verify your implementation.`);
|
|
193
211
|
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (hashedEmailChanged) {
|
|
215
|
+
newIdentities[this.mappedEmailShaIdentityType] = newHashedEmail;
|
|
216
|
+
this.logger.warning(`emailsha256 mismatch detected. Current mParticle hashedEmail differs from hashedEmail passed to selectPlacements call. Proceeding to call identify with hashedEmail from selectPlacements call. Please verify your implementation.`);
|
|
217
|
+
}
|
|
194
218
|
|
|
219
|
+
if (!isEmpty(newIdentities)) {
|
|
195
220
|
// Call identify with the new user identities
|
|
196
221
|
try {
|
|
197
222
|
await new Promise<void>((resolve, reject) => {
|
|
198
223
|
this.identityService.identify({
|
|
199
224
|
userIdentities: {
|
|
200
225
|
...currentUserIdentities,
|
|
201
|
-
|
|
226
|
+
...newIdentities
|
|
202
227
|
}
|
|
203
228
|
}, () => {
|
|
204
229
|
resolve();
|
|
@@ -321,6 +346,7 @@ export default class RoktManager {
|
|
|
321
346
|
this.messageQueue.forEach((message) => {
|
|
322
347
|
if(!(message.methodName in this) || !isFunction(this[message.methodName])) {
|
|
323
348
|
this.logger?.error(`RoktManager: Method ${message.methodName} not found`);
|
|
349
|
+
|
|
324
350
|
return;
|
|
325
351
|
}
|
|
326
352
|
|
|
@@ -373,4 +399,27 @@ export default class RoktManager {
|
|
|
373
399
|
|
|
374
400
|
this.messageQueue.delete(messageId);
|
|
375
401
|
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Checks if an identity value has changed by comparing current and new values
|
|
405
|
+
*
|
|
406
|
+
* @param {string | undefined} currentValue - The current identity value
|
|
407
|
+
* @param {string | undefined} newValue - The new identity value to compare against
|
|
408
|
+
* @returns {boolean} True if the identity has changed (new value exists and differs from current), false otherwise
|
|
409
|
+
*/
|
|
410
|
+
private hasIdentityChanged(currentValue: string | undefined, newValue: string | undefined): boolean {
|
|
411
|
+
if (!newValue) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (!currentValue) {
|
|
416
|
+
return true; // New value exists but no current value
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (currentValue !== newValue) {
|
|
420
|
+
return true; // Values are different
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return false; // Values are the same
|
|
424
|
+
}
|
|
376
425
|
}
|