From 6f952d8fd64b4fe398751a4498b17b75da8e5ff5 Mon Sep 17 00:00:00 2001 From: Jaissica Date: Mon, 13 Apr 2026 18:42:17 -0400 Subject: [PATCH 1/6] 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 469e7283a09338a00d47720847e5169fff3e6cbb Mon Sep 17 00:00:00 2001 From: Jaissica Date: Wed, 13 May 2026 07:09:38 -0400 Subject: [PATCH 2/6] 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 8ad6296081152e2d091c264e423bbeb7fd19aca5 Mon Sep 17 00:00:00 2001 From: Jaissica Date: Wed, 13 May 2026 07:14:51 -0400 Subject: [PATCH 3/6] 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 4de7ee4605a275170f6573b7850dab249af959e0 Mon Sep 17 00:00:00 2001 From: Jaissica Date: Wed, 13 May 2026 07:21:44 -0400 Subject: [PATCH 4/6] 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 14dfc686c6a7c5427dce7efdfe2114e5394eca97 Mon Sep 17 00:00:00 2001 From: Jaissica Date: Wed, 13 May 2026 07:47:40 -0400 Subject: [PATCH 5/6] 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 12da5707a0af6c6bc78bf7121feb060a838611d5 Mon Sep 17 00:00:00 2001 From: Jaissica Date: Wed, 13 May 2026 09:48:11 -0400 Subject: [PATCH 6/6] 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);