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/events.interfaces.ts b/src/events.interfaces.ts index 0674e47a8..c86823257 100644 --- a/src/events.interfaces.ts +++ b/src/events.interfaces.ts @@ -1,5 +1,4 @@ import { - Callback, SDKEventAttrs, SDKEventOptions, TransactionAttributes, @@ -8,6 +7,7 @@ import { BaseEvent, SDKEvent, SDKEventCustomFlags, + SDKImpression, SDKProduct, SDKProductImpression, SDKPromotion, @@ -15,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; @@ -45,11 +51,12 @@ export interface IEvents { ): void; logEvent(event: BaseEvent, eventOptions?: SDKEventOptions): void; logImpressionEvent( - impression: SDKProductImpression, + // https://go/j/SDKE-1199 + impression: SDKImpression | SDKImpression[] | SDKProductImpression | SDKProductImpression[], attrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags, eventOptions?: SDKEventOptions - ); + ): void; logOptOut(): void; logProductActionEvent( productActionType: valueof, @@ -61,7 +68,7 @@ export interface IEvents { ): void; logPromotionEvent( promotionType: valueof, - promotion: SDKPromotion, + promotion: SDKPromotion | SDKPromotion[], attrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags, eventOptions?: SDKEventOptions @@ -78,6 +85,6 @@ export interface IEvents { attrs?: SDKEventAttrs, customFlags?: SDKEventCustomFlags ): void; - startTracking(callback: Callback): void; + startTracking(callback: TrackingCallback): void; stopTracking(): void; } diff --git a/src/events.js b/src/events.ts similarity index 58% rename from src/events.js rename to src/events.ts index 73969b5df..65e6eefa1 100644 --- a/src/events.js +++ b/src/events.ts @@ -1,16 +1,39 @@ -import Types from './types'; +import Types, { EventType, ProductActionType, PromotionActionType } from './types'; import Constants from './constants'; +import { IEvents, TrackingCallback } 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'; + +interface DOMHandlerElement extends HTMLElement { + href?: string; + target?: string; + submit?: () => void; + attachEvent?: (event: string, handler: EventListener) => void; +} -var Messages = Constants.Messages; +const Messages = Constants.Messages; -export default function Events(mpInstance) { - var self = this; - this.logEvent = function(event, options) { +export default function Events( + this: IEvents, + mpInstance: IMParticleWebSDKInstance +): void { + 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( @@ -19,25 +42,25 @@ export default function Events(mpInstance) { } }; - this.startTracking = function(callback) { - if (!mpInstance._Store.isTracking) { - if ('geolocation' in navigator) { - mpInstance._Store.watchPositionId = navigator.geolocation.watchPosition( - successTracking, - errorTracking - ); - } - } else { - var position = { + this.startTracking = function(callback: TrackingCallback): void { + if (mpInstance._Store.isTracking) { + const position = { coords: { latitude: mpInstance._Store.currentPosition.lat, longitude: mpInstance._Store.currentPosition.lng, }, }; triggerCallback(callback, position); + } else { + if ('geolocation' in navigator) { + mpInstance._Store.watchPositionId = navigator.geolocation.watchPosition( + successTracking, + errorTracking + ); + } } - function successTracking(position) { + function successTracking(position: GeolocationPosition): void { mpInstance._Store.currentPosition = { lat: position.coords.latitude, lng: position.coords.longitude, @@ -50,14 +73,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: TrackingCallback, + position?: GeolocationPosition | { coords: { latitude: number | string; longitude: number | string } } + ): void { if (callback) { try { if (position) { @@ -69,13 +95,13 @@ export default function Events(mpInstance) { mpInstance.Logger.error( 'Error invoking the callback passed to startTrackingLocation.' ); - mpInstance.Logger.error(e); + mpInstance.Logger.error(e instanceof Error ? e.message : String(e)); } } } }; - this.stopTracking = function() { + this.stopTracking = function(): void { if (mpInstance._Store.isTracking) { navigator.geolocation.clearWatch(mpInstance._Store.watchPositionId); mpInstance._Store.currentPosition = null; @@ -83,24 +109,29 @@ export default function Events(mpInstance) { } }; - this.logOptOut = function() { + this.logOptOut = function(): void { mpInstance.Logger.verbose( Messages.InformationMessages.StartingLogOptOut ); - var event = mpInstance._ServerModel.createEventObject({ + const event = mpInstance._ServerModel.createEventObject({ messageType: Types.MessageType.OptOut, eventType: Types.EventType.Other, }); mpInstance._APIClient.sendEventToServer(event); }; - this.logAST = function() { - self.logEvent({ messageType: Types.MessageType.AppStateTransition }); + this.logAST = (): void => { + this.logEvent({ messageType: Types.MessageType.AppStateTransition }); }; - this.logCheckoutEvent = function(step, option, attrs, customFlags) { - var event = mpInstance._Ecommerce.createCommerceEventObject( + this.logCheckoutEvent = ( + step: number, + option?: string, + attrs?: SDKEventAttrs, + customFlags?: SDKEventCustomFlags + ): void => { + const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); @@ -116,26 +147,26 @@ export default function Events(mpInstance) { ProductList: [], }; - self.logCommerceEvent(event, attrs); + this.logCommerceEvent(event, attrs); } }; - this.logProductActionEvent = function( - productActionType, - product, - customAttrs, - customFlags, - transactionAttributes, - options - ) { - var event = mpInstance._Ecommerce.createCommerceEventObject( + this.logProductActionEvent = ( + productActionType: valueof, + product: SDKProduct | SDKProduct[], + customAttrs?: SDKEventAttrs, + customFlags?: SDKEventCustomFlags, + transactionAttributes?: TransactionAttributes, + options?: SDKEventOptions + ): void => { + const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags, options ); - var productList = Array.isArray(product) ? product : [product]; + const 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,35 +194,36 @@ export default function Events(mpInstance) { }); if (event) { - event.EventCategory = mpInstance._Ecommerce.convertProductActionToEventType( + // 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 ); 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 ); } - self.logCommerceEvent(event, customAttrs, options); + this.logCommerceEvent(event, customAttrs, options); } }; - this.logPurchaseEvent = function( - transactionAttributes, - product, - attrs, - customFlags - ) { - var event = mpInstance._Ecommerce.createCommerceEventObject( + this.logPurchaseEvent = ( + transactionAttributes: TransactionAttributes, + product: SDKProduct | SDKProduct[], + attrs?: SDKEventAttrs, + customFlags?: SDKEventCustomFlags + ): void => { + const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); @@ -203,32 +235,32 @@ 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 ); - self.logCommerceEvent(event, attrs); + this.logCommerceEvent(event, attrs); } }; - this.logRefundEvent = function( - transactionAttributes, - product, - attrs, - customFlags - ) { + this.logRefundEvent = ( + transactionAttributes: TransactionAttributes, + product: SDKProduct | SDKProduct[], + attrs?: SDKEventAttrs, + customFlags?: SDKEventCustomFlags + ): void => { if (!transactionAttributes) { mpInstance.Logger.error(Messages.ErrorMessages.TransactionRequired); return; } - var event = mpInstance._Ecommerce.createCommerceEventObject( + const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); @@ -240,28 +272,28 @@ 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 ); - self.logCommerceEvent(event, attrs); + this.logCommerceEvent(event, attrs); } }; - this.logPromotionEvent = function( - promotionType, - promotion, - attrs, - customFlags, - eventOptions - ) { - var event = mpInstance._Ecommerce.createCommerceEventObject( + this.logPromotionEvent = ( + promotionType: valueof, + promotion: SDKPromotion | SDKPromotion[], + attrs?: SDKEventAttrs, + customFlags?: SDKEventCustomFlags, + eventOptions?: SDKEventOptions + ): void => { + const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); @@ -269,7 +301,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 = { @@ -279,43 +311,57 @@ export default function Events(mpInstance) { : [promotion], }; - self.logCommerceEvent(event, attrs, eventOptions); + this.logCommerceEvent(event, attrs, eventOptions); } }; - this.logImpressionEvent = function( - impression, - attrs, - customFlags, - options - ) { - var event = mpInstance._Ecommerce.createCommerceEventObject( + this.logImpressionEvent = ( + impression: SDKImpression | SDKImpression[] | SDKProductImpression | SDKProductImpression[], + attrs?: SDKEventAttrs, + customFlags?: SDKEventCustomFlags, + options?: SDKEventOptions + ): void => { + const event = mpInstance._Ecommerce.createCommerceEventObject( customFlags ); if (event) { event.EventName += 'Impression'; event.EventCategory = Types.CommerceEventType.ProductImpression; - if (!Array.isArray(impression)) { - impression = [impression]; - } + // https://go/j/SDKE-1199 + const impressionList: (SDKImpression | SDKProductImpression)[] = Array.isArray(impression) + ? impression + : [impression]; event.ProductImpressions = []; - impression.forEach(function(impression) { - event.ProductImpressions.push({ - ProductImpressionList: impression.Name, - ProductList: Array.isArray(impression.Product) - ? impression.Product - : [impression.Product], - }); + impressionList.forEach(function(item) { + 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 || [], + }); + } }); - self.logCommerceEvent(event, attrs, options); + this.logCommerceEvent(event, attrs, options); } }; - this.logCommerceEvent = function(commerceEvent, attrs, options) { + this.logCommerceEvent = function( + commerceEvent: SDKEvent, + attrs?: SDKEventAttrs, + options?: SDKEventOptions + ): void { mpInstance.Logger.verbose( Messages.InformationMessages.StartingLogCommerceEvent ); @@ -346,7 +392,7 @@ export default function Events(mpInstance) { } if (attrs) { - commerceEvent.EventAttributes = attrs; + commerceEvent.EventAttributes = attrs as Record; } mpInstance._APIClient.sendEventToServer(commerceEvent, options); @@ -360,16 +406,18 @@ export default function Events(mpInstance) { } }; - this.addEventHandler = function( - domEvent, - selector, - eventName, - data, - eventType - ) { - var elements = [], - handler = function(e) { - var timeoutHandler = function() { + this.addEventHandler = ( + domEvent: string, + selector: string | Node, + eventName: ((element: HTMLLinkElement | HTMLFormElement) => string) | string, + data: ((element: HTMLLinkElement | HTMLFormElement) => SDKEventAttrs) | SDKEventAttrs, + eventType: valueof + ): void => { + let elements: ArrayLike | Element[] = []; + let element: DOMHandlerElement; + let elementIndex: number; + const handler = (e: Event): void => { + const timeoutHandler = function(): void { if (element.href) { window.location.href = element.href; } else if (element.submit) { @@ -381,14 +429,14 @@ export default function Events(mpInstance) { 'DOM event triggered, handling event' ); - self.logEvent({ + this.logEvent({ messageType: Types.MessageType.PageEvent, name: typeof eventName === 'function' - ? eventName(element) + ? eventName(element as HTMLLinkElement) : eventName, - data: typeof data === 'function' ? data(element) : data, - eventType: eventType || Types.EventType.Other, + 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) @@ -401,7 +449,7 @@ export default function Events(mpInstance) { if (e.preventDefault) { e.preventDefault(); } else { - e.returnValue = false; + (e as { returnValue: boolean }).returnValue = false; } setTimeout( @@ -409,9 +457,7 @@ export default function Events(mpInstance) { mpInstance._Store.SDKConfig.timeout ); } - }, - element, - i; + }; if (!selector) { mpInstance.Logger.error("Can't bind event, selector is required"); @@ -422,7 +468,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) { @@ -434,17 +480,14 @@ export default function Events(mpInstance) { ', attaching event handlers' ); - for (i = 0; i < elements.length; i++) { - element = elements[i]; + for (elementIndex = 0; elementIndex < elements.length; elementIndex++) { + element = elements[elementIndex] as DOMHandlerElement; if (element.addEventListener) { - // Modern browsers element.addEventListener(domEvent, handler, false); } else if (element.attachEvent) { - // IE < 9 element.attachEvent('on' + domEvent, handler); } else { - // All other browsers element['on' + domEvent] = handler; } } 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/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/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/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/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.' + ); + }); +}); 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.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..821be660e 100644 --- a/test/src/tests-event-logging.js +++ b/test/src/tests-event-logging.ts @@ -6,10 +6,27 @@ import { urls, apiKey, testMPID, + mParticle, MPConfig, MessageType, } from './config/constants'; +declare global { + 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 { findEventFromRequest, findBatch, getIdentityEvent, waitForCondition, fetchMockSuccess, hasIdentifyReturned } = Utils; describe('event logging', function() { @@ -209,7 +226,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 +243,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 +265,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 +286,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 +459,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 +471,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 +494,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 +503,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 +527,7 @@ describe('event logging', function() { await waitForCondition(hasIdentifyReturned); fetchMock.resetHistory(); - mParticle.logEvent(); + (mParticle.logEvent as Function)(); fetchMock.calls().should.have.lengthOf(0); }); @@ -520,7 +537,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 +545,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 +643,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 +887,7 @@ describe('event logging', function() { }) let currentPosition; - function callback(position) { + function callback(position?: GeolocationPosition) { currentPosition = position; } const clock = sinon.useFakeTimers(); @@ -938,7 +955,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 +977,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 +987,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 +1009,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 +1035,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 +1064,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 +1091,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 +1118,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 +1131,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 +1156,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 +1206,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 +1289,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 +1297,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 +1318,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 +1329,7 @@ describe('event logging', function() { 'string', 'coupon' ); - const product2 = mParticle.eCommerce.createProduct( + const product2 = (mParticle.eCommerce.createProduct as Function)( 'galaxy', 'galaxySKU', 'string', @@ -1333,7 +1350,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 +1358,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);