diff --git a/src/consent.ts b/src/consent.ts index 81ae944cd..502153c99 100644 --- a/src/consent.ts +++ b/src/consent.ts @@ -89,7 +89,7 @@ export interface IConsentStateV2DTO { } export interface IConsentRulesValues { - consentPurpose: string; + consentPurpose: string | number; hasConsented: boolean; } export interface IConsentRules { diff --git a/src/helpers.js b/src/helpers.ts similarity index 69% rename from src/helpers.js rename to src/helpers.ts index 633c9d007..2c61d3095 100644 --- a/src/helpers.js +++ b/src/helpers.ts @@ -3,12 +3,19 @@ import Constants from './constants'; import * as utils from './utils'; import Validators from './validators'; import KitFilterHelper from './kitFilterHelper'; +import { IMParticleWebSDKInstance } from './mp-instance'; +import { SDKHelpersApi } from './sdkRuntimeModels'; +import { Dictionary } from './utils'; +import { IMParticleUser } from './identity-user-interfaces'; +import { MPID } from '@mparticle/web-sdk'; -var StorageNames = Constants.StorageNames; +const StorageNames = Constants.StorageNames; -export default function Helpers(mpInstance) { - var self = this; - this.canLog = function() { +export default function Helpers( + this: SDKHelpersApi, + mpInstance: IMParticleWebSDKInstance +): void { + this.canLog = function(): boolean { if ( mpInstance._Store.isEnabled && (mpInstance._Store.devToken || @@ -20,25 +27,25 @@ export default function Helpers(mpInstance) { return false; }; - this.getFeatureFlag = function(feature) { + this.getFeatureFlag = function(feature: string): boolean | string | null { if (mpInstance._Store.SDKConfig.flags.hasOwnProperty(feature)) { return mpInstance._Store.SDKConfig.flags[feature]; } return null; }; - this.invokeCallback = function( - callback, - code, - body, - mParticleUser, - previousMpid - ) { + this.invokeCallback = ( + callback: Function, + code: number, + body: string, + mParticleUser?: IMParticleUser, + previousMpid?: MPID + ): void => { if (!callback) { mpInstance.Logger.warning('There is no callback provided'); } try { - if (self.Validators.isFunction(callback)) { + if (this.Validators.isFunction(callback)) { callback({ httpCode: code, body: body, @@ -51,9 +58,9 @@ export default function Helpers(mpInstance) { }, getPreviousUser: function() { if (!previousMpid) { - var users = mpInstance.Identity.getUsers(); - var mostRecentUser = users.shift(); - var currentUser = + const users = mpInstance.Identity.getUsers(); + const mostRecentUser = users.shift(); + const currentUser = mParticleUser || mpInstance.Identity.getCurrentUser(); if ( @@ -62,7 +69,7 @@ export default function Helpers(mpInstance) { mostRecentUser.getMPID() === currentUser.getMPID() ) { - mostRecentUser = users.shift(); + return users.shift() || null; } return mostRecentUser || null; } else { @@ -78,13 +85,17 @@ export default function Helpers(mpInstance) { } }; - this.invokeAliasCallback = function(callback, code, message) { + this.invokeAliasCallback = ( + callback: Function, + code: number, + message?: string + ): void => { if (!callback) { mpInstance.Logger.warning('There is no callback provided'); } try { - if (self.Validators.isFunction(callback)) { - var callbackMessage = { + if (this.Validators.isFunction(callback)) { + const callbackMessage: Dictionary = { httpCode: code, }; if (message) { @@ -101,12 +112,15 @@ export default function Helpers(mpInstance) { this.extend = utils.extend; - this.createServiceUrl = function(secureServiceUrl, devToken) { - var serviceScheme = + this.createServiceUrl = function( + secureServiceUrl: string, + devToken?: string + ): string { + const serviceScheme = window.mParticle && mpInstance._Store.SDKConfig.forceHttps ? 'https://' : window.location.protocol + '//'; - var baseUrl; + let baseUrl: string; if (mpInstance._Store.SDKConfig.forceHttps) { baseUrl = 'https://' + secureServiceUrl; } else { @@ -118,8 +132,8 @@ export default function Helpers(mpInstance) { return baseUrl; }; - this.createXHR = function(cb) { - var xhr; + this.createXHR = function(cb: () => void): XMLHttpRequest { + let xhr: XMLHttpRequest; try { xhr = new window.XMLHttpRequest(); @@ -129,11 +143,11 @@ export default function Helpers(mpInstance) { if (xhr && cb && 'withCredentials' in xhr) { xhr.onreadystatechange = cb; - } else if (typeof window.XDomainRequest !== 'undefined') { + } else if (typeof (window as Dictionary).XDomainRequest !== 'undefined') { mpInstance.Logger.verbose('Creating XDomainRequest object'); try { - xhr = new window.XDomainRequest(); + xhr = new (window as Dictionary).XDomainRequest(); xhr.onload = cb; } catch (e) { mpInstance.Logger.error('Error creating XDomainRequest object'); @@ -143,17 +157,20 @@ export default function Helpers(mpInstance) { return xhr; }; - this.filterUserIdentities = function(userIdentitiesObject, filterList) { - var filteredUserIdentities = []; + this.filterUserIdentities = ( + userIdentitiesObject: Dictionary, + filterList: number[] + ): Array<{ Type: number; Identity: string }> => { + const filteredUserIdentities: Array<{ Type: number; Identity: string }> = []; if (userIdentitiesObject && Object.keys(userIdentitiesObject).length) { - for (var userIdentityName in userIdentitiesObject) { + for (const userIdentityName in userIdentitiesObject) { if (userIdentitiesObject.hasOwnProperty(userIdentityName)) { - var userIdentityType = Types.IdentityType.getIdentityType( + const userIdentityType = Types.IdentityType.getIdentityType( userIdentityName ); - if (!self.inArray(filterList, userIdentityType)) { - var identity = { + if (!this.inArray(filterList, userIdentityType)) { + const identity = { Type: userIdentityType, Identity: userIdentitiesObject[userIdentityName], }; @@ -176,8 +193,8 @@ export default function Helpers(mpInstance) { KitFilterHelper.filterUserIdentities; this.filterUserAttributes = KitFilterHelper.filterUserAttributes; - this.isEventType = function(type) { - for (var prop in Types.EventType) { + this.isEventType = function(type: number): boolean { + for (const prop in Types.EventType) { if (Types.EventType.hasOwnProperty(prop)) { if (Types.EventType[prop] === type) { return true; @@ -187,18 +204,21 @@ export default function Helpers(mpInstance) { return false; }; - this.sanitizeAttributes = function(attrs, name) { - if (!attrs || !self.isObject(attrs)) { + this.sanitizeAttributes = ( + attrs: Dictionary, + name: string + ): Dictionary | null => { + if (!attrs || !this.isObject(attrs)) { return null; } - var sanitizedAttrs = {}; + const sanitizedAttrs: Dictionary = {}; - for (var prop in attrs) { + for (const prop in attrs) { // Make sure that attribute values are not objects or arrays, which are not valid if ( attrs.hasOwnProperty(prop) && - self.Validators.isValidAttributeValue(attrs[prop]) + this.Validators.isValidAttributeValue(attrs[prop]) ) { sanitizedAttrs[prop] = attrs[prop]; } else { @@ -216,17 +236,17 @@ export default function Helpers(mpInstance) { }; this.isDelayedByIntegration = function( - delayedIntegrations, - timeoutStart, - now - ) { + delayedIntegrations: Dictionary, + timeoutStart: number, + now: number + ): boolean { if ( now - timeoutStart > mpInstance._Store.SDKConfig.integrationDelayTimeout ) { return false; } - for (var integration in delayedIntegrations) { + for (const integration in delayedIntegrations) { if (delayedIntegrations[integration] === true) { return true; } else { @@ -236,7 +256,7 @@ export default function Helpers(mpInstance) { return false; }; - this.createMainStorageName = function(workspaceToken) { + this.createMainStorageName = function(workspaceToken: string): string { if (workspaceToken) { return StorageNames.currentStorageName + '_' + workspaceToken; } else { diff --git a/src/sdkRuntimeModels.ts b/src/sdkRuntimeModels.ts index e31084228..5bfe7197d 100644 --- a/src/sdkRuntimeModels.ts +++ b/src/sdkRuntimeModels.ts @@ -161,9 +161,9 @@ export interface SDKProductAction { } export interface SDKProduct { - Sku: string; - Name: string; - Price: number; + Sku?: string; + Name?: string; + Price?: number; Quantity?: number; Brand?: string; Variant?: string; @@ -173,7 +173,7 @@ export interface SDKProduct { TotalAmount?: number; // https://go.mparticle.com/work/SQDSDKS-4801 - Attributes?: Record; + Attributes?: Record | null; } // https://go.mparticle.com/work/SQDSDKS-6949 @@ -253,7 +253,7 @@ export interface MParticleWebSDK { startTrackingLocation(callback?: Callback): void; stopTrackingLocation(): void; - generateHash(value: string): string; + generateHash(value: string): number; setIntegrationAttribute( integrationModuleId: number, attrs: IntegrationAttribute @@ -306,7 +306,7 @@ export const LogLevelType = { // Currently, this extends MPConfiguration in @types/mparticle__web-sdk // and the two will be merged in once the Store module is refactored export interface SDKInitConfig - extends Omit { + extends Omit { dataPlan?: DataPlanConfig | KitBlockerDataPlan; // TODO: These should be eventually split into two different attributes logLevel?: LogLevelType; @@ -363,9 +363,21 @@ export interface SDKHelpersApi { findKeyInObject?(obj: any, key: string): string; parseNumber?(value: string | number): number; generateUniqueId(); - generateHash?(value: string): string; + generateHash?(value: string): number; // https://go.mparticle.com/work/SQDSDKS-6317 getFeatureFlag?(feature: string): boolean | string; // TODO: Feature Constants should be converted to enum + decoded?(s: string): string; + parseStringOrNumber?(value: string): string | number; + inArray?(items: number[], value: number): boolean; + converted?(s: string): string; + filterUserIdentitiesForForwarders?( + userIdentities: Dictionary, + filterList: number[] + ): Dictionary; + filterUserAttributes?( + userAttributes: Dictionary, + filterList: number[] + ): Dictionary; invokeAliasCallback( aliasCallback: IAliasCallback, number: number, @@ -376,8 +388,12 @@ export interface SDKHelpersApi { timeoutStart: number, now: number ): boolean; - isEventType?(type: valueof): boolean; + isEventType?(type: number | valueof): boolean; isObject?(item: any); + filterUserIdentities?( + userIdentitiesObject: Dictionary, + filterList: number[] + ): Array<{ Type: number; Identity: string }>; invokeCallback?( callback: IdentityCallback, code: number, diff --git a/test/src/tests-helpers.js b/test/src/tests-helpers.ts similarity index 74% rename from test/src/tests-helpers.js rename to test/src/tests-helpers.ts index 0ef812119..c6d449bab 100644 --- a/test/src/tests-helpers.js +++ b/test/src/tests-helpers.ts @@ -9,10 +9,12 @@ import sinon from 'sinon'; import fetchMock from 'fetch-mock/esm/client'; import Utils from './config/utils'; +declare var Should: Function; + const { waitForCondition, fetchMockSuccess, hasIdentityCallInflightReturned } = Utils; describe('helpers', function() { - let sandbox; + let sandbox: sinon.SinonSandbox; beforeEach(function() { mParticle._resetForTests(MPConfig); @@ -44,24 +46,13 @@ describe('helpers', function() { }); it('should correctly validate an attribute value', () => { - const validatedString = mParticle - .getInstance() - ._Helpers.Validators.isValidAttributeValue('testValue1'); - const validatedNumber = mParticle - .getInstance() - ._Helpers.Validators.isValidAttributeValue(1); - const validatedNull = mParticle - .getInstance() - ._Helpers.Validators.isValidAttributeValue(null); - const validatedObject = mParticle - .getInstance() - ._Helpers.Validators.isValidAttributeValue({}); - const validatedArray = mParticle - .getInstance() - ._Helpers.Validators.isValidAttributeValue([]); - const validatedUndefined = mParticle - .getInstance() - ._Helpers.Validators.isValidAttributeValue(undefined); + const isValid = mParticle.getInstance()._Helpers.Validators.isValidAttributeValue as Function; + const validatedString = isValid('testValue1'); + const validatedNumber = isValid(1); + const validatedNull = isValid(null); + const validatedObject = isValid({}); + const validatedArray = isValid([]); + const validatedUndefined = isValid(undefined); validatedString.should.be.ok(); validatedNumber.should.be.ok(); @@ -74,7 +65,7 @@ describe('helpers', function() { it('should return event name in warning when sanitizing invalid attributes', async () => { await waitForCondition(hasIdentityCallInflightReturned); const bond = sandbox.spy(mParticle.getInstance().Logger, 'warning'); - mParticle.logEvent('eventName', mParticle.EventType.Location, {invalidValue: {}}); + (mParticle.logEvent as Function)('eventName', (mParticle as any).EventType.Location, {invalidValue: {}}); bond.called.should.eql(true); bond.callCount.should.equal(1); @@ -86,7 +77,7 @@ describe('helpers', function() { it('should return product name in warning when sanitizing invalid attributes', () => { const bond = sandbox.spy(mParticle.getInstance().Logger, 'warning'); - mParticle.eCommerce.createProduct( + (mParticle.eCommerce.createProduct as Function)( 'productName', 'sku', 1, @@ -115,7 +106,7 @@ describe('helpers', function() { const product2 = mParticle.eCommerce.createProduct('prod2', 'prod2sku', 799); const customAttributes = {invalidValue: {}}; - mParticle.eCommerce.logProductAction(mParticle.ProductActionType.AddToCart, [product1, product2], customAttributes); + (mParticle.eCommerce.logProductAction as Function)((mParticle as any).ProductActionType.AddToCart, [product1, product2], customAttributes); bond.called.should.eql(true); bond.callCount.should.equal(1); @@ -157,21 +148,12 @@ describe('helpers', function() { const object = {}; const array = []; - const stringResult = mParticle - .getInstance() - ._Helpers.parseStringOrNumber(string); - const numberResult = mParticle - .getInstance() - ._Helpers.parseStringOrNumber(number); - const objectResult = mParticle - .getInstance() - ._Helpers.parseStringOrNumber(object); - const arrayResult = mParticle - .getInstance() - ._Helpers.parseStringOrNumber(array); - const nullResult = mParticle - .getInstance() - ._Helpers.parseStringOrNumber(null); + const parseStringOrNumber = mParticle.getInstance()._Helpers.parseStringOrNumber as Function; + const stringResult = parseStringOrNumber(string); + const numberResult = parseStringOrNumber(number); + const objectResult = parseStringOrNumber(object); + const arrayResult = parseStringOrNumber(array); + const nullResult = parseStringOrNumber(null); stringResult.should.equal(string); numberResult.should.equal(number); @@ -225,18 +207,11 @@ describe('helpers', function() { 10: false, }; - const result1 = mParticle - .getInstance() - ._Helpers.isDelayedByIntegration(integrationDelays1); - const result2 = mParticle - .getInstance() - ._Helpers.isDelayedByIntegration(integrationDelays2); - const result3 = mParticle - .getInstance() - ._Helpers.isDelayedByIntegration(integrationDelays3); - const result4 = mParticle - .getInstance() - ._Helpers.isDelayedByIntegration(integrationDelays4); + const isDelayed = mParticle.getInstance()._Helpers.isDelayedByIntegration as Function; + const result1 = isDelayed(integrationDelays1); + const result2 = isDelayed(integrationDelays2); + const result3 = isDelayed(integrationDelays3); + const result4 = isDelayed(integrationDelays4); result1.should.equal(true); result2.should.equal(true); @@ -246,32 +221,31 @@ describe('helpers', function() { it('should return false if integration delay object is empty', () => { const emptyIntegrationDelays = {}; - const result1 = mParticle - .getInstance() - ._Helpers.isDelayedByIntegration(emptyIntegrationDelays); + const result1 = (mParticle.getInstance()._Helpers.isDelayedByIntegration as Function)(emptyIntegrationDelays); result1.should.equal(false); }); it('should return 0 when hashing undefined or null', () => { - mParticle.generateHash(undefined) + const generateHash = mParticle.generateHash as Function; + generateHash(undefined) .should.equal(0); - mParticle.generateHash(null) + generateHash(null) .should.equal(0); - (typeof mParticle.generateHash(false)).should.equal('number'); - mParticle.generateHash(false) + (typeof generateHash(false)).should.equal('number'); + generateHash(false) .should.not.equal(0); }); it('should generate random value', () => { let randomValue = mParticle.getInstance()._Helpers.generateUniqueId(); randomValue.should.be.ok(); - window.crypto.getRandomValues = undefined; + (window.crypto as any).getRandomValues = undefined; randomValue = mParticle.getInstance()._Helpers.generateUniqueId(); randomValue.should.be.ok(); //old browsers may return undefined despite //defining the getRandomValues API. - window.crypto.getRandomValues = function(a) { + (window.crypto as any).getRandomValues = function(a: any) { a = undefined; return a; }; @@ -288,9 +262,7 @@ describe('helpers', function() { }); it('should create a storage name based on default mParticle storage version if no apiKey is passed in', () => { - const cookieName = mParticle - .getInstance() - ._Helpers.createMainStorageName(); + const cookieName = (mParticle.getInstance()._Helpers.createMainStorageName as Function)(); cookieName.should.equal('mprtcl-v4'); }); }); \ No newline at end of file