From 59819c9c866967cebfd52c4f505d0a2b0e95b17c Mon Sep 17 00:00:00 2001 From: Robert Ing Date: Tue, 3 Sep 2024 15:18:59 -0400 Subject: [PATCH] refactor: Use fetch instead of XHR for forwarding stats uploads (#904) --- src/apiClient.ts | 85 ++++++++++++----------- src/forwarders.js | 32 +++++++++ src/persistence.interfaces.ts | 4 +- src/sdkRuntimeModels.ts | 1 + test/fixtures/events.ts | 4 ++ test/src/tests-batchUploader.ts | 3 + test/src/tests-forwarders.js | 7 +- test/src/tests-kit-blocking.ts | 3 + test/src/tests-runtimeToBatchEventsDTO.ts | 2 + test/src/tests-serverModel.ts | 1 + 10 files changed, 97 insertions(+), 45 deletions(-) diff --git a/src/apiClient.ts b/src/apiClient.ts index ec4d8899..3a6695ad 100644 --- a/src/apiClient.ts +++ b/src/apiClient.ts @@ -1,14 +1,13 @@ import Constants from './constants'; import Types from './types'; import { BatchUploader } from './batchUploader'; -import { MParticleWebSDK, SDKEvent } from './sdkRuntimeModels'; +import { MParticleWebSDK, SDKEvent, SDKDataPlan } from './sdkRuntimeModels'; import KitBlocker from './kitBlocking'; import { Dictionary, getRampNumber, isEmpty, parseNumber } from './utils'; import { IUploadObject } from './serverModel'; import { MPForwarder } from './forwarders.interfaces'; -import { IMParticleUser } from './identity-user-interfaces'; - -export type ForwardingStatsData = Dictionary; +import { IMParticleUser, ISDKUserAttributes } from './identity-user-interfaces'; +import { AsyncUploader, FetchUploader, XHRUploader } from './uploaders'; export interface IAPIClient { uploader: BatchUploader | null; @@ -18,18 +17,30 @@ export interface IAPIClient { sendEventToServer: (event: SDKEvent, _options?: Dictionary) => void; sendSingleEventToServer: (event: SDKEvent) => void; sendBatchForwardingStatsToServer: ( - forwardingStatsData: ForwardingStatsData, + forwardingStatsData: IForwardingStatsData, xhr: XMLHttpRequest ) => void; - sendSingleForwardingStatsToServer: ( - forwardingStatsData: ForwardingStatsData - ) => void; + initializeForwarderStatsUploader: () => AsyncUploader; prepareForwardingStats: ( forwarder: MPForwarder, event: IUploadObject ) => void; } +export interface IForwardingStatsData { + mid: number; // Module Id + esid: number; // Event Subscription Id + n: string; // Event Name + attrs: ISDKUserAttributes; // User Attributes + sdk: string; // SDK Version + dt: number; // Data Type + et: number; // Event Type + dbg: boolean; // Development Mode (for debugging in Live Stream) + ct: number; // Current Timestamp + eec: number; // Expanded Event Count + dp: SDKDataPlan; // Data Plan +} + export default function APIClient( this: IAPIClient, mpInstance: MParticleWebSDK, @@ -161,41 +172,26 @@ export default function APIClient( } }; - this.sendSingleForwardingStatsToServer = function(forwardingStatsData) { - let url; - let data; - try { - const xhrCallback = function() { - if (xhr.readyState === 4) { - if (xhr.status === 202) { - mpInstance.Logger.verbose( - 'Successfully sent ' + - xhr.statusText + - ' from server' - ); - } - } - }; - const xhr = mpInstance._Helpers.createXHR(xhrCallback); - url = mpInstance._Helpers.createServiceUrl( - mpInstance._Store.SDKConfig.v1SecureServiceUrl, - mpInstance._Store.devToken - ); - data = forwardingStatsData; + this.initializeForwarderStatsUploader = (): AsyncUploader => { + const { + v1SecureServiceUrl: forwardingDomain, + } = mpInstance._Store.SDKConfig; + const { devToken } = mpInstance._Store; - if (xhr) { - xhr.open('post', url + '/Forwarding'); - xhr.send(JSON.stringify(data)); - } - } catch (e) { - mpInstance.Logger.error( - 'Error sending forwarding stats to mParticle servers.' - ); - } + const uploadUrl: string = `https://${forwardingDomain}${devToken}/Forwarding`; + + const uploader: AsyncUploader = window.fetch + ? new FetchUploader(uploadUrl) + : new XHRUploader(uploadUrl); + + return uploader; }; - this.prepareForwardingStats = function(forwarder, event) { - let forwardingStatsData; + this.prepareForwardingStats = function( + forwarder: MPForwarder, + event:SDKEvent, + ) : void { + let forwardingStatsData: IForwardingStatsData; const queue = mpInstance._Forwarders.getForwarderStatsQueue(); if (forwarder && forwarder.isVisible) { @@ -212,6 +208,11 @@ export default function APIClient( eec: event.ExpandedEventCount, dp: event.DataPlan, }; + + const { + sendSingleForwardingStatsToServer, + setForwarderStatsQueue, + } = mpInstance._Forwarders; if ( mpInstance._Helpers.getFeatureFlag( @@ -219,9 +220,9 @@ export default function APIClient( ) ) { queue.push(forwardingStatsData); - mpInstance._Forwarders.setForwarderStatsQueue(queue); + setForwarderStatsQueue(queue); } else { - self.sendSingleForwardingStatsToServer(forwardingStatsData); + sendSingleForwardingStatsToServer(forwardingStatsData); } } }; diff --git a/src/forwarders.js b/src/forwarders.js index 8347f549..2847020b 100644 --- a/src/forwarders.js +++ b/src/forwarders.js @@ -3,11 +3,16 @@ import filteredMparticleUser from './filteredMparticleUser'; import { isEmpty } from './utils'; import KitFilterHelper from './kitFilterHelper'; import Constants from './constants'; +import APIClient from './apiClient'; const { Modify, Identify, Login, Logout } = Constants.IdentityMethods; export default function Forwarders(mpInstance, kitBlocker) { var self = this; + this.forwarderStatsUploader = new APIClient( + mpInstance, + kitBlocker + ).initializeForwarderStatsUploader(); const UserAttributeActionTypes = { setUserAttribute: 'setUserAttribute', @@ -771,4 +776,31 @@ export default function Forwarders(mpInstance, kitBlocker) { ); } }; + + this.sendSingleForwardingStatsToServer = async forwardingStatsData => { + // https://go.mparticle.com/work/SQDSDKS-6568 + const fetchPayload = { + method: 'post', + body: JSON.stringify(forwardingStatsData), + headers: { + Accept: 'text/plain;charset=UTF-8', + 'Content-Type': 'text/plain;charset=UTF-8', + }, + }; + + const response = await this.forwarderStatsUploader.upload(fetchPayload); + + let message; + // This is a fire and forget, so we only need to log the response based on the code, and not return any response body + if (response.status === 202) { + // https://go.mparticle.com/work/SQDSDKS-6670 + message = 'Successfully sent forwarding stats to mParticle Servers'; + } else { + message = + 'Issue with forwarding stats to mParticle Servers, received HTTP Code of ' + + response.statusText; + } + + mpInstance?.Logger?.verbose(message); + }; } diff --git a/src/persistence.interfaces.ts b/src/persistence.interfaces.ts index 1443ec89..59a1bd76 100644 --- a/src/persistence.interfaces.ts +++ b/src/persistence.interfaces.ts @@ -5,7 +5,7 @@ import { Product, UserIdentities, } from '@mparticle/web-sdk'; -import { ForwardingStatsData } from './apiClient'; +import { IForwardingStatsData } from './apiClient'; import { IntegrationAttributes, ServerSettings, @@ -18,7 +18,7 @@ import { UserAttributes } from './identity-user-interfaces'; export type UploadsTable = Dictionary; export interface iForwardingStatsBatches { uploadsTable: UploadsTable; - forwardingStatsEventQueue: ForwardingStatsData[]; + forwardingStatsEventQueue: IForwardingStatsData[]; } export interface IGlobalStoreV2MinifiedKeys { diff --git a/src/sdkRuntimeModels.ts b/src/sdkRuntimeModels.ts index 8513a98f..42920cf6 100644 --- a/src/sdkRuntimeModels.ts +++ b/src/sdkRuntimeModels.ts @@ -65,6 +65,7 @@ export interface SDKEvent { CurrencyCode: string; DataPlan?: SDKDataPlan; LaunchReferral?: string; + ExpandedEventCount: number; } export interface SDKGeoLocation { lat: number | string; diff --git a/test/fixtures/events.ts b/test/fixtures/events.ts index f9bcb47e..6b0aecbf 100644 --- a/test/fixtures/events.ts +++ b/test/fixtures/events.ts @@ -6,6 +6,7 @@ export const event0: SDKEvent = { SourceMessageId: 'test-smid', EventDataType: 4, EventCategory: 1, + ExpandedEventCount: 0, CustomFlags: {}, IsFirstRun: false, CurrencyCode: null, @@ -27,6 +28,7 @@ export const event1: SDKEvent = { SourceMessageId: 'test-smid', EventDataType: 4, EventCategory: 1, + ExpandedEventCount: 0, CustomFlags: {}, IsFirstRun: false, CurrencyCode: null, @@ -48,6 +50,7 @@ export const event2: SDKEvent = { SourceMessageId: 'test-smid', EventDataType: 4, EventCategory: 1, + ExpandedEventCount: 0, CustomFlags: {}, IsFirstRun: false, CurrencyCode: null, @@ -69,6 +72,7 @@ export const event3: SDKEvent = { SourceMessageId: 'test-smid', EventDataType: 4, EventCategory: 1, + ExpandedEventCount: 0, CustomFlags: {}, IsFirstRun: false, CurrencyCode: null, diff --git a/test/src/tests-batchUploader.ts b/test/src/tests-batchUploader.ts index cf625b64..c12dfc59 100644 --- a/test/src/tests-batchUploader.ts +++ b/test/src/tests-batchUploader.ts @@ -63,6 +63,7 @@ describe('batch uploader', () => { SourceMessageId: 'test-smid', EventDataType: 4, EventCategory: 1, + ExpandedEventCount: 0, CustomFlags: {}, IsFirstRun: false, CurrencyCode: null, @@ -384,6 +385,7 @@ describe('batch uploader', () => { SourceMessageId: 'test-smid', EventDataType: 4, EventCategory: 1, + ExpandedEventCount: 0, CustomFlags: {}, IsFirstRun: false, CurrencyCode: null, @@ -439,6 +441,7 @@ describe('batch uploader', () => { SourceMessageId: 'test-smid', EventDataType: 4, EventCategory: 1, + ExpandedEventCount: 0, CustomFlags: {}, IsFirstRun: false, CurrencyCode: null, diff --git a/test/src/tests-forwarders.js b/test/src/tests-forwarders.js index 8bf07cc7..3a1ef39c 100644 --- a/test/src/tests-forwarders.js +++ b/test/src/tests-forwarders.js @@ -993,6 +993,11 @@ describe('forwarders', function() { }); it('sends events to forwarder v1 endpoint when mParticle.config.isDevelopmentMode = config.isDebug = false', function(done) { + fetchMock.post(urls.forwarding, { + status: 200, + body: JSON.stringify({ mpid: testMPID, is_logged_in: false }), + }); + mParticle._resetForTests(MPConfig); mParticle.config.isDevelopmentMode = false; const mockForwarder = new MockForwarder(); @@ -1011,7 +1016,7 @@ describe('forwarders', function() { true ); - mockServer.requests[mockServer.requests.length - 1].url.includes( + fetchMock.lastCall().includes( '/v1/JS/test_key/Forwarding' ); diff --git a/test/src/tests-kit-blocking.ts b/test/src/tests-kit-blocking.ts index bce07c56..10afcaa6 100644 --- a/test/src/tests-kit-blocking.ts +++ b/test/src/tests-kit-blocking.ts @@ -107,6 +107,7 @@ describe('kit blocking', () => { IsFirstRun: true, EventName: null, EventCategory: null, + ExpandedEventCount: 0, MPID: testMPID, EventAttributes: null, SDKVersion: '1.0.0', @@ -219,6 +220,7 @@ describe('kit blocking', () => { IsFirstRun: true, EventName: null, EventCategory: null, + ExpandedEventCount: 0, MPID: testMPID, EventAttributes: null, SDKVersion: '1.0.0', @@ -430,6 +432,7 @@ describe('kit blocking', () => { IsFirstRun: true, EventName: null, EventCategory: null, + ExpandedEventCount: 0, MPID: testMPID, EventAttributes: null, SDKVersion: '1.0.0', diff --git a/test/src/tests-runtimeToBatchEventsDTO.ts b/test/src/tests-runtimeToBatchEventsDTO.ts index 607e393d..dc1024b3 100644 --- a/test/src/tests-runtimeToBatchEventsDTO.ts +++ b/test/src/tests-runtimeToBatchEventsDTO.ts @@ -320,6 +320,7 @@ describe('Old model to batch model conversion', () => { const sdkEvent: SDKEvent = { EventName: 'eCommerce - Purchase', EventCategory: 16, + ExpandedEventCount: 0, EventAttributes: null, SDKVersion: '2.9.10', SourceMessageId: 'testSMID', @@ -420,6 +421,7 @@ describe('Old model to batch model conversion', () => { const sdkEvent: SDKEvent = { EventName: "Pause Event", EventCategory: 9, + ExpandedEventCount: 0, EventDataType: 4, EventAttributes: { content_duration: '120000', diff --git a/test/src/tests-serverModel.ts b/test/src/tests-serverModel.ts index 2bbb0613..5cc3c70e 100644 --- a/test/src/tests-serverModel.ts +++ b/test/src/tests-serverModel.ts @@ -82,6 +82,7 @@ describe('ServerModel', () => { // or set up an event factory const event: SDKEvent = { EventName: 'Test Event', + ExpandedEventCount: 0, MPID: '', IsFirstRun: true, DeviceId: 'test-device',