From 2578b9a30aa4d54cf82631fa4a6ef5171c87eb3a Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Thu, 21 May 2026 20:38:31 -0400 Subject: [PATCH 01/11] feat: Report deprecated method usage through logging dispatcher (#1270) --- src/consent.ts | 10 ++- src/identity.js | 110 +++++++++++++++++------- src/mp-instance.ts | 86 ++++++++++++------ src/reporting/deprecatedMethodLogger.ts | 19 ++++ src/reporting/types.ts | 1 + src/sessionManager.ts | 18 ++-- test/jest/reportingLogger.spec.ts | 41 +++++++++ 7 files changed, 216 insertions(+), 69 deletions(-) create mode 100644 src/reporting/deprecatedMethodLogger.ts diff --git a/src/consent.ts b/src/consent.ts index 81ae944cd..bcf1f6258 100644 --- a/src/consent.ts +++ b/src/consent.ts @@ -9,6 +9,7 @@ import KitFilterHelper from './kitFilterHelper'; import Constants from './constants'; import { IMParticleUser } from './identity-user-interfaces'; import { IMParticleWebSDKInstance } from './mp-instance'; +import { logDeprecatedMethodUsage } from './reporting/deprecatedMethodLogger'; const { CCPAPurpose } = Constants; @@ -505,8 +506,13 @@ export default function Consent(this: IConsent, mpInstance: IMParticleWebSDKInst // TODO: Can we remove this? It is deprecated. function removeCCPAState(this: ConsentState) { - mpInstance.Logger.warning( - 'removeCCPAState is deprecated and will be removed in a future release; use removeCCPAConsentState instead' + logDeprecatedMethodUsage( + { + methodName: 'Consent.removeCCPAState', + warningMessage: 'removeCCPAState is deprecated and will be removed in a future release; use removeCCPAConsentState instead', + }, + mpInstance.Logger, + mpInstance._LoggingDispatcher ); // @ts-ignore return removeCCPAConsentState(); diff --git a/src/identity.js b/src/identity.js index 29c9dacb9..c9e5dbe4d 100644 --- a/src/identity.js +++ b/src/identity.js @@ -20,6 +20,7 @@ import { } from './utils'; import { hasMPIDAndUserLoginChanged, hasMPIDChanged } from './user-utils'; import { processReadyQueue } from './pre-init-utils'; +import { logDeprecatedMethodUsage } from './reporting/deprecatedMethodLogger'; export default function Identity(mpInstance) { const { getFeatureFlag, extend } = mpInstance._Helpers; @@ -1252,8 +1253,14 @@ export default function Identity(mpInstance) { * @return a cart object */ getCart: function() { - mpInstance.Logger.warning( - 'Deprecated function Identity.getCurrentUser().getCart() will be removed in future releases' + logDeprecatedMethodUsage( + { + methodName: 'Identity.getCurrentUser().getCart()', + warningMessage: + 'Deprecated function Identity.getCurrentUser().getCart() will be removed in future releases', + }, + mpInstance.Logger, + mpInstance._LoggingDispatcher ); return self.mParticleUserCart(); }, @@ -1334,13 +1341,18 @@ export default function Identity(mpInstance) { * @deprecated */ add: function() { - mpInstance.Logger.warning( - generateDeprecationMessage( - 'Identity.getCurrentUser().getCart().add()', - true, - 'eCommerce.logProductAction()', - 'https://docs.mparticle.com/developers/sdk/web/commerce-tracking' - ) + logDeprecatedMethodUsage( + { + methodName: 'Identity.getCurrentUser().getCart().add()', + warningMessage: generateDeprecationMessage( + 'Identity.getCurrentUser().getCart().add()', + true, + 'eCommerce.logProductAction()', + 'https://docs.mparticle.com/developers/sdk/web/commerce-tracking' + ), + }, + mpInstance.Logger, + mpInstance._LoggingDispatcher ); }, /** @@ -1349,13 +1361,19 @@ export default function Identity(mpInstance) { * @deprecated */ remove: function() { - mpInstance.Logger.warning( - generateDeprecationMessage( - 'Identity.getCurrentUser().getCart().remove()', - true, - 'eCommerce.logProductAction()', - 'https://docs.mparticle.com/developers/sdk/web/commerce-tracking' - ) + logDeprecatedMethodUsage( + { + methodName: + 'Identity.getCurrentUser().getCart().remove()', + warningMessage: generateDeprecationMessage( + 'Identity.getCurrentUser().getCart().remove()', + true, + 'eCommerce.logProductAction()', + 'https://docs.mparticle.com/developers/sdk/web/commerce-tracking' + ), + }, + mpInstance.Logger, + mpInstance._LoggingDispatcher ); }, /** @@ -1364,13 +1382,19 @@ export default function Identity(mpInstance) { * @deprecated */ clear: function() { - mpInstance.Logger.warning( - generateDeprecationMessage( - 'Identity.getCurrentUser().getCart().clear()', - true, - '', - 'https://docs.mparticle.com/developers/sdk/web/commerce-tracking' - ) + logDeprecatedMethodUsage( + { + methodName: + 'Identity.getCurrentUser().getCart().clear()', + warningMessage: generateDeprecationMessage( + 'Identity.getCurrentUser().getCart().clear()', + true, + '', + 'https://docs.mparticle.com/developers/sdk/web/commerce-tracking' + ), + }, + mpInstance.Logger, + mpInstance._LoggingDispatcher ); }, /** @@ -1380,13 +1404,19 @@ export default function Identity(mpInstance) { * @deprecated */ getCartProducts: function() { - mpInstance.Logger.warning( - generateDeprecationMessage( - 'Identity.getCurrentUser().getCart().getCartProducts()', - true, - 'eCommerce.logProductAction()', - 'https://docs.mparticle.com/developers/sdk/web/commerce-tracking' - ) + logDeprecatedMethodUsage( + { + methodName: + 'Identity.getCurrentUser().getCart().getCartProducts()', + warningMessage: generateDeprecationMessage( + 'Identity.getCurrentUser().getCart().getCartProducts()', + true, + 'eCommerce.logProductAction()', + 'https://docs.mparticle.com/developers/sdk/web/commerce-tracking' + ), + }, + mpInstance.Logger, + mpInstance._LoggingDispatcher ); return []; }, @@ -1540,7 +1570,8 @@ export default function Identity(mpInstance) { prevUser, newUser, identityApiData, - mpInstance.Logger + mpInstance.Logger, + mpInstance._LoggingDispatcher ); const persistence = mpInstance._Persistence.getPersistence(); @@ -1775,14 +1806,27 @@ export default function Identity(mpInstance) { } // https://go.mparticle.com/work/SQDSDKS-6359 -function tryOnUserAlias(previousUser, newUser, identityApiData, logger) { +function tryOnUserAlias( + previousUser, + newUser, + identityApiData, + logger, + loggingDispatcher +) { if ( identityApiData && identityApiData.onUserAlias && isFunction(identityApiData.onUserAlias) ) { try { - logger.warning(generateDeprecationMessage('onUserAlias')); + logDeprecatedMethodUsage( + { + methodName: 'onUserAlias', + warningMessage: generateDeprecationMessage('onUserAlias'), + }, + logger, + loggingDispatcher + ); identityApiData.onUserAlias(previousUser, newUser); } catch (e) { logger.error( diff --git a/src/mp-instance.ts b/src/mp-instance.ts index 618b19ab3..4b9078573 100644 --- a/src/mp-instance.ts +++ b/src/mp-instance.ts @@ -55,6 +55,7 @@ import CookieConsentManager, { ICookieConsentManager } from './cookieConsentMana import { ErrorReportingDispatcher } from './reporting/errorReportingDispatcher'; import { LoggingDispatcher } from './reporting/loggingDispatcher'; import { IErrorReportingService, ILoggingService } from './reporting/types'; +import { logDeprecatedMethodUsage } from './reporting/deprecatedMethodLogger'; export interface IErrorLogMessage { message?: string; @@ -772,13 +773,18 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan * @deprecated */ add: function(product, logEventBoolean) { - self.Logger.warning( - generateDeprecationMessage( - 'eCommerce.Cart.add()', - true, - 'eCommerce.logProductAction()', - 'https://docs.mparticle.com/developers/sdk/web/commerce-tracking' - ) + logDeprecatedMethodUsage( + { + methodName: 'mPInstance.eCommerce.Cart.add()', + warningMessage: generateDeprecationMessage( + 'eCommerce.Cart.add()', + true, + 'eCommerce.logProductAction()', + 'https://docs.mparticle.com/developers/sdk/web/commerce-tracking' + ), + }, + self.Logger, + self._LoggingDispatcher ); }, /** @@ -789,13 +795,18 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan * @deprecated */ remove: function(product, logEventBoolean) { - self.Logger.warning( - generateDeprecationMessage( - 'eCommerce.Cart.remove()', - true, - 'eCommerce.logProductAction()', - 'https://docs.mparticle.com/developers/sdk/web/commerce-tracking' - ) + logDeprecatedMethodUsage( + { + methodName: 'mPInstance.eCommerce.Cart.remove()', + warningMessage: generateDeprecationMessage( + 'eCommerce.Cart.remove()', + true, + 'eCommerce.logProductAction()', + 'https://docs.mparticle.com/developers/sdk/web/commerce-tracking' + ), + }, + self.Logger, + self._LoggingDispatcher ); }, /** @@ -804,13 +815,18 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan * @deprecated */ clear: function() { - self.Logger.warning( - generateDeprecationMessage( - 'eCommerce.Cart.clear()', - true, - '', - 'https://docs.mparticle.com/developers/sdk/web/commerce-tracking' - ) + logDeprecatedMethodUsage( + { + methodName: 'mPInstance.eCommerce.Cart.clear()', + warningMessage: generateDeprecationMessage( + 'eCommerce.Cart.clear()', + true, + '', + 'https://docs.mparticle.com/developers/sdk/web/commerce-tracking' + ), + }, + self.Logger, + self._LoggingDispatcher ); }, }, @@ -940,8 +956,13 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan * @deprecated */ logCheckout: function(step, option, attrs, customFlags) { - self.Logger.warning( - 'mParticle.logCheckout is deprecated, please use mParticle.logProductAction instead' + logDeprecatedMethodUsage( + { + methodName: 'mParticle.logCheckout', + warningMessage: 'mParticle.logCheckout is deprecated, please use mParticle.logProductAction instead', + }, + self.Logger, + self._LoggingDispatcher ); if (!self._Store.isInitialized) { @@ -1020,8 +1041,13 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan attrs, customFlags ) { - self.Logger.warning( - 'mParticle.logPurchase is deprecated, please use mParticle.logProductAction instead' + logDeprecatedMethodUsage( + { + methodName: 'mParticle.logPurchase', + warningMessage: 'mParticle.logPurchase is deprecated, please use mParticle.logProductAction instead', + }, + self.Logger, + self._LoggingDispatcher ); if (!self._Store.isInitialized) { self.ready(function() { @@ -1132,8 +1158,13 @@ export default function mParticleInstance(this: IMParticleWebSDKInstance, instan attrs, customFlags ) { - self.Logger.warning( - 'mParticle.logRefund is deprecated, please use mParticle.logProductAction instead' + logDeprecatedMethodUsage( + { + methodName: 'mParticle.logRefund', + warningMessage: 'mParticle.logRefund is deprecated, please use mParticle.logProductAction instead', + }, + self.Logger, + self._LoggingDispatcher ); if (!self._Store.isInitialized) { self.ready(function() { @@ -1741,4 +1772,3 @@ function queueIfNotInitialized(func, self) { }); return true; } - diff --git a/src/reporting/deprecatedMethodLogger.ts b/src/reporting/deprecatedMethodLogger.ts new file mode 100644 index 000000000..33a9b55ed --- /dev/null +++ b/src/reporting/deprecatedMethodLogger.ts @@ -0,0 +1,19 @@ +import { ErrorCodes, ILoggingService } from './types'; +import { SDKLoggerApi } from '../sdkRuntimeModels'; + +interface DeprecatedMethodUsage { + methodName: string; + warningMessage: string; +} + +export function logDeprecatedMethodUsage( + usage: DeprecatedMethodUsage, + logger: Pick, + loggingDispatcher: ILoggingService | undefined +): void { + logger.warning(usage.warningMessage); + loggingDispatcher?.log({ + message: usage.methodName, + code: ErrorCodes.MP_DEPRECATED_METHOD_USAGE, + }); +} diff --git a/src/reporting/types.ts b/src/reporting/types.ts index 7378ca8f0..ba06ef73d 100644 --- a/src/reporting/types.ts +++ b/src/reporting/types.ts @@ -6,6 +6,7 @@ export const ErrorCodes = { IDENTITY_REQUEST: 'IDENTITY_REQUEST', IDENTITY_MISMATCH: 'IDENTITY_MISMATCH', ROKT_KIT_ATTACHED: 'ROKT_KIT_ATTACHED', + MP_DEPRECATED_METHOD_USAGE: 'MP_DEPRECATED_METHOD_USAGE', } as const; export type ErrorCodes = valueof; diff --git a/src/sessionManager.ts b/src/sessionManager.ts index 4b6cf68a4..418c4507e 100644 --- a/src/sessionManager.ts +++ b/src/sessionManager.ts @@ -6,6 +6,7 @@ import { generateDeprecationMessage } from './utils'; import { IMParticleUser } from './identity-user-interfaces'; import { IMParticleWebSDKInstance } from './mp-instance'; import { hasIdentityRequestChanged, hasExplicitIdentifier } from './identity-utils'; +import { logDeprecatedMethodUsage } from './reporting/deprecatedMethodLogger'; const { Messages } = Constants; @@ -67,12 +68,17 @@ export default function SessionManager( }; this.getSession = function (): string { - mpInstance.Logger.warning( - generateDeprecationMessage( - 'SessionManager.getSession()', - false, - 'SessionManager.getSessionId()' - ) + logDeprecatedMethodUsage( + { + methodName: 'SessionManager.getSession()', + warningMessage: generateDeprecationMessage( + 'SessionManager.getSession()', + false, + 'SessionManager.getSessionId()' + ), + }, + mpInstance.Logger, + mpInstance._LoggingDispatcher ); return this.getSessionId(); }; diff --git a/test/jest/reportingLogger.spec.ts b/test/jest/reportingLogger.spec.ts index 82ee5204d..ca4e1d700 100644 --- a/test/jest/reportingLogger.spec.ts +++ b/test/jest/reportingLogger.spec.ts @@ -1,6 +1,7 @@ import { ErrorReportingDispatcher } from '../../src/reporting/errorReportingDispatcher'; import { LoggingDispatcher } from '../../src/reporting/loggingDispatcher'; import { IErrorReportingService, ILoggingService, ISDKError, ISDKLogEntry, WSDKErrorSeverity, ErrorCodes } from '../../src/reporting/types'; +import { logDeprecatedMethodUsage } from '../../src/reporting/deprecatedMethodLogger'; describe('ErrorReportingDispatcher', () => { let dispatcher: ErrorReportingDispatcher; @@ -128,3 +129,43 @@ describe('LoggingDispatcher', () => { expect(service2.log).toHaveBeenCalledWith(entry); }); }); + +describe('logDeprecatedMethodUsage', () => { + it('keeps the console warning and emits structured usage details', () => { + const warning = jest.fn(); + const log = jest.fn(); + + logDeprecatedMethodUsage( + { + methodName: 'mParticle.logCheckout', + warningMessage: 'mParticle.logCheckout is deprecated, please use mParticle.logProductAction instead', + }, + { warning }, + { log } + ); + + expect(warning).toHaveBeenCalledWith( + 'mParticle.logCheckout is deprecated, please use mParticle.logProductAction instead' + ); + expect(log).toHaveBeenCalledWith({ + message: 'mParticle.logCheckout', + code: ErrorCodes.MP_DEPRECATED_METHOD_USAGE, + }); + }); + + it('does not require a registered logging dispatcher', () => { + const warning = jest.fn(); + + expect(() => logDeprecatedMethodUsage( + { + methodName: 'onUserAlias', + warningMessage: 'onUserAlias is a deprecated method and will be removed in future releases.', + }, + { warning }, + undefined + )).not.toThrow(); + expect(warning).toHaveBeenCalledWith( + 'onUserAlias is a deprecated method and will be removed in future releases.' + ); + }); +}); From 75265162a48a38535a477fec1d4fec0650672b3d Mon Sep 17 00:00:00 2001 From: Jaissica Date: Mon, 13 Apr 2026 18:42:17 -0400 Subject: [PATCH 02/11] refactor: convert events module to TS --- src/events.interfaces.ts | 5 +- src/{events.js => events.ts} | 215 ++++++++++-------- src/sdkRuntimeModels.ts | 2 +- src/serverModel.ts | 2 +- src/store.ts | 1 + ...vent-logging.js => tests-event-logging.ts} | 83 ++++--- 6 files changed, 185 insertions(+), 123 deletions(-) rename src/{events.js => events.ts} (66%) rename test/src/{tests-event-logging.js => tests-event-logging.ts} (94%) diff --git a/src/events.interfaces.ts b/src/events.interfaces.ts index 0674e47a8..730c3a9af 100644 --- a/src/events.interfaces.ts +++ b/src/events.interfaces.ts @@ -8,6 +8,7 @@ import { BaseEvent, SDKEvent, SDKEventCustomFlags, + SDKImpression, SDKProduct, SDKProductImpression, SDKPromotion, @@ -45,11 +46,11 @@ export interface IEvents { ): void; logEvent(event: BaseEvent, eventOptions?: SDKEventOptions): void; logImpressionEvent( - impression: SDKProductImpression, + impression: SDKImpression | SDKImpression[] | SDKProductImpression | SDKProductImpression[], attrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags, eventOptions?: SDKEventOptions - ); + ): void; logOptOut(): void; logProductActionEvent( productActionType: valueof, diff --git a/src/events.js b/src/events.ts similarity index 66% rename from src/events.js rename to src/events.ts index 73969b5df..09be3a7d9 100644 --- a/src/events.js +++ b/src/events.ts @@ -1,11 +1,36 @@ import Types from './types'; import Constants from './constants'; +import { IEvents } from './events.interfaces'; +import { IMParticleWebSDKInstance } from './mp-instance'; +import { + BaseEvent, + SDKEvent, + SDKEventCustomFlags, + SDKImpression, + SDKProduct, + SDKProductActionType, + SDKProductImpression, + SDKPromotion, +} from './sdkRuntimeModels'; +import { SDKEventAttrs, SDKEventOptions, TransactionAttributes } from '@mparticle/web-sdk'; +import { valueof } from './utils'; +import { EventType, ProductActionType, PromotionActionType } from './types'; + +interface DOMHandlerElement extends HTMLElement { + href?: string; + target?: string; + submit?: () => void; + attachEvent?: (event: string, handler: EventListener) => void; +} var Messages = Constants.Messages; -export default function Events(mpInstance) { +export default function Events( + this: IEvents, + mpInstance: IMParticleWebSDKInstance +): void { var self = this; - this.logEvent = function(event, options) { + this.logEvent = function(event: BaseEvent, options?: SDKEventOptions): void { mpInstance.Logger.verbose( Messages.InformationMessages.StartingLogEvent + ': ' + event.name ); @@ -19,7 +44,7 @@ export default function Events(mpInstance) { } }; - this.startTracking = function(callback) { + this.startTracking = function(callback: Function | null): void { if (!mpInstance._Store.isTracking) { if ('geolocation' in navigator) { mpInstance._Store.watchPositionId = navigator.geolocation.watchPosition( @@ -37,7 +62,7 @@ export default function Events(mpInstance) { triggerCallback(callback, position); } - function successTracking(position) { + function successTracking(position: GeolocationPosition): void { mpInstance._Store.currentPosition = { lat: position.coords.latitude, lng: position.coords.longitude, @@ -50,14 +75,17 @@ export default function Events(mpInstance) { mpInstance._Store.isTracking = true; } - function errorTracking() { + function errorTracking(): void { triggerCallback(callback); // prevents callback from being fired multiple times callback = null; mpInstance._Store.isTracking = false; } - function triggerCallback(callback, position) { + function triggerCallback( + callback: Function | null, + position?: GeolocationPosition | { coords: { latitude: number | string; longitude: number | string } } + ): void { if (callback) { try { if (position) { @@ -69,13 +97,13 @@ export default function Events(mpInstance) { mpInstance.Logger.error( 'Error invoking the callback passed to startTrackingLocation.' ); - mpInstance.Logger.error(e); + mpInstance.Logger.error(e as string); } } } }; - this.stopTracking = function() { + this.stopTracking = function(): void { if (mpInstance._Store.isTracking) { navigator.geolocation.clearWatch(mpInstance._Store.watchPositionId); mpInstance._Store.currentPosition = null; @@ -83,7 +111,7 @@ export default function Events(mpInstance) { } }; - this.logOptOut = function() { + this.logOptOut = function(): void { mpInstance.Logger.verbose( Messages.InformationMessages.StartingLogOptOut ); @@ -95,11 +123,16 @@ export default function Events(mpInstance) { mpInstance._APIClient.sendEventToServer(event); }; - this.logAST = function() { + this.logAST = function(): void { self.logEvent({ messageType: Types.MessageType.AppStateTransition }); }; - this.logCheckoutEvent = function(step, option, attrs, customFlags) { + this.logCheckoutEvent = function( + step: number, + option?: string, + attrs?: SDKEventAttrs, + customFlags?: SDKEventCustomFlags + ): void { var event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); @@ -121,21 +154,21 @@ export default function Events(mpInstance) { }; this.logProductActionEvent = function( - productActionType, - product, - customAttrs, - customFlags, - transactionAttributes, - options - ) { + productActionType: valueof, + product: SDKProduct | SDKProduct[], + customAttrs?: SDKEventAttrs, + customFlags?: SDKEventCustomFlags, + transactionAttributes?: TransactionAttributes, + options?: SDKEventOptions + ): void { var event = mpInstance._Ecommerce.createCommerceEventObject( customFlags, options ); - var productList = Array.isArray(product) ? product : [product]; + var productList: SDKProduct[] = Array.isArray(product) ? product : [product]; - productList.forEach(function(product) { + productList.forEach(function(product: SDKProduct) { if (product.TotalAmount) { product.TotalAmount = mpInstance._Ecommerce.sanitizeAmount( product.TotalAmount, @@ -163,19 +196,19 @@ export default function Events(mpInstance) { }); if (event) { - event.EventCategory = mpInstance._Ecommerce.convertProductActionToEventType( + event.EventCategory = (mpInstance._Ecommerce.convertProductActionToEventType as Function)( productActionType ); event.EventName += mpInstance._Ecommerce.getProductActionEventName( productActionType ); event.ProductAction = { - ProductActionType: productActionType, + ProductActionType: productActionType as SDKProductActionType, ProductList: productList, }; if (mpInstance._Helpers.isObject(transactionAttributes)) { - mpInstance._Ecommerce.convertTransactionAttributesToProductAction( + (mpInstance._Ecommerce.convertTransactionAttributesToProductAction as Function)( transactionAttributes, event.ProductAction ); @@ -186,11 +219,11 @@ export default function Events(mpInstance) { }; this.logPurchaseEvent = function( - transactionAttributes, - product, - attrs, - customFlags - ) { + transactionAttributes: TransactionAttributes, + product: SDKProduct | SDKProduct[], + attrs?: SDKEventAttrs, + customFlags?: SDKEventCustomFlags + ): void { var event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); @@ -203,12 +236,12 @@ export default function Events(mpInstance) { event.ProductAction = { ProductActionType: Types.ProductActionType.Purchase, }; - event.ProductAction.ProductList = mpInstance._Ecommerce.buildProductList( + event.ProductAction.ProductList = (mpInstance._Ecommerce.buildProductList as Function)( event, product ); - mpInstance._Ecommerce.convertTransactionAttributesToProductAction( + (mpInstance._Ecommerce.convertTransactionAttributesToProductAction as Function)( transactionAttributes, event.ProductAction ); @@ -218,11 +251,11 @@ export default function Events(mpInstance) { }; this.logRefundEvent = function( - transactionAttributes, - product, - attrs, - customFlags - ) { + transactionAttributes: TransactionAttributes, + product: SDKProduct | SDKProduct[], + attrs?: SDKEventAttrs, + customFlags?: SDKEventCustomFlags + ): void { if (!transactionAttributes) { mpInstance.Logger.error(Messages.ErrorMessages.TransactionRequired); return; @@ -240,12 +273,12 @@ export default function Events(mpInstance) { event.ProductAction = { ProductActionType: Types.ProductActionType.Refund, }; - event.ProductAction.ProductList = mpInstance._Ecommerce.buildProductList( + event.ProductAction.ProductList = (mpInstance._Ecommerce.buildProductList as Function)( event, product ); - mpInstance._Ecommerce.convertTransactionAttributesToProductAction( + (mpInstance._Ecommerce.convertTransactionAttributesToProductAction as Function)( transactionAttributes, event.ProductAction ); @@ -255,12 +288,12 @@ export default function Events(mpInstance) { }; this.logPromotionEvent = function( - promotionType, - promotion, - attrs, - customFlags, - eventOptions - ) { + promotionType: valueof, + promotion: SDKPromotion | SDKPromotion[], + attrs?: SDKEventAttrs, + customFlags?: SDKEventCustomFlags, + eventOptions?: SDKEventOptions + ): void { var event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); @@ -269,7 +302,7 @@ export default function Events(mpInstance) { event.EventName += mpInstance._Ecommerce.getPromotionActionEventName( promotionType ); - event.EventCategory = mpInstance._Ecommerce.convertPromotionActionToEventType( + event.EventCategory = (mpInstance._Ecommerce.convertPromotionActionToEventType as Function)( promotionType ); event.PromotionAction = { @@ -284,11 +317,11 @@ export default function Events(mpInstance) { }; this.logImpressionEvent = function( - impression, - attrs, - customFlags, - options - ) { + impression: SDKImpression | SDKImpression[] | SDKProductImpression | SDKProductImpression[], + attrs?: SDKEventAttrs, + customFlags?: SDKEventCustomFlags, + options?: SDKEventOptions + ): void { var event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); @@ -296,18 +329,19 @@ export default function Events(mpInstance) { if (event) { event.EventName += 'Impression'; event.EventCategory = Types.CommerceEventType.ProductImpression; - if (!Array.isArray(impression)) { - impression = [impression]; - } + var rawList = Array.isArray(impression) + ? impression + : [impression]; event.ProductImpressions = []; - impression.forEach(function(impression) { + rawList.forEach(function(item) { + var imp = item as SDKImpression; event.ProductImpressions.push({ - ProductImpressionList: impression.Name, - ProductList: Array.isArray(impression.Product) - ? impression.Product - : [impression.Product], + ProductImpressionList: imp.Name, + ProductList: Array.isArray(imp.Product) + ? imp.Product + : [imp.Product], }); }); @@ -315,7 +349,11 @@ export default function Events(mpInstance) { } }; - this.logCommerceEvent = function(commerceEvent, attrs, options) { + this.logCommerceEvent = function( + commerceEvent: SDKEvent, + attrs?: SDKEventAttrs, + options?: SDKEventOptions + ): void { mpInstance.Logger.verbose( Messages.InformationMessages.StartingLogCommerceEvent ); @@ -346,7 +384,7 @@ export default function Events(mpInstance) { } if (attrs) { - commerceEvent.EventAttributes = attrs; + commerceEvent.EventAttributes = attrs as Record; } mpInstance._APIClient.sendEventToServer(commerceEvent, options); @@ -361,19 +399,20 @@ export default function Events(mpInstance) { }; this.addEventHandler = function( - domEvent, - selector, - eventName, - data, - eventType - ) { - var elements = [], - handler = function(e) { - var timeoutHandler = function() { - if (element.href) { - window.location.href = element.href; - } else if (element.submit) { - element.submit(); + domEvent: string, + selector: string | Node, + eventName: ((element: HTMLLinkElement | HTMLFormElement) => string) | string, + data: ((element: HTMLLinkElement | HTMLFormElement) => SDKEventAttrs) | SDKEventAttrs, + eventType: valueof + ): void { + var elements: ArrayLike | Element[] = [], + handler = function(e: Event): void { + var el = element as DOMHandlerElement; + var timeoutHandler = function(): void { + if (el.href) { + window.location.href = el.href; + } else if (el.submit) { + el.submit(); } }; @@ -385,23 +424,23 @@ export default function Events(mpInstance) { messageType: Types.MessageType.PageEvent, name: typeof eventName === 'function' - ? eventName(element) + ? eventName(el as HTMLLinkElement) : eventName, - data: typeof data === 'function' ? data(element) : data, - eventType: eventType || Types.EventType.Other, + data: typeof data === 'function' ? data(el as HTMLLinkElement) : data, + eventType: (eventType || Types.EventType.Other) as number, }); // TODO: Handle middle-clicks and special keys (ctrl, alt, etc) if ( - (element.href && element.target !== '_blank') || - element.submit + (el.href && el.target !== '_blank') || + el.submit ) { // Give xmlhttprequest enough time to execute before navigating a link or submitting form if (e.preventDefault) { e.preventDefault(); } else { - e.returnValue = false; + (e as { returnValue: boolean }).returnValue = false; } setTimeout( @@ -410,8 +449,8 @@ export default function Events(mpInstance) { ); } }, - element, - i; + element: Element, + i: number; if (!selector) { mpInstance.Logger.error("Can't bind event, selector is required"); @@ -422,7 +461,7 @@ export default function Events(mpInstance) { if (typeof selector === 'string') { elements = document.querySelectorAll(selector); } else if (selector.nodeType) { - elements = [selector]; + elements = [selector as Element]; } if (elements.length) { @@ -436,16 +475,14 @@ export default function Events(mpInstance) { for (i = 0; i < elements.length; i++) { element = elements[i]; + var el = element as DOMHandlerElement; - if (element.addEventListener) { - // Modern browsers - element.addEventListener(domEvent, handler, false); - } else if (element.attachEvent) { - // IE < 9 - element.attachEvent('on' + domEvent, handler); + if (el.addEventListener) { + el.addEventListener(domEvent, handler, false); + } else if (el.attachEvent) { + el.attachEvent('on' + domEvent, handler); } else { - // All other browsers - element['on' + domEvent] = handler; + el['on' + domEvent] = handler; } } } else { diff --git a/src/sdkRuntimeModels.ts b/src/sdkRuntimeModels.ts index e31084228..e42560ebc 100644 --- a/src/sdkRuntimeModels.ts +++ b/src/sdkRuntimeModels.ts @@ -112,7 +112,7 @@ export interface SDKShoppingCart { } export interface SDKPromotionAction { - PromotionActionType: string; + PromotionActionType: string | valueof; PromotionList?: SDKPromotion[]; } diff --git a/src/serverModel.ts b/src/serverModel.ts index c21663c9a..4bda3a8d7 100644 --- a/src/serverModel.ts +++ b/src/serverModel.ts @@ -476,7 +476,7 @@ export default function ServerModel( }; } else if (event.PromotionAction) { dto.pm = { - an: event.PromotionAction.PromotionActionType, + an: event.PromotionAction.PromotionActionType as string, pl: event.PromotionAction.PromotionList.map(function( promotion ) { diff --git a/src/store.ts b/src/store.ts index 4bd1617e1..a050e52c4 100644 --- a/src/store.ts +++ b/src/store.ts @@ -96,6 +96,7 @@ export interface SDKConfig { workspaceToken?: string; requiredWebviewBridgeName?: string; isLoggingEnabled?: boolean; + timeout?: number; } function createSDKConfig(config: SDKInitConfig): SDKConfig { diff --git a/test/src/tests-event-logging.js b/test/src/tests-event-logging.ts similarity index 94% rename from test/src/tests-event-logging.js rename to test/src/tests-event-logging.ts index 69a47be79..d82e9b1aa 100644 --- a/test/src/tests-event-logging.js +++ b/test/src/tests-event-logging.ts @@ -9,6 +9,29 @@ import { MPConfig, MessageType, } from './config/constants'; +import { IMParticleInstanceManager } from '../../src/sdkRuntimeModels'; +import { TransactionAttributes } from '@mparticle/web-sdk'; + +declare global { + interface Window { + mParticle: IMParticleInstanceManager; + } + namespace Should { + interface Assertion { + not: Assertion; + be: Assertion; + have: Assertion; + ok(): void; + } + } + function Should(obj: unknown): Should.Assertion; + // geomock.js custom property + interface Geolocation { + shouldFail: boolean; + } +} + +const mParticle = window.mParticle as IMParticleInstanceManager; const { findEventFromRequest, findBatch, getIdentityEvent, waitForCondition, fetchMockSuccess, hasIdentifyReturned } = Utils; @@ -209,7 +232,7 @@ describe('event logging', function() { it('should log an error', async () => { await waitForCondition(hasIdentifyReturned); - mParticle.logError('my error'); + (mParticle.logError as Function)('my error'); const errorEvent = findEventFromRequest(fetchMock.calls(), 'my error'); @@ -226,7 +249,7 @@ describe('event logging', function() { const error = new Error('my error'); error.stack = 'my stacktrace'; - mParticle.logError(error); + (mParticle.logError as Function)(error); const errorEvent = findEventFromRequest(fetchMock.calls(), 'my error'); @@ -248,7 +271,7 @@ describe('event logging', function() { const error = new Error('my error'); error.stack = 'my stacktrace'; - mParticle.logError(error, { location: 'my path', myData: 'my data' }); + (mParticle.logError as Function)(error, { location: 'my path', myData: 'my data' }); const errorEvent = findEventFromRequest(fetchMock.calls(), 'my error'); @@ -269,7 +292,7 @@ describe('event logging', function() { await waitForCondition(hasIdentifyReturned); const bond = sinon.spy(mParticle.getInstance().Logger, 'warning'); - mParticle.logError('my error', { + (mParticle.logError as Function)('my error', { invalid: ['my invalid attr'], valid: 10, }); @@ -442,7 +465,7 @@ describe('event logging', function() { it('should not log a PageView event if there are invalid attrs', async () => { await waitForCondition(hasIdentifyReturned); - mParticle.logPageView('test1', 'invalid', null); + (mParticle.logPageView as Function)('test1', 'invalid', null); const pageViewEvent = findEventFromRequest( fetchMock.calls(), 'test1' @@ -454,7 +477,7 @@ describe('event logging', function() { it('should not log an event that has an invalid customFlags', async () => { await waitForCondition(hasIdentifyReturned); - mParticle.logPageView('test', null, 'invalid'); + (mParticle.logPageView as Function)('test', null, 'invalid'); const pageViewEvent = findEventFromRequest( fetchMock.calls(), @@ -477,7 +500,7 @@ describe('event logging', function() { pageViewEvent.data.screen_name.should.equal('PageView'); fetchMock.resetHistory(); - mParticle.logPageView({ test: 'test' }); + (mParticle.logPageView as Function)({ test: 'test' }); fetchMock.calls().length.should.equal(1); const pageViewEvent2 = findEventFromRequest( fetchMock.calls(), @@ -486,7 +509,7 @@ describe('event logging', function() { pageViewEvent2.data.screen_name.should.equal('PageView'); fetchMock.resetHistory(); - mParticle.logPageView([1, 2, 3]); + (mParticle.logPageView as Function)([1, 2, 3]); fetchMock.calls().length.should.equal(1); const pageViewEvent3 = findEventFromRequest( fetchMock.calls(), @@ -510,7 +533,7 @@ describe('event logging', function() { await waitForCondition(hasIdentifyReturned); fetchMock.resetHistory(); - mParticle.logEvent(); + (mParticle.logEvent as Function)(); fetchMock.calls().should.have.lengthOf(0); }); @@ -520,7 +543,7 @@ describe('event logging', function() { fetchMock.resetHistory(); - mParticle.logEvent('test', 100); + (mParticle.logEvent as Function)('test', 100); fetchMock.calls().should.have.lengthOf(0); }); @@ -528,7 +551,7 @@ describe('event logging', function() { it('event attributes must be object', async () => { await waitForCondition(hasIdentifyReturned); - mParticle.logEvent('Test Event', null, 1); + (mParticle.logEvent as Function)('Test Event', null, 1); const testEvent = findEventFromRequest(fetchMock.calls(), 'Test Event'); @@ -626,7 +649,7 @@ describe('event logging', function() { ); expect(identityCalls.length).to.equal(1); - const data = JSON.parse(identityCalls[0][1].body); + const data = JSON.parse(String(identityCalls[0][1].body)); data.should.have.properties( 'client_sdk', 'environment', @@ -870,7 +893,7 @@ describe('event logging', function() { }) let currentPosition; - function callback(position) { + function callback(position?: GeolocationPosition) { currentPosition = position; } const clock = sinon.useFakeTimers(); @@ -938,7 +961,7 @@ describe('event logging', function() { window.mParticle.logEvent('Test Event'); - const batch = JSON.parse(fetchMock.lastOptions().body); + const batch = JSON.parse(String(fetchMock.lastOptions().body)); batch.application_info.should.have.property( 'application_name', @@ -960,7 +983,7 @@ describe('event logging', function() { ); }) - const batch = JSON.parse(fetchMock.lastOptions().body); + const batch = JSON.parse(String(fetchMock.lastOptions().body)); batch.events[0].data.should.have.property('is_first_run', true); await waitForCondition(() => { @@ -970,7 +993,7 @@ describe('event logging', function() { }) mParticle.init(apiKey, mParticle.config); - const batch2 = JSON.parse(fetchMock.lastOptions().body); + const batch2 = JSON.parse(String(fetchMock.lastOptions().body)); batch2.events[0].data.should.have.property('is_first_run', false); delete window.mParticle.config.flags; @@ -992,7 +1015,7 @@ describe('event logging', function() { }); - const batch = JSON.parse(fetchMock.lastOptions().body); + const batch = JSON.parse(String(fetchMock.lastOptions().body)); batch.events[0].data.should.have.property('launch_referral'); batch.events[0].data.launch_referral.should.startWith( 'http://localhost' @@ -1018,7 +1041,7 @@ describe('event logging', function() { window.mParticle.logEvent('Test Event'); - const batch = JSON.parse(fetchMock.lastOptions().body); + const batch = JSON.parse(String(fetchMock.lastOptions().body)); batch.application_info.should.have.property( 'application_name', 'another name' @@ -1047,7 +1070,7 @@ describe('event logging', function() { }); window.mParticle.logEvent('Test Event'); - const batch = JSON.parse(fetchMock.lastOptions().body); + const batch = JSON.parse(String(fetchMock.lastOptions().body)); batch.should.have.property('context'); batch.context.should.have.property('data_plan'); @@ -1074,7 +1097,7 @@ describe('event logging', function() { }); window.mParticle.logEvent('Test Event'); - const batch = JSON.parse(fetchMock.lastOptions().body); + const batch = JSON.parse(String(fetchMock.lastOptions().body)); batch.should.have.property('context'); batch.context.should.have.property('data_plan'); @@ -1101,7 +1124,7 @@ describe('event logging', function() { }); window.mParticle.logEvent('Test Event'); - const batch = JSON.parse(fetchMock.lastOptions().body); + const batch = JSON.parse(String(fetchMock.lastOptions().body)); batch.should.not.have.property('context'); @@ -1114,7 +1137,7 @@ describe('event logging', function() { mParticle.config.logLevel = 'verbose'; mParticle.config.logger = { - error: function(msg) { + error: function(msg: string) { if (!errorMessage) { errorMessage = msg; } @@ -1139,7 +1162,7 @@ describe('event logging', function() { errorMessage.should.equal( 'Your data plan id must be a string and match the data plan slug format (i.e. under_case_slug)' ); - const batch = JSON.parse(fetchMock.lastOptions().body); + const batch = JSON.parse(String(fetchMock.lastOptions().body)); batch.should.not.have.property('context'); delete window.mParticle.config.flags; }); @@ -1189,7 +1212,7 @@ describe('event logging', function() { window.mParticle.logEvent('Test Event'); - const batch = JSON.parse(fetchMock.lastOptions().body); + const batch = JSON.parse(String(fetchMock.lastOptions().body)); batch.should.have.property('consent_state'); batch.consent_state.should.have.properties(['gdpr', 'ccpa']); @@ -1272,7 +1295,7 @@ describe('event logging', function() { const customAttributes = { sale: true }; const customFlags = { 'Google.Category': 'travel' }; - mParticle.eCommerce.logProductAction( + (mParticle.eCommerce.logProductAction as Function)( mParticle.ProductActionType.Purchase, [product1, product2], customAttributes, @@ -1280,7 +1303,7 @@ describe('event logging', function() { transactionAttributes ); - const batch = JSON.parse(fetchMock.lastOptions().body); + const batch = JSON.parse(String(fetchMock.lastOptions().body)); batch.events[0].data.product_action.total_amount.should.equal(0); batch.events[0].data.product_action.shipping_amount.should.equal(0); @@ -1301,7 +1324,7 @@ describe('event logging', function() { mParticle.getInstance()._Store.identityCallInFlight === false ); }); - const product1 = mParticle.eCommerce.createProduct( + const product1 = (mParticle.eCommerce.createProduct as Function)( 'iphone', 'iphoneSKU', 'string', @@ -1312,7 +1335,7 @@ describe('event logging', function() { 'string', 'coupon' ); - const product2 = mParticle.eCommerce.createProduct( + const product2 = (mParticle.eCommerce.createProduct as Function)( 'galaxy', 'galaxySKU', 'string', @@ -1333,7 +1356,7 @@ describe('event logging', function() { const customAttributes = { sale: true }; const customFlags = { 'Google.Category': 'travel' }; - mParticle.eCommerce.logProductAction( + (mParticle.eCommerce.logProductAction as Function)( mParticle.ProductActionType.Purchase, [product1, product2], customAttributes, @@ -1341,7 +1364,7 @@ describe('event logging', function() { transactionAttributes ); - const batch = JSON.parse(fetchMock.lastOptions().body); + const batch = JSON.parse(String(fetchMock.lastOptions().body)); ( batch.events[0].data.product_action.products[0].position === null ).should.equal(true); From 0c785965283c3881e4e7e5accf897d0a43c4201f Mon Sep 17 00:00:00 2001 From: Jaissica Date: Wed, 13 May 2026 07:09:38 -0400 Subject: [PATCH 03/11] fix SonarCloud issues --- src/events.ts | 58 ++++++++++++++++----------------- test/src/tests-event-logging.ts | 2 +- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/events.ts b/src/events.ts index 09be3a7d9..f048a87ff 100644 --- a/src/events.ts +++ b/src/events.ts @@ -1,4 +1,4 @@ -import Types from './types'; +import Types, { EventType, ProductActionType, PromotionActionType } from './types'; import Constants from './constants'; import { IEvents } from './events.interfaces'; import { IMParticleWebSDKInstance } from './mp-instance'; @@ -14,7 +14,6 @@ import { } from './sdkRuntimeModels'; import { SDKEventAttrs, SDKEventOptions, TransactionAttributes } from '@mparticle/web-sdk'; import { valueof } from './utils'; -import { EventType, ProductActionType, PromotionActionType } from './types'; interface DOMHandlerElement extends HTMLElement { href?: string; @@ -23,19 +22,18 @@ interface DOMHandlerElement extends HTMLElement { attachEvent?: (event: string, handler: EventListener) => void; } -var Messages = Constants.Messages; +const Messages = Constants.Messages; export default function Events( this: IEvents, mpInstance: IMParticleWebSDKInstance ): void { - var self = this; this.logEvent = function(event: BaseEvent, options?: SDKEventOptions): void { mpInstance.Logger.verbose( Messages.InformationMessages.StartingLogEvent + ': ' + event.name ); if (mpInstance._Helpers.canLog()) { - var uploadObject = mpInstance._ServerModel.createEventObject(event); + const uploadObject = mpInstance._ServerModel.createEventObject(event); mpInstance._APIClient.sendEventToServer(uploadObject, options); } else { mpInstance.Logger.verbose( @@ -53,7 +51,7 @@ export default function Events( ); } } else { - var position = { + const position = { coords: { latitude: mpInstance._Store.currentPosition.lat, longitude: mpInstance._Store.currentPosition.lng, @@ -116,7 +114,7 @@ export default function Events( Messages.InformationMessages.StartingLogOptOut ); - var event = mpInstance._ServerModel.createEventObject({ + const event = mpInstance._ServerModel.createEventObject({ messageType: Types.MessageType.OptOut, eventType: Types.EventType.Other, }); @@ -124,7 +122,7 @@ export default function Events( }; this.logAST = function(): void { - self.logEvent({ messageType: Types.MessageType.AppStateTransition }); + this.logEvent({ messageType: Types.MessageType.AppStateTransition }); }; this.logCheckoutEvent = function( @@ -133,7 +131,7 @@ export default function Events( attrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags ): void { - var event = mpInstance._Ecommerce.createCommerceEventObject( + const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); @@ -149,7 +147,7 @@ export default function Events( ProductList: [], }; - self.logCommerceEvent(event, attrs); + this.logCommerceEvent(event, attrs); } }; @@ -161,12 +159,12 @@ export default function Events( transactionAttributes?: TransactionAttributes, options?: SDKEventOptions ): void { - var event = mpInstance._Ecommerce.createCommerceEventObject( + const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags, options ); - var productList: SDKProduct[] = Array.isArray(product) ? product : [product]; + const productList: SDKProduct[] = Array.isArray(product) ? product : [product]; productList.forEach(function(product: SDKProduct) { if (product.TotalAmount) { @@ -214,7 +212,7 @@ export default function Events( ); } - self.logCommerceEvent(event, customAttrs, options); + this.logCommerceEvent(event, customAttrs, options); } }; @@ -224,7 +222,7 @@ export default function Events( attrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags ): void { - var event = mpInstance._Ecommerce.createCommerceEventObject( + const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); @@ -246,7 +244,7 @@ export default function Events( event.ProductAction ); - self.logCommerceEvent(event, attrs); + this.logCommerceEvent(event, attrs); } }; @@ -261,7 +259,7 @@ export default function Events( return; } - var event = mpInstance._Ecommerce.createCommerceEventObject( + const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); @@ -283,7 +281,7 @@ export default function Events( event.ProductAction ); - self.logCommerceEvent(event, attrs); + this.logCommerceEvent(event, attrs); } }; @@ -294,7 +292,7 @@ export default function Events( customFlags?: SDKEventCustomFlags, eventOptions?: SDKEventOptions ): void { - var event = mpInstance._Ecommerce.createCommerceEventObject( + const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); @@ -312,7 +310,7 @@ export default function Events( : [promotion], }; - self.logCommerceEvent(event, attrs, eventOptions); + this.logCommerceEvent(event, attrs, eventOptions); } }; @@ -322,21 +320,21 @@ export default function Events( customFlags?: SDKEventCustomFlags, options?: SDKEventOptions ): void { - var event = mpInstance._Ecommerce.createCommerceEventObject( + const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); if (event) { event.EventName += 'Impression'; event.EventCategory = Types.CommerceEventType.ProductImpression; - var rawList = Array.isArray(impression) + const rawList = Array.isArray(impression) ? impression : [impression]; event.ProductImpressions = []; rawList.forEach(function(item) { - var imp = item as SDKImpression; + const imp = item as SDKImpression; event.ProductImpressions.push({ ProductImpressionList: imp.Name, ProductList: Array.isArray(imp.Product) @@ -345,7 +343,7 @@ export default function Events( }); }); - self.logCommerceEvent(event, attrs, options); + this.logCommerceEvent(event, attrs, options); } }; @@ -405,12 +403,12 @@ export default function Events( data: ((element: HTMLLinkElement | HTMLFormElement) => SDKEventAttrs) | SDKEventAttrs, eventType: valueof ): void { - var elements: ArrayLike | Element[] = [], - handler = function(e: Event): void { - var el = element as DOMHandlerElement; - var timeoutHandler = function(): void { + let elements: ArrayLike | Element[] = [], + handler = (e: Event): void => { + const el = element as DOMHandlerElement; + const timeoutHandler = function(): void { if (el.href) { - window.location.href = el.href; + globalThis.location.href = el.href; } else if (el.submit) { el.submit(); } @@ -420,7 +418,7 @@ export default function Events( 'DOM event triggered, handling event' ); - self.logEvent({ + this.logEvent({ messageType: Types.MessageType.PageEvent, name: typeof eventName === 'function' @@ -475,7 +473,7 @@ export default function Events( for (i = 0; i < elements.length; i++) { element = elements[i]; - var el = element as DOMHandlerElement; + const el = element as DOMHandlerElement; if (el.addEventListener) { el.addEventListener(domEvent, handler, false); diff --git a/test/src/tests-event-logging.ts b/test/src/tests-event-logging.ts index d82e9b1aa..7f3454b3d 100644 --- a/test/src/tests-event-logging.ts +++ b/test/src/tests-event-logging.ts @@ -31,7 +31,7 @@ declare global { } } -const mParticle = window.mParticle as IMParticleInstanceManager; +const mParticle = window.mParticle; const { findEventFromRequest, findBatch, getIdentityEvent, waitForCondition, fetchMockSuccess, hasIdentifyReturned } = Utils; From 21b560c2beaf0b980a282d9665b90bd1be95acd5 Mon Sep 17 00:00:00 2001 From: Jaissica Date: Wed, 13 May 2026 07:14:51 -0400 Subject: [PATCH 04/11] fix failing tests --- test/src/tests-event-logging.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/tests-event-logging.ts b/test/src/tests-event-logging.ts index 7f3454b3d..d82e9b1aa 100644 --- a/test/src/tests-event-logging.ts +++ b/test/src/tests-event-logging.ts @@ -31,7 +31,7 @@ declare global { } } -const mParticle = window.mParticle; +const mParticle = window.mParticle as IMParticleInstanceManager; const { findEventFromRequest, findBatch, getIdentityEvent, waitForCondition, fetchMockSuccess, hasIdentifyReturned } = Utils; From fc885cbb4b4242f5b205a70a1d7e613ce2ff3797 Mon Sep 17 00:00:00 2001 From: Jaissica Date: Wed, 13 May 2026 07:21:44 -0400 Subject: [PATCH 05/11] fix SonarCloud warnings --- src/events.ts | 16 ++++++++-------- test/src/tests-event-logging.ts | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/events.ts b/src/events.ts index f048a87ff..e0924e177 100644 --- a/src/events.ts +++ b/src/events.ts @@ -43,14 +43,7 @@ export default function Events( }; this.startTracking = function(callback: Function | null): void { - if (!mpInstance._Store.isTracking) { - if ('geolocation' in navigator) { - mpInstance._Store.watchPositionId = navigator.geolocation.watchPosition( - successTracking, - errorTracking - ); - } - } else { + if (mpInstance._Store.isTracking) { const position = { coords: { latitude: mpInstance._Store.currentPosition.lat, @@ -58,6 +51,13 @@ export default function Events( }, }; triggerCallback(callback, position); + } else { + if ('geolocation' in navigator) { + mpInstance._Store.watchPositionId = navigator.geolocation.watchPosition( + successTracking, + errorTracking + ); + } } function successTracking(position: GeolocationPosition): void { diff --git a/test/src/tests-event-logging.ts b/test/src/tests-event-logging.ts index d82e9b1aa..3dbad0288 100644 --- a/test/src/tests-event-logging.ts +++ b/test/src/tests-event-logging.ts @@ -10,7 +10,6 @@ import { MessageType, } from './config/constants'; import { IMParticleInstanceManager } from '../../src/sdkRuntimeModels'; -import { TransactionAttributes } from '@mparticle/web-sdk'; declare global { interface Window { From 7acabb100b8c89000b56a437e59f345476a7ef4f Mon Sep 17 00:00:00 2001 From: Jaissica Date: Wed, 13 May 2026 07:47:40 -0400 Subject: [PATCH 06/11] improve type safety in events module --- src/events.interfaces.ts | 2 +- src/events.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/events.interfaces.ts b/src/events.interfaces.ts index 730c3a9af..71bef989f 100644 --- a/src/events.interfaces.ts +++ b/src/events.interfaces.ts @@ -62,7 +62,7 @@ export interface IEvents { ): void; logPromotionEvent( promotionType: valueof, - promotion: SDKPromotion, + promotion: SDKPromotion | SDKPromotion[], attrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags, eventOptions?: SDKEventOptions diff --git a/src/events.ts b/src/events.ts index e0924e177..f5e01b33e 100644 --- a/src/events.ts +++ b/src/events.ts @@ -42,7 +42,7 @@ export default function Events( } }; - this.startTracking = function(callback: Function | null): void { + this.startTracking = function(callback: ((position?: GeolocationPosition | { coords: { latitude: number | string; longitude: number | string } }) => void) | null): void { if (mpInstance._Store.isTracking) { const position = { coords: { @@ -81,7 +81,7 @@ export default function Events( } function triggerCallback( - callback: Function | null, + callback: ((position?: GeolocationPosition | { coords: { latitude: number | string; longitude: number | string } }) => void) | null, position?: GeolocationPosition | { coords: { latitude: number | string; longitude: number | string } } ): void { if (callback) { @@ -95,7 +95,7 @@ export default function Events( mpInstance.Logger.error( 'Error invoking the callback passed to startTrackingLocation.' ); - mpInstance.Logger.error(e as string); + mpInstance.Logger.error(e instanceof Error ? e.message : String(e)); } } } @@ -194,6 +194,7 @@ export default function Events( }); if (event) { + // TODO(SDKE-1106): Remove `as Function` casts when ecommerce.js is migrated to TS event.EventCategory = (mpInstance._Ecommerce.convertProductActionToEventType as Function)( productActionType ); From d4b22534d3a9ec77c1d3c3372b997318bea3be6a Mon Sep 17 00:00:00 2001 From: Jaissica Date: Wed, 13 May 2026 09:48:11 -0400 Subject: [PATCH 07/11] use arrow functions to preserve this context and handle SDKProductImpression input safely --- src/events.ts | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/src/events.ts b/src/events.ts index f5e01b33e..f728ae9e0 100644 --- a/src/events.ts +++ b/src/events.ts @@ -121,16 +121,16 @@ export default function Events( mpInstance._APIClient.sendEventToServer(event); }; - this.logAST = function(): void { + this.logAST = (): void => { this.logEvent({ messageType: Types.MessageType.AppStateTransition }); }; - this.logCheckoutEvent = function( + this.logCheckoutEvent = ( step: number, option?: string, attrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags - ): void { + ): void => { const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); @@ -151,14 +151,14 @@ export default function Events( } }; - this.logProductActionEvent = function( + this.logProductActionEvent = ( productActionType: valueof, product: SDKProduct | SDKProduct[], customAttrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags, transactionAttributes?: TransactionAttributes, options?: SDKEventOptions - ): void { + ): void => { const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags, options @@ -217,12 +217,12 @@ export default function Events( } }; - this.logPurchaseEvent = function( + this.logPurchaseEvent = ( transactionAttributes: TransactionAttributes, product: SDKProduct | SDKProduct[], attrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags - ): void { + ): void => { const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); @@ -249,12 +249,12 @@ export default function Events( } }; - this.logRefundEvent = function( + this.logRefundEvent = ( transactionAttributes: TransactionAttributes, product: SDKProduct | SDKProduct[], attrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags - ): void { + ): void => { if (!transactionAttributes) { mpInstance.Logger.error(Messages.ErrorMessages.TransactionRequired); return; @@ -286,13 +286,13 @@ export default function Events( } }; - this.logPromotionEvent = function( + this.logPromotionEvent = ( promotionType: valueof, promotion: SDKPromotion | SDKPromotion[], attrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags, eventOptions?: SDKEventOptions - ): void { + ): void => { const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); @@ -315,12 +315,12 @@ export default function Events( } }; - this.logImpressionEvent = function( + this.logImpressionEvent = ( impression: SDKImpression | SDKImpression[] | SDKProductImpression | SDKProductImpression[], attrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags, options?: SDKEventOptions - ): void { + ): void => { const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); @@ -335,13 +335,21 @@ export default function Events( event.ProductImpressions = []; rawList.forEach(function(item) { - const imp = item as SDKImpression; - event.ProductImpressions.push({ - ProductImpressionList: imp.Name, - ProductList: Array.isArray(imp.Product) - ? imp.Product - : [imp.Product], - }); + if ('Name' in item) { + const imp = item as SDKImpression; + event.ProductImpressions.push({ + ProductImpressionList: imp.Name, + ProductList: Array.isArray(imp.Product) + ? imp.Product + : [imp.Product], + }); + } else { + const imp = item as SDKProductImpression; + event.ProductImpressions.push({ + ProductImpressionList: imp.ProductImpressionList, + ProductList: imp.ProductList || [], + }); + } }); this.logCommerceEvent(event, attrs, options); From 15d9342eccd3e1f9660023faf582dbee50663dae Mon Sep 17 00:00:00 2001 From: Jaissica Date: Wed, 20 May 2026 17:12:05 -0400 Subject: [PATCH 08/11] address PR comments --- src/events.interfaces.ts | 9 ++++-- src/events.ts | 55 ++++++++++++++++----------------- src/sdkRuntimeModels.ts | 2 +- test/src/config/constants.ts | 4 +-- test/src/tests-event-logging.ts | 7 +---- 5 files changed, 38 insertions(+), 39 deletions(-) diff --git a/src/events.interfaces.ts b/src/events.interfaces.ts index 71bef989f..b781c3d57 100644 --- a/src/events.interfaces.ts +++ b/src/events.interfaces.ts @@ -1,5 +1,4 @@ import { - Callback, SDKEventAttrs, SDKEventOptions, TransactionAttributes, @@ -16,6 +15,12 @@ import { import { valueof } from './utils'; import { EventType, ProductActionType, PromotionActionType } from './types'; +export type TrackingCallback = (( + position?: GeolocationPosition | { + coords: { latitude: number | string; longitude: number | string }; + } +) => void) | null; + // Supports wrapping event handlers functions that will ideally return a specific type type EventHandlerFunction = (element: HTMLLinkElement | HTMLFormElement) => T; @@ -79,6 +84,6 @@ export interface IEvents { attrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags ): void; - startTracking(callback: Callback): void; + startTracking(callback: TrackingCallback): void; stopTracking(): void; } diff --git a/src/events.ts b/src/events.ts index f728ae9e0..1acc30497 100644 --- a/src/events.ts +++ b/src/events.ts @@ -1,6 +1,6 @@ import Types, { EventType, ProductActionType, PromotionActionType } from './types'; import Constants from './constants'; -import { IEvents } from './events.interfaces'; +import { IEvents, TrackingCallback } from './events.interfaces'; import { IMParticleWebSDKInstance } from './mp-instance'; import { BaseEvent, @@ -42,7 +42,7 @@ export default function Events( } }; - this.startTracking = function(callback: ((position?: GeolocationPosition | { coords: { latitude: number | string; longitude: number | string } }) => void) | null): void { + this.startTracking = function(callback: TrackingCallback): void { if (mpInstance._Store.isTracking) { const position = { coords: { @@ -81,7 +81,7 @@ export default function Events( } function triggerCallback( - callback: ((position?: GeolocationPosition | { coords: { latitude: number | string; longitude: number | string } }) => void) | null, + callback: TrackingCallback, position?: GeolocationPosition | { coords: { latitude: number | string; longitude: number | string } } ): void { if (callback) { @@ -194,7 +194,7 @@ export default function Events( }); if (event) { - // TODO(SDKE-1106): Remove `as Function` casts when ecommerce.js is migrated to TS + // TODO(https://go/j/SDKE-1108): Remove `as Function` casts when ecommerce.js is migrated to TS event.EventCategory = (mpInstance._Ecommerce.convertProductActionToEventType as Function)( productActionType ); @@ -328,13 +328,14 @@ export default function Events( if (event) { event.EventName += 'Impression'; event.EventCategory = Types.CommerceEventType.ProductImpression; - const rawList = Array.isArray(impression) + // https://go/j/SDKE-1199 + const impressionList: (SDKImpression | SDKProductImpression)[] = Array.isArray(impression) ? impression : [impression]; event.ProductImpressions = []; - rawList.forEach(function(item) { + impressionList.forEach(function(item) { if ('Name' in item) { const imp = item as SDKImpression; event.ProductImpressions.push({ @@ -412,14 +413,15 @@ export default function Events( data: ((element: HTMLLinkElement | HTMLFormElement) => SDKEventAttrs) | SDKEventAttrs, eventType: valueof ): void { - let elements: ArrayLike | Element[] = [], - handler = (e: Event): void => { - const el = element as DOMHandlerElement; + let elements: ArrayLike | Element[] = []; + let element: DOMHandlerElement; + let elementIndex: number; + const handler = (e: Event): void => { const timeoutHandler = function(): void { - if (el.href) { - globalThis.location.href = el.href; - } else if (el.submit) { - el.submit(); + if (element.href) { + globalThis.location.href = element.href; + } else if (element.submit) { + element.submit(); } }; @@ -431,16 +433,16 @@ export default function Events( messageType: Types.MessageType.PageEvent, name: typeof eventName === 'function' - ? eventName(el as HTMLLinkElement) + ? eventName(element as HTMLLinkElement) : eventName, - data: typeof data === 'function' ? data(el as HTMLLinkElement) : data, + data: typeof data === 'function' ? data(element as HTMLLinkElement) : data, eventType: (eventType || Types.EventType.Other) as number, }); // TODO: Handle middle-clicks and special keys (ctrl, alt, etc) if ( - (el.href && el.target !== '_blank') || - el.submit + (element.href && element.target !== '_blank') || + element.submit ) { // Give xmlhttprequest enough time to execute before navigating a link or submitting form @@ -455,9 +457,7 @@ export default function Events( mpInstance._Store.SDKConfig.timeout ); } - }, - element: Element, - i: number; + }; if (!selector) { mpInstance.Logger.error("Can't bind event, selector is required"); @@ -480,16 +480,15 @@ export default function Events( ', attaching event handlers' ); - for (i = 0; i < elements.length; i++) { - element = elements[i]; - const el = element as DOMHandlerElement; + for (elementIndex = 0; elementIndex < elements.length; elementIndex++) { + element = elements[elementIndex] as DOMHandlerElement; - if (el.addEventListener) { - el.addEventListener(domEvent, handler, false); - } else if (el.attachEvent) { - el.attachEvent('on' + domEvent, handler); + if (element.addEventListener) { + element.addEventListener(domEvent, handler, false); + } else if (element.attachEvent) { + element.attachEvent('on' + domEvent, handler); } else { - el['on' + domEvent] = handler; + element['on' + domEvent] = handler; } } } else { diff --git a/src/sdkRuntimeModels.ts b/src/sdkRuntimeModels.ts index e42560ebc..18e608392 100644 --- a/src/sdkRuntimeModels.ts +++ b/src/sdkRuntimeModels.ts @@ -112,7 +112,7 @@ export interface SDKShoppingCart { } export interface SDKPromotionAction { - PromotionActionType: string | valueof; + PromotionActionType: valueof; PromotionList?: SDKPromotion[]; } diff --git a/test/src/config/constants.ts b/test/src/config/constants.ts index 6f645a13e..816f4f804 100644 --- a/test/src/config/constants.ts +++ b/test/src/config/constants.ts @@ -1,4 +1,4 @@ -import { SDKInitConfig } from "../../../src/sdkRuntimeModels"; +import { IMParticleInstanceManager, SDKInitConfig } from "../../../src/sdkRuntimeModels"; import { MILLIS_IN_ONE_SEC, ONE_DAY_IN_SECONDS } from "../../../src/constants"; export const urls = { @@ -15,7 +15,7 @@ export const urls = { export const MILLISECONDS_IN_ONE_DAY = ONE_DAY_IN_SECONDS * MILLIS_IN_ONE_SEC export const MILLISECONDS_IN_ONE_DAY_PLUS_ONE_SECOND = MILLISECONDS_IN_ONE_DAY + 1; -export const mParticle = window.mParticle; +export const mParticle = window.mParticle as IMParticleInstanceManager; export const apiKey = 'test_key'; export const testMPID = 'testMPID'; diff --git a/test/src/tests-event-logging.ts b/test/src/tests-event-logging.ts index 3dbad0288..821be660e 100644 --- a/test/src/tests-event-logging.ts +++ b/test/src/tests-event-logging.ts @@ -6,15 +6,12 @@ import { urls, apiKey, testMPID, + mParticle, MPConfig, MessageType, } from './config/constants'; -import { IMParticleInstanceManager } from '../../src/sdkRuntimeModels'; declare global { - interface Window { - mParticle: IMParticleInstanceManager; - } namespace Should { interface Assertion { not: Assertion; @@ -30,8 +27,6 @@ declare global { } } -const mParticle = window.mParticle as IMParticleInstanceManager; - const { findEventFromRequest, findBatch, getIdentityEvent, waitForCondition, fetchMockSuccess, hasIdentifyReturned } = Utils; describe('event logging', function() { From 21548755e785dc51649789fd92e2115647117ebb Mon Sep 17 00:00:00 2001 From: Jaissica Date: Wed, 20 May 2026 17:24:20 -0400 Subject: [PATCH 09/11] revert PromotionActionType type tightening to avoid downstream build failure --- src/sdkRuntimeModels.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sdkRuntimeModels.ts b/src/sdkRuntimeModels.ts index 18e608392..e42560ebc 100644 --- a/src/sdkRuntimeModels.ts +++ b/src/sdkRuntimeModels.ts @@ -112,7 +112,7 @@ export interface SDKShoppingCart { } export interface SDKPromotionAction { - PromotionActionType: valueof; + PromotionActionType: string | valueof; PromotionList?: SDKPromotion[]; } From 89505d45e9dfcc39f2cb8a93e9917aff63b88b4a Mon Sep 17 00:00:00 2001 From: Jaissica Date: Wed, 20 May 2026 21:19:40 -0400 Subject: [PATCH 10/11] convert addEventHandler to arrow function --- src/events.interfaces.ts | 1 + src/events.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/events.interfaces.ts b/src/events.interfaces.ts index b781c3d57..c86823257 100644 --- a/src/events.interfaces.ts +++ b/src/events.interfaces.ts @@ -51,6 +51,7 @@ export interface IEvents { ): void; logEvent(event: BaseEvent, eventOptions?: SDKEventOptions): void; logImpressionEvent( + // https://go/j/SDKE-1199 impression: SDKImpression | SDKImpression[] | SDKProductImpression | SDKProductImpression[], attrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags, diff --git a/src/events.ts b/src/events.ts index 1acc30497..c3e88a9c4 100644 --- a/src/events.ts +++ b/src/events.ts @@ -406,13 +406,13 @@ export default function Events( } }; - this.addEventHandler = function( + this.addEventHandler = ( domEvent: string, selector: string | Node, eventName: ((element: HTMLLinkElement | HTMLFormElement) => string) | string, data: ((element: HTMLLinkElement | HTMLFormElement) => SDKEventAttrs) | SDKEventAttrs, eventType: valueof - ): void { + ): void => { let elements: ArrayLike | Element[] = []; let element: DOMHandlerElement; let elementIndex: number; From 1f4ed649178e4eecc21c6ce97fe6f5171cfc0c0e Mon Sep 17 00:00:00 2001 From: Jaissica Date: Mon, 25 May 2026 18:01:41 -0400 Subject: [PATCH 11/11] revert globalThis.location.href to window.location.href --- src/events.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/events.ts b/src/events.ts index c3e88a9c4..65e6eefa1 100644 --- a/src/events.ts +++ b/src/events.ts @@ -419,7 +419,7 @@ export default function Events( const handler = (e: Event): void => { const timeoutHandler = function(): void { if (element.href) { - globalThis.location.href = element.href; + window.location.href = element.href; } else if (element.submit) { element.submit(); }