UNPKG

@swrve/smarttv-sdk

Version:

Swrve marketing engagement platform SDK for SmartTV OTT devices

1,265 lines (1,080 loc) 54.3 kB
import {EventFactory} from "./Events/EventFactory"; import {ProfileManager} from "./Profile/ProfileManager"; import PAL from "./utils/PAL"; import {CampaignManager} from "./Campaigns/CampaignManager"; import {ISwrveButton, ISwrveCampaign, ISwrveCampaignResourceResponse, ISwrveEmbeddedMessage, IUserResource } from "./Campaigns/ISwrveCampaign"; import {IPlatform, NETWORK_CONNECTED, NetworkListener} from "./utils/platforms/IPlatform"; import {ResourceManagerInternal} from "./Resources/ResourceManagerInternal"; import {ResourceManager} from "./Resources/ResourceManager"; import { GetResourcesCallback, GetUserResourcesDiffCallback, OnCampaignLoadedCallback, OnCustomButtonClicked, OnIAMDismissed, OnIAMShown, OnIdentifySuccessCallback, OnIdentifyErrorCallback, OnMessageListener, OnResourcesLoadedCallback, } from "./SwrveSDK"; import * as SwrveConstants from "./utils/SwrveConstants"; import {StorageManager} from "./Storage/StorageManager"; import {IUserInfo} from "./Profile/IUser"; import {ISwrveInternalConfig, validateConfig} from "./Config/ISwrveInternalConfig"; import SwrveLogger from "./utils/SwrveLogger"; import {EventManager} from "./Events/EventManager"; import {SwrveRestClient} from "./RestClient/SwrveRestClient"; import ISwrveConfig, {configWithDefaults} from "./Config/ISwrveConfig"; import {queryDeviceProperties} from "./utils/DeviceProperties"; import IResourceDiff from "./WebApi/Resources/IResourceDiff"; import IDictionary from "./utils/IDictionary"; import IRestResponse from "./RestClient/IRestResponse"; import IReadonlyDictionary from "./utils/IReadonlyDictionary"; import IReward from "./WebApi/Events/IReward"; import SwrveEvent from "./WebApi/Events/SwrveEvent"; import DateHelper from "./utils/DateHelper"; import { getInstallDateFormat } from "./utils/TimeHelper"; import {CAMPAIGN_STATE, SWRVE_PAYLOAD_DEVICE_TYPE_TV} from "./utils/SwrveConstants"; import { TextTemplating } from "./utils/TextTemplating"; import { RealTimeUserPropertiesManager } from "./UserProperties/RealTimeUserPropertiesManager"; import { combineDictionaries } from "./utils/DictionaryHelper"; import { generateUuid } from "./utils/uuid"; import { ICampaignState } from "./Campaigns/ICampaignState"; export class SwrveInternal { public readonly profileManager: ProfileManager; private readonly config: ISwrveInternalConfig; private readonly evtManager: EventManager; private readonly eventFactory: EventFactory; private readonly restClient: SwrveRestClient; private readonly campaignManager: CampaignManager; private readonly resourceManager: ResourceManagerInternal; private readonly platform: IPlatform; private readonly realTimeUserPropertiesManager: RealTimeUserPropertiesManager; private onResourcesLoadedCallback: OnResourcesLoadedCallback | null = null; private onCampaignLoadedCallback: OnCampaignLoadedCallback | null = null; private onCustomButtonClickedCallback: OnCustomButtonClicked | null = null; private onIAMDismissedCallback: OnIAMDismissed | null = null; private onIAMShownCallback: OnIAMShown | null = null; private eventLoopTimer: number = 0; private flushFrequency: number = 0; private _shutdown: boolean = false; private autoShowEnabled: boolean = true; private pauseSDK: boolean = false; private installDate: string = ""; private identifyNetworkMonitorHandle?: NetworkListener; private campaignNetworkMonitorHandle?: NetworkListener; private identifiedOnAnotherDevice: boolean = false; private refreshAssets: boolean = true; public constructor(config: Readonly<ISwrveConfig>, dependencies?: IDependencies) { dependencies = dependencies || {}; this.platform = config.customPlatform || dependencies.platform || PAL.getPlatform(config); if (PAL.platform !== undefined) { //in case it was custom, set the static instance in PAl, as asset /campaign /storage managers reference it. PAL.platform = this.platform; } this.loadInstallDate(); let lastUserId = StorageManager.getData( SwrveConstants.SWRVE_USER_ID, ); SwrveLogger.debug(`last user ID: ${lastUserId}`); if (lastUserId === null) { lastUserId = generateUuid().toString(); } this.config = configWithDefaults(config, lastUserId); validateConfig(this.config); this.resourceManager = new ResourceManagerInternal(); this.profileManager = dependencies.profileManager || new ProfileManager( this.config.userId, this.config.appId, this.config.apiKey, this.config.newSessionInterval, ); this.realTimeUserPropertiesManager = new RealTimeUserPropertiesManager( this.profileManager, ); this.campaignManager = dependencies.campaignManager || new CampaignManager(this.profileManager, this.platform, this.config, this.getResourceManager()); this.campaignManager.onPageViewed((messageId, pageId, pageName) => { this.handlePageViewed(messageId, pageId, pageName); }); this.campaignManager.onButtonClicked((button, campaign, pageId, pageName) => { this.handleButtonClicked(button, campaign, pageId, pageName); }); this.campaignManager.onBackButtonClicked(() => { if (this.onIAMDismissedCallback) { this.onIAMDismissedCallback(); } }); this.restClient = dependencies.restClient || new SwrveRestClient(this.config, this.profileManager, this.platform); this.evtManager = dependencies.eventManager || new EventManager(this.restClient); this.eventFactory = new EventFactory(); if ( this.config.embeddedMessageConfig && this.config.embeddedMessageConfig.embeddedCallback ) { if ( typeof this.config.embeddedMessageConfig.embeddedCallback !== "function" ) { SwrveLogger.error( SwrveConstants.INVALID_FUNCTION.replace("$", "onEmbeddedMessage"), ); } this.campaignManager.onEmbeddedMessage( this.config.embeddedMessageConfig.embeddedCallback, ); } } public init(): void { ProfileManager.storeUserId(this.config.userId); window.onbeforeunload = (): void => { this.pageStateHandler(); this.shutdown(); }; window.onblur = (): void => { this.pageStateHandler(); }; this.platform.init(["language", "countryCode", "timezone", "firmware", "deviceHeight", "deviceWidth"]) .then(() => { if (!this._shutdown) { this.queueDeviceProperties(); } }); if (this.isIdentifyCallPending()) { const externalId = StorageManager.getData(SwrveConstants.IDENTIFY_CALL_PENDING_EXTERNAL_ID)!; const swrveId = StorageManager.getData(SwrveConstants.IDENTIFY_CALL_PENDING)!; this.makeIdentityCall(externalId, this.profileManager.currentUser.userId, swrveId, () => {}, () => this.initSDK()); } else if (this.config.managedMode) { SwrveLogger.debug("SwrveSDK: This application has started Swrve in MANAGED mode. Call start() to begin tracking"); this.stop(); } else { this.initSDK(); } } public getConfig(): ISwrveInternalConfig { return this.config; } public getUserInfo(): IUserInfo { const { userId, firstUse, sessionStart, isQAUser } = this.profileManager.currentUser; return { userId, firstUse, sessionStart, isQAUser }; } public getMessageCenterCampaigns(personalizationProperties?: IDictionary<string>): ISwrveCampaign[] { if (personalizationProperties) { return this.campaignManager.getMessageCenterCampaigns(personalizationProperties); } else { return this.campaignManager.getMessageCenterCampaigns(); } } public getPlatform(): IPlatform { return this.platform; } //******************************************** Embedded Campaigns ********************************************/ public embeddedMessageWasShownToUser(message: ISwrveEmbeddedMessage): void { this.campaignManager.updateCampaignState(message); this.queueMessageImpressionEvent(message.id, { embedded: "true" }); } public embeddedMessageButtonWasPressed( message: ISwrveEmbeddedMessage, buttonName: string, ): void { const nextSeqNum = this.profileManager.getNextSequenceNumber(); const evt = this.eventFactory.getButtonClickEvent(nextSeqNum, message.id, buttonName, "true", this.platform.os, 0, "", ""); this.queueEvent(evt); if (this.profileManager.isQAUser()) { this.queueEvent(this.eventFactory.getWrappedNamedEvent(evt)); } } public getPersonalizedEmbeddedMessageData( message: ISwrveEmbeddedMessage, personalizationProperties: IDictionary<string>, ): string | null { if (message != null) { try { if (message.type === "json") { return TextTemplating.applyTextTemplatingToJSON( message.data, personalizationProperties, ); } else { return TextTemplating.applyTextTemplatingToString( message.data, personalizationProperties, ); } } catch (e) { SwrveLogger.error( "Campaign id:%s Could not resolve, error with personalization", e, ); } } return null; } public getPersonalizedText( text: string, personalizationProperties: IDictionary<string>, ): string | null { if (text != null) { try { return TextTemplating.applyTextTemplatingToString( text, personalizationProperties, ); } catch (e) { SwrveLogger.error("Could not resolve, error with personalization", e); } } return null; } //************************************ EVENTS ********************************************************************/ public sendEvent(keyName: string, payload: IDictionary<string|number>): void { if (this.pauseSDK) return; this.validateEventName(keyName); const evt = this.eventFactory.getNamedEvent( keyName, payload, this.profileManager.getNextSequenceNumber(), DateHelper.nowInUtcTime()); this.queueEvent(evt); if (this.profileManager.isQAUser()) { this.queueEvent(this.eventFactory.getWrappedNamedEvent(evt)); this.sendQueuedEvents(); } this.checkTriggers(keyName, payload); } public sendUserUpdateWithDate(keyName: string, date: Date): void { if (this.pauseSDK) return; this.validateEventName(keyName); const evt = this.eventFactory.getUserUpdateWithDate( keyName, date, this.profileManager.getNextSequenceNumber(), DateHelper.nowInUtcTime()); this.queueEvent(evt); if (this.profileManager.isQAUser()) { this.queueEvent(this.eventFactory.getWrappedUserUpdateWithDate(evt)); this.sendQueuedEvents(); } } public sendUserUpdate(attributes: IReadonlyDictionary<string | number | boolean>): void { if (this.pauseSDK) return; const evt = this.eventFactory.getUserUpdate(attributes, this.profileManager.getNextSequenceNumber(), DateHelper.nowInUtcTime()); this.queueEvent(evt); if (this.profileManager.isQAUser()) { this.queueEvent(this.eventFactory.getWrappedUserUpdate(evt)); this.sendQueuedEvents(); } } public sendPurchaseEvent(keyName: string, currency: string, cost: number, quantity: number): void { if (this.pauseSDK) return; this.validateEventName(keyName); const evt = this.eventFactory.getPurchaseEvent(keyName, currency, cost, quantity, this.profileManager.getNextSequenceNumber(), DateHelper.nowInUtcTime()); this.queueEvent(evt); if (this.profileManager.isQAUser()) { this.queueEvent(this.eventFactory.getWrappedPurchaseEvent(evt)); } this.sendQueuedEvents(); } public sendInAppPurchaseWithoutReceipt(quantity: number, productId: string, productPrice: number, currency: string, rewards?: IReadonlyDictionary<IReward>): void { if (this.pauseSDK) return; const evt = this.eventFactory.getInAppPurchaseEventWithoutReceipt(quantity, productId, productPrice, currency, this.profileManager.getNextSequenceNumber(), DateHelper.nowInUtcTime(), rewards); this.queueEvent(evt); if (this.profileManager.isQAUser()) { this.queueEvent(this.eventFactory.getWrappedInAppPurchaseEventWithoutReceipt(evt)); } this.sendQueuedEvents(); } public sendCurrencyGiven(currencyGiven: string, amount: number): void { if (this.pauseSDK) return; const evt = this.eventFactory.getCurrencyGivenEvent( currencyGiven, amount, this.profileManager.getNextSequenceNumber(), DateHelper.nowInUtcTime()); this.queueEvent(evt); if (this.profileManager.isQAUser()) { this.queueEvent(this.eventFactory.getWrappedCurrencyGivenEvent(evt)); this.sendQueuedEvents(); } } public sendQueuedEvents(userId: string = this.profileManager.currentUser.userId, forceUpdate: boolean = false): void { SwrveLogger.info("SWRVE INTERNAL: SEND QUEUED EVENTS"); if (this.pauseSDK) { return; } this.evtManager.sendQueue(userId).then(success => { if (success || forceUpdate) { this.updateCampaignsAndResources(forceUpdate); } }); } //******************************************** OTHER *********************************************************/ public stop(): void { this.pauseSDK = true; clearInterval(this.eventLoopTimer); this.eventLoopTimer = 0; } public updateCampaignsAndResources(forceUpdate: boolean = false): Promise<void> { SwrveLogger.info("updateCampaignsAndResources"); return this.restClient.getCampaignsAndResources() .then(response => { this.handleCampaignResponse(response); if (this.isCampaignCallPending()) { this.cleanUpCampaignCallPending(); } }) .catch((error) => { SwrveLogger.warn("getCampaigns failed ", error); if (!this.isCampaignCallPending()) { this.campaignNetworkMonitorHandle = this.platform.monitorNetwork((state) => { if (state === NETWORK_CONNECTED) { SwrveLogger.info("NETWORK RECONNECTED - RETRY CAMPAIGNS AND RESOURCES"); this.platform.stopMonitoringNetwork(this.campaignNetworkMonitorHandle!); this.updateCampaignsAndResources(); } }); } StorageManager.saveData(SwrveConstants.CAMPAIGN_CALL_PENDING, this.profileManager.currentUser.userId); if (forceUpdate) { const userId = this.profileManager.currentUser.userId; this.realTimeUserPropertiesManager.loadStoredUserProperties(userId); this.campaignManager.loadStoredCampaigns(userId); this.resourceManager.getResources(userId).then(resources => { if (this.onResourcesLoadedCallback != null) { this.onResourcesLoadedCallback(resources || []); } }); this.autoShowMessages(); } }); } public saveToStorage(): void { this.evtManager.saveEventsToStorage(this.profileManager.currentUser.userId); } public identify(externalUserId: string | null, onIdentifySuccess: OnIdentifySuccessCallback, onIdentifyError: OnIdentifyErrorCallback): void { if (this.config.managedMode) { SwrveLogger.error("SwrveSDK: identify() cannot be called when MANAGED mode is enabled. Use start() instead."); return; } this.sendQueuedEvents(); this.pauseSDK = true; const previousSwrveId: string = this.profileManager.currentUser.userId; if (externalUserId === "" || externalUserId == null) { this.pauseSDK = false; SwrveLogger.error("Swrve identify: External user id cannot be nil or empty"); if (onIdentifyError !== undefined) { onIdentifyError("External user id cannot be nil or empty"); } return; } const cachedSwrveUserId = this.profileManager.getSwrveIdByThirdPartyId(externalUserId); if (cachedSwrveUserId) { this.pauseSDK = false; SwrveLogger.info("Identity API call skipped, user loaded from cache: ", this.profileManager.currentUser.userId); if (cachedSwrveUserId !== this.profileManager.currentUser.userId) { this.verifyAndSwitchUser(cachedSwrveUserId); } if (onIdentifySuccess) { onIdentifySuccess("Identity API call skipped, user loaded from cache", this.profileManager.currentUser.userId); } } else { if (ProfileManager.isUserIdVerified(this.profileManager.currentUser.userId) || (!this.profileManager.currentUser.isAnonymous)) { this.createAnonymousUser(); } const swrveId = this.profileManager.currentUser.userId; this.makeIdentityCall(externalUserId, previousSwrveId, swrveId, onIdentifySuccess, onIdentifyError); } } //******************************************** CALLBACKS *********************************************************/ public queueMessageImpressionEvent( messageId: number, payload?: IDictionary<string | number>, ): void { const nextSeqNum = this.profileManager.getNextSequenceNumber(); if (!payload) { payload = { embedded: "false" }; } payload['platform'] = this.platform.os; payload['deviceType'] = SWRVE_PAYLOAD_DEVICE_TYPE_TV; const evt = this.eventFactory.getImpressionEvent( messageId, nextSeqNum, payload, ); this.queueEvent(evt); if (this.profileManager.isQAUser()) { this.queueEvent(this.eventFactory.getWrappedNamedEvent(evt)); } } public onResourcesLoaded(callback: OnResourcesLoadedCallback): void { if (typeof callback !== "function") { SwrveLogger.error(SwrveConstants.INVALID_FUNCTION.replace("$", "onResourcesLoaded")); return; } this.onResourcesLoadedCallback = callback; } public onCampaignLoaded(callback: OnCampaignLoadedCallback): void { if (typeof callback !== "function") { SwrveLogger.error(SwrveConstants.INVALID_FUNCTION.replace("$", "onCampaignLoaded")); return; } this.onCampaignLoadedCallback = callback; } public onMessage(callback: OnMessageListener): void { if (typeof callback !== "function") { SwrveLogger.error(SwrveConstants.INVALID_FUNCTION.replace("$", "onMessage")); return; } this.campaignManager.onMessage(callback); } public onIAMDismissed(callback: OnIAMDismissed): void { if (typeof callback !== "function") { SwrveLogger.error(SwrveConstants.INVALID_FUNCTION.replace("$", "onIAMDismissed")); return; } this.onIAMDismissedCallback = callback; } public onCustomButtonClicked(callback: OnCustomButtonClicked): void { if (typeof callback !== "function") { SwrveLogger.error(SwrveConstants.INVALID_FUNCTION.replace("$", "onCustomButtonClicked")); return; } this.onCustomButtonClickedCallback = callback; } public onIAMShown(callback: OnIAMShown): void { if (typeof callback !== "function") { SwrveLogger.error(SwrveConstants.INVALID_FUNCTION.replace("$", "onIAMShown")); return; } this.onIAMShownCallback = callback; } //************************************* RESOURCES *****************************************************************/ public getResources(callback: GetResourcesCallback): void { if (typeof callback !== "function") { SwrveLogger.error(SwrveConstants.INVALID_FUNCTION.replace("$", "getResources")); return; } this.resourceManager.getResources(this.profileManager.currentUser.userId) .then(resources => { if (resources) { SwrveLogger.info("RESOURCES READILY AVAILABLE"); if (callback) { callback(resources); } } else { SwrveLogger.info("NO RESOURCES AVAILABLE"); if (callback) { callback([]); } } }); } public getResourceManager(): ResourceManager { return this.resourceManager.getResourceManager(); } public getUserResourcesDiff(callback: GetUserResourcesDiffCallback): void { if (typeof callback !== "function") { SwrveLogger.error(SwrveConstants.INVALID_FUNCTION.replace("$", "getResources")); return; } const resourcesDiffKey = "resourcesDiff" + this.profileManager.currentUser.userId; this.restClient.getUserResourcesDiff() .then(response => { return StorageManager.saveDataWithMD5Hash(resourcesDiffKey, JSON.stringify(response.json)) .then(() => response.json); }) .catch((error: any) => { SwrveLogger.warn("getUserResourcesDiff failed", error); return StorageManager.getDataWithMD5Hash(resourcesDiffKey) .then(data => data ? JSON.parse(data) : []); }) .then(json => { if (callback) { const diff = this.transformResourcesDiff(json); callback(diff[0], diff[1], json); } }); } //*****************************************************************************************************************/ public showMessageCenterCampaign( campaign: ISwrveCampaign, personalizationProperties?: IDictionary<string>, ): boolean { const properties = this.retrievePersonalizationProperties( {}, personalizationProperties, ); return this.campaignManager.showCampaign(campaign, properties, (msg) => { this.queueMessageImpressionEvent(msg.id); if (this.onIAMShownCallback) { this.onIAMShownCallback(); } }); } public markMessageCenterCampaignAsSeen(campaign: ISwrveCampaign): void { this.campaignManager.markCampaignAsSeen(campaign); } public removeMessageCenterCampaign(campaign: ISwrveCampaign): void { this.campaignManager.removeMessageCenterCampaign(campaign); } public getCampaignState(campaignId: string): ICampaignState { return this.campaignManager.getCampaignState(campaignId); } public start(userId?: string): void { if (this.config.managedMode) { if (userId) { if (this.profileManager.currentUser.userId === userId) { if (this.pauseSDK) { this.pauseSDK = false; this.initSDK(); } else { SwrveLogger.info("SwrveSDK: Already running on userID: " + userId); } } else { this.stopAndSwitchUser(userId); } } else if (this.pauseSDK) { this.pauseSDK = false; this.initSDK(); } else { SwrveLogger.info("SwrveSDK: Already running on userID: " + this.profileManager.currentUser.userId); } } else { SwrveLogger.error("SwrveSDK: start() can only be called when managedMode in SwrveConfig is enabled"); } } public stopAndSwitchUser(userId: string): void { if (this.config.managedMode) { this.stop(); this.switchUser(userId); } else { SwrveLogger.error("SwrveSDK: switchUser() can only be called when managedMode in SwrveConfig is enabled"); } } public isSDKStarted(): boolean { return !this.pauseSDK; } public showCampaign( campaign: ISwrveCampaign, personalizationProperties?: IDictionary<string>, ): boolean { const properties = this.retrievePersonalizationProperties( {}, personalizationProperties, ); return this.campaignManager.showCampaign(campaign, properties, (msg) => { this.queueMessageImpressionEvent(msg.id); if (this.onIAMShownCallback) { this.onIAMShownCallback(); } }); } public shutdown(): void { this._shutdown = true; this.evtManager.saveEventsToStorage(this.profileManager.currentUser.userId); clearTimeout(this.eventLoopTimer); const qa = this.profileManager.QAUser; if (qa && qa.reset_device_state === true) { SwrveLogger.info("SwrveSDK: Clearing campaign state for QA user: " + this.profileManager.currentUser.userId); this.campaignManager.resetCampaignState(); StorageManager.clearData(CAMPAIGN_STATE + this.profileManager.currentUser.userId); } } public handleSendingQueue(): void { this.sendQueuedEvents(); } public isIdentifyCallPending(): boolean { return Boolean(StorageManager.getData(SwrveConstants.IDENTIFY_CALL_PENDING)); } public isCampaignCallPending(): boolean { return Boolean(StorageManager.getData(SwrveConstants.CAMPAIGN_CALL_PENDING)); } public getRealTimeUserProperties(): IDictionary<string> { return this.realTimeUserPropertiesManager.UserProperties; } public retrievePersonalizationProperties( eventPayload?: IDictionary<string>, properties?: IDictionary<string>, ): IDictionary<string> { const processedRealTimeUserProperties: IDictionary<string> = RealTimeUserPropertiesManager.processForPersonalization( this.getRealTimeUserProperties(), ); let resultProperties = {}; if ( (!properties || Object.keys(properties).length === 0) && this.config.personalizationProvider ) { const providerResult = this.config.personalizationProvider( eventPayload || {}, ); resultProperties = combineDictionaries( processedRealTimeUserProperties, providerResult, ); } else if (properties) { resultProperties = combineDictionaries( processedRealTimeUserProperties, properties, ); } else { resultProperties = processedRealTimeUserProperties; } return resultProperties; } public isMessageShowing(): boolean { return this.campaignManager?.isMessageShowing() ?? false; } private checkTriggers(triggerName: string, payload: object): void { const qa = this.profileManager.isQAUser(); const personalization = this.retrievePersonalizationProperties( payload as IDictionary<string>, ); const { globalStatus, campaignStatus, campaigns } = this.campaignManager.checkTriggers( triggerName, payload, (msg) => { this.queueMessageImpressionEvent(msg.id); if (this.onIAMShownCallback) { this.onIAMShownCallback(); } }, qa, personalization, ); if (qa && globalStatus.status !== SwrveConstants.CAMPAIGN_MATCH) { SwrveLogger.debug(globalStatus.message); const event = this.eventFactory.getCampaignTriggeredEvent(triggerName, payload, globalStatus.message, "false"); const nextQASeqNum = this.profileManager.getNextSequenceNumber(); const wrappedEvent = this.eventFactory.getWrappedCampaignTriggeredEvent(nextQASeqNum, event); this.queueEvent(wrappedEvent); } if (qa && campaignStatus) { SwrveLogger.debug(campaignStatus.message); const displayed = campaignStatus.status === SwrveConstants.CAMPAIGN_MATCH ? "true" : "false"; const event = this.eventFactory.getCampaignTriggeredEvent(triggerName, payload, campaignStatus.message, displayed, campaigns); const nextQASeqNum = this.profileManager.getNextSequenceNumber(); const wrappedEvent = this.eventFactory.getWrappedCampaignTriggeredEvent(nextQASeqNum, event); this.queueEvent(wrappedEvent); } } private makeIdentityCall(thirdPartyLoginId: string, previousSwrveId: string, swrveId: string, onIdentifySuccess: OnIdentifySuccessCallback, onIdentifyError: OnIdentifyErrorCallback): void { this.identifiedOnAnotherDevice = false; // reset the flag this.restClient.identify(thirdPartyLoginId, swrveId) .then((response) => { this.profileManager.cacheThirdPartyId(thirdPartyLoginId, response.swrve_id); if (response.status === SwrveConstants.NEW_EXTERNAL_ID || response.status === SwrveConstants.EXISTING_EXTERNAL_ID_MATCHES_SWRVE_ID) { this.pauseSDK = false; if (previousSwrveId !== response.swrve_id) { this.verifyAndSwitchUser(response.swrve_id); } } else if (response.status === SwrveConstants.EXISTING_EXTERNAL_ID) { this.identifiedOnAnotherDevice = true; this.verifyAndSwitchUser(response.swrve_id); } else { this.pauseSDK = false; } if (this.isIdentifyCallPending()) { this.cleanUpIdentifyCallPending(); } ProfileManager.setUserIdAsVerified(this.profileManager.currentUser.userId); if (onIdentifySuccess) { onIdentifySuccess(response.status, response.swrve_id); } }) .catch(error => { SwrveLogger.info("Identify error" + error); this.handleIdentifyOffline(thirdPartyLoginId, previousSwrveId, swrveId, onIdentifySuccess, onIdentifyError); this.pauseSDK = false; if (onIdentifyError) { onIdentifyError(error); } }); } private handleIdentifyOffline(thirdPartyLoginId: string, previousSwrveId: string, swrveId: string, onIdentifySuccess: OnIdentifySuccessCallback, onIdentifyError: OnIdentifyErrorCallback): void { const existingThirdPartyLoginId = StorageManager.getData(SwrveConstants.IDENTIFY_CALL_PENDING_EXTERNAL_ID); if (existingThirdPartyLoginId && existingThirdPartyLoginId !== thirdPartyLoginId) { this.evtManager.clearQueueAndStorage(this.profileManager.currentUser.userId); } else { this.evtManager.saveEventsToStorage(this.profileManager.currentUser.userId); } if (!this.isIdentifyCallPending()) { this.identifyNetworkMonitorHandle = this.platform.monitorNetwork((state: number) => { if (state === NETWORK_CONNECTED) { SwrveLogger.info("connected"); this.makeIdentityCall(StorageManager.getData(SwrveConstants.IDENTIFY_CALL_PENDING_EXTERNAL_ID)!, previousSwrveId, swrveId, onIdentifySuccess, onIdentifyError); } }); } StorageManager.saveData(SwrveConstants.IDENTIFY_CALL_PENDING, swrveId); StorageManager.saveData(SwrveConstants.IDENTIFY_CALL_PENDING_EXTERNAL_ID, thirdPartyLoginId); } private cleanUpCampaignCallPending(): void { if (this.campaignNetworkMonitorHandle !== undefined) { this.platform.stopMonitoringNetwork(this.campaignNetworkMonitorHandle); delete this.campaignNetworkMonitorHandle; } StorageManager.clearData(SwrveConstants.CAMPAIGN_CALL_PENDING); } private cleanUpIdentifyCallPending(): void { if (this.identifyNetworkMonitorHandle !== undefined) { this.platform.stopMonitoringNetwork(this.identifyNetworkMonitorHandle); delete this.identifyNetworkMonitorHandle; } const anonId = StorageManager.getData(SwrveConstants.IDENTIFY_CALL_PENDING); if (anonId) { this.sendQueuedEvents(anonId); } StorageManager.clearData(SwrveConstants.IDENTIFY_CALL_PENDING); StorageManager.clearData(SwrveConstants.IDENTIFY_CALL_PENDING_EXTERNAL_ID); } private createAnonymousUser(): void { this.profileManager.setCurrentUserAsNewAnonymousUser(); this.campaignManager.resetCampaignState(); } private verifyAndSwitchUser(newUserId: string): void { ProfileManager.setUserIdAsVerified(newUserId); this.switchUser(newUserId); } private switchUser(newUserId: string): void { this.profileManager.setCurrentUser(newUserId); this.realTimeUserPropertiesManager.loadStoredUserProperties(newUserId); this.campaignManager.loadStoredCampaigns(newUserId); this.startNewSession(); } private startNewSession(): void { SwrveLogger.info("Start new session"); this.pauseSDK = false; this.autoShowEnabled = true; //reset this as it may have timed out this.initSDK(); //send all init events this.queueDeviceProperties(); //send device props as at constuction time we wait for PAL to send this but PAL is ready in this case if (this.eventLoopTimer === 0) { this.updateTimer(this.flushFrequency); } } private handlePageViewed(messageId: number, pageId: number, pageName: string): void { const sentPageViewEvents = this.campaignManager.getSentPageViewEvents(); if (sentPageViewEvents.indexOf(pageId) > -1) { return; } const seqnum = this.profileManager.getNextSequenceNumber(); const id = messageId.toString(); const contextId = pageId.toString(); const payload: IDictionary<string> = {}; if (pageName && pageName.length > 0) { payload['pageName'] = pageName; } payload['platform'] = this.platform.os; payload['deviceType'] = SWRVE_PAYLOAD_DEVICE_TYPE_TV; const event: any = this.eventFactory.getPageViewEvent(seqnum, id, contextId, payload); this.queueEvent(event); sentPageViewEvents.push(pageId); if (this.profileManager.isQAUser()) { const qaEvent: any = this.eventFactory.getWrappedGenericCampaignEvent(event); this.queueEvent(qaEvent); this.sendQueuedEvents(); } } private handleButtonClicked(button: ISwrveButton, parentCampaign: ISwrveCampaign, pageId: string, pageName: string): void { const type = String(button.type.value); const action = String(button.action.value); const campaignId = parentCampaign.id; const messageId = this.campaignManager.getCampaignVariantID(parentCampaign); const buttonName = button.name; const buttonId = button.button_id || 0; switch (type) { case SwrveConstants.DISMISS: this.dismissButtonClicked(messageId, pageId, pageName, buttonName, buttonId); break; case SwrveConstants.CUSTOM: this.customButtonClicked(campaignId, messageId, pageId, pageName, buttonName, buttonId, action); break; case SwrveConstants.PAGE_LINK: this.pageLinkButtonClicked(messageId, pageId, pageName, buttonName, buttonId, action); break; case SwrveConstants.COPY_TO_CLIPBOARD: this.copyToClipboard(action); break; } this.queueButtonEvents(button); this.queueButtonUserUpdates(button); } private dismissButtonClicked(messageId: number, pageId: string, pageName: string, buttonName: string, buttonId: number): void { const seqnum = this.profileManager.getNextSequenceNumber(); const id = messageId.toString(); const contextId = pageId.toString(); const payload: IDictionary<string> = {}; if (pageName && pageName.length > 0) { payload['pageName'] = pageName; } if (buttonName && buttonName.length > 0) { payload['buttonName'] = buttonName; } if (buttonId > 0) { payload['buttonId'] = buttonId.toString(); } payload['platform'] = this.platform.os; payload['deviceType'] = SWRVE_PAYLOAD_DEVICE_TYPE_TV; const event: any = this.eventFactory.getDismissEvent(seqnum, id, contextId, payload); this.queueEvent(event); if (this.profileManager.isQAUser()) { const qaEvent: any = this.eventFactory.getWrappedGenericCampaignEvent(event); this.queueEvent(qaEvent); this.sendQueuedEvents(); } if (this.onIAMDismissedCallback) { this.onIAMDismissedCallback(); } } private customButtonClicked(campaignId: number, messageId: number, pageId: string, pageName: string, buttonName: string, buttonId: number, action: string): void { const seqnum = this.profileManager.getNextSequenceNumber(); const event: any = this.eventFactory.getButtonClickEvent(seqnum, messageId, buttonName, "false", this.platform.os, buttonId, pageId, pageName); this.queueEvent(event); if (this.profileManager.isQAUser()) { const qaSeqnum = this.profileManager.getNextSequenceNumber(); // const action = action || "No action"; // TODO: check if this is needed const qaEvent: any = this.eventFactory.getQAButtonClickEvent(campaignId, messageId, buttonName, "deeplink", action, qaSeqnum); this.queueEvent(qaEvent); this.sendQueuedEvents(); } if (this.onCustomButtonClickedCallback) { this.onCustomButtonClickedCallback(action); } else if (action.match(/^https?:\/\//)) { this.platform.openLink(action); } } private async copyToClipboard(text: string): Promise<void> { if (navigator.clipboard) { try { await navigator.clipboard.writeText(text); } catch (err) { SwrveLogger.error("Clipboard API error:", err); } } } private pageLinkButtonClicked(messageId: number, pageId: string, pageName: string, buttonName: string, buttonId: number, pageToId: string): void { const sentNavigationEvents = this.campaignManager.getSentNavigationEvents(); if (sentNavigationEvents.indexOf(buttonId) > -1) { return; } const seqnum = this.profileManager.getNextSequenceNumber(); const id = messageId.toString(); const contextId = pageId.toString(); const payload: IDictionary<string> = {}; if (pageName && pageName.length > 0) { payload['pageName'] = pageName; } if (buttonName && buttonName.length > 0) { payload['buttonName'] = buttonName; } if (buttonId > 0) { payload['buttonId'] = buttonId.toString(); } payload['platform'] = this.platform.os; payload['deviceType'] = SWRVE_PAYLOAD_DEVICE_TYPE_TV; payload['to'] = pageToId; const event: any = this.eventFactory.getNavigationEvent(seqnum, id, contextId, payload); this.queueEvent(event); sentNavigationEvents.push(buttonId); if (this.profileManager.isQAUser()) { const qaEvent: any = this.eventFactory.getWrappedGenericCampaignEvent(event); this.queueEvent(qaEvent); this.sendQueuedEvents(); } } private queueButtonEvents(button: ISwrveButton): void { if (button.events) { button.events.forEach(event => { let personalizedPayload: IDictionary<string | number> = {}; if (event.payload) { const personalizationProperties = this.retrievePersonalizationProperties(); event.payload.forEach(item => { let personalizedValue: string | number | null = item.value; if (typeof item.value === 'string') { personalizedValue = this.getPersonalizedText(item.value, personalizationProperties); } if (personalizedValue != null) { personalizedPayload = { ...personalizedPayload, [item.key]: personalizedValue, }; } }); } const sendEvent = this.eventFactory.getNamedEvent(event.name, personalizedPayload, this.profileManager.getNextSequenceNumber(), DateHelper.nowInUtcTime(), ); this.queueEvent(sendEvent); if (this.profileManager.isQAUser()) { this.queueEvent( this.eventFactory.getWrappedNamedEvent(sendEvent)); this.sendQueuedEvents(); } }); } } private queueButtonUserUpdates(button: ISwrveButton): void { if (button.user_updates) { const personalizationProperties = this.retrievePersonalizationProperties(); let personalizedAttributes: IDictionary<string | number | boolean > = {}; button.user_updates.forEach(user_update => { let personalizedValue: string | number | boolean | null = user_update.value; if (typeof user_update.value === 'string') { personalizedValue = this.getPersonalizedText(user_update.value, personalizationProperties); } if (personalizedValue != null) { personalizedAttributes = { ...personalizedAttributes, [user_update.key]: personalizedValue, }; } }); const userUpdateEvent = this.eventFactory.getUserUpdate(personalizedAttributes, this.profileManager.getNextSequenceNumber(), DateHelper.nowInUtcTime(), ); this.queueEvent(userUpdateEvent); if (this.profileManager.isQAUser()) { this.queueEvent(this.eventFactory.getWrappedUserUpdate(userUpdateEvent)); this.sendQueuedEvents(); } } } private autoShowMessages(): void { SwrveLogger.debug("AUTO SHOW MESSAGES " + this.autoShowEnabled); if (!this.autoShowEnabled) { return; } this.checkTriggers(SwrveConstants.SWRVE_AUTOSHOW_AT_SESSION_START_TRIGGER, {}); this.autoShowEnabled = false; } private queueStartSessionEvent(): void { const event = this.eventFactory.getStartSessionEvent(this.profileManager.getNextSequenceNumber(), DateHelper.nowInUtcTime()); this.queueEvent(event); if (this.profileManager.isQAUser()) { this.queueEvent(this.eventFactory.getWrappedSessionStart(event)); } } private handleCampaignResponse(response: IRestResponse<ISwrveCampaignResourceResponse>): void { if (Object.keys(response.json).length !== 0) { this.handleRealTimeUserProperties(response.json); const personalizationProperties = this.retrievePersonalizationProperties(); //this will also download assets, pass in up todate personalizationProperties this.campaignManager.storeCampaigns(response.json, personalizationProperties, (error?: Error) => { SwrveLogger.debug("ON ASSETS LOADED"); this.refreshAssets = false; this.autoShowMessages(); if (this.onCampaignLoadedCallback) { this.onCampaignLoadedCallback(error); } }); this.handleQAUser(response.json); this.handleFlushRefresh(response.json); if (this.profileManager.isQAUser()) { this.sendCampaignsDownloadedEvent(); } } else { //if etag takes affect and no campaigns to update //then we still want to refresh assets just once on the first launch if (this.refreshAssets) { SwrveLogger.debug("Refresh Assets"); this.refreshAssets = false; const personalizationProperties = this.retrievePersonalizationProperties(); this.campaignManager.refreshAssets(personalizationProperties, (error?: Error) => { SwrveLogger.debug("ON ASSETS LOADED"); //campaigns wil have already been loaded from storage on init if (this.onCampaignLoadedCallback) { this.onCampaignLoadedCallback(error); } }); } } this.handleResources(response.json); if (response.etag != null) { this.profileManager.storeEtagHeader(response.etag); } } private handleRealTimeUserProperties( response: ISwrveCampaignResourceResponse, ): void { this.realTimeUserPropertiesManager.storeUserProperties(response); } private sendCampaignsDownloadedEvent(): void { const ids = this.campaignManager.getCampaignIDs(); if (ids.length > 0) { const nextSeqNum = this.profileManager.getNextSequenceNumber(); this.queueEvent(this.eventFactory.getCampaignsDownloadedEvent(nextSeqNum, ids)); } } private initSDK(): void { SwrveLogger.info("Initialising Swrve SDK"); this.autoShowEnabled = false; const isValid = this.profileManager.hasSessionRestored(); if (!isValid) { SwrveLogger.debug("Setting autoShowBack to true"); this.queueStartSessionEvent(); this.checkFirstUserInitiated(); this.autoShowEnabled = true; } this.disableAutoShowAfterDelay(); this.sendQueuedEvents(this.profileManager.currentUser.userId, true); SwrveLogger.info("Swrve Config: ", this.config); } private disableAutoShowAfterDelay(): void { setTimeout(() => { SwrveLogger.debug("AUTO SHOW TIMED OUT " + this.config.autoShowMessagesMaxDelay); this.autoShowEnabled = false; }, this.config.autoShowMessagesMaxDelay); } private queueEvent(event: SwrveEvent): void { this.evtManager.queueEvent(event); if (this.evtManager.queueSize > this.evtManager.MAX_QUEUE_SIZE) { this.handleSendingQueue(); } } private handleResources(response: ISwrveCampaignResourceResponse): void { if (response.user_resources) { this.resourceManager.storeResources(response.user_resources, this.profileManager.currentUser.userId); const resources = this.resourceManager.getResourceManager().getResources(); if (this.onResourcesLoadedCallback != null) { this.onResourcesLoadedCallback(resources || []); } } else { this.resourceManager.getResources(this.profileManager.currentUser.userId).then(resources => { if (this.onResourcesLoadedCallback != null) { this.onResourcesLoadedCallback(resources || []); } }); } } private handleUpdate(): void { if (this.evtManager.getQueue().length > 0) { this.handleSendingQueue(); } } private handleQAUser(response: ISwrveCampaignResourceResponse): void { if (response.qa) { this.p