UNPKG

@causalfoundry/js-sdk

Version:

Causal Foundry WEB SDK (JS/TS)

359 lines (338 loc) 9.21 kB
import { AppProperties, CoreCatalogType, IdentifyProperties, MediaCatalog, MediaProperties, MediaType, ModuleSelectionProperties, NavigationTypes, PageProperties, RateProperties, SearchProperties, SiteCatalog, TrackProperties, UserCatalog, } from "./typings"; import { injectEvent } from "../../core/injector"; import CfExceptionHandler from "../../core/CfExceptionHandler"; import { NudgeResponseProperties } from "../../core/repositories/nudges/typings"; import { toSnakeCase } from "../../core/extFunctions"; import { GlobalSDKEventTypes } from "../../core/typings"; import { validateAndSaveMediaCatalog, validateAndSaveSiteCatalog, validateAndSaveUserCatalog, } from "./CoreCatalogValidator"; import { logSearchEvent } from "./legacyLoggerFunctions"; /** * @internal * * @param userId * @param properties * @param sendNow */ const logIdentifyEvent = ( properties: IdentifyProperties, sendNow = false, eventTime?: string ) => { injectEvent( NavigationTypes.Identify, properties.action, properties, sendNow, "", eventTime ); }; /** * @internal * * @param name * @param property * @param ctx * @param sendNow */ const logIngestEvent = ( name: NavigationTypes | string, ctx?: | AppProperties | PageProperties | MediaProperties | SearchProperties | RateProperties | ModuleSelectionProperties | TrackProperties | Record<string, string | number | boolean>, sendNow = false, eventTime?: string ) => { let checkProps = true; let property: string | undefined; switch (name) { case NavigationTypes.App: checkProps = validateAppEvent(ctx as AppProperties); if (checkProps) { property = (ctx as AppProperties).action; } break; case NavigationTypes.Page: checkProps = validatePageEvent(ctx as PageProperties); if (checkProps) { property = (ctx as PageProperties).title; } break; case NavigationTypes.Media: checkProps = validateMediaEvent(ctx as MediaProperties); if (checkProps) { property = (ctx as MediaProperties).type; } break; case NavigationTypes.Search: { const searchContext = ctx as SearchProperties; checkProps = validateSearchEvent(searchContext); if (checkProps) { property = searchContext.query; if (Array.isArray(searchContext.results_list)) { (ctx as SearchProperties).results_list = [ ...new Set(searchContext.results_list), ]; } else { (ctx as SearchProperties).results_list = []; } } break; } case NavigationTypes.Rate: checkProps = validateRateEvent(ctx as RateProperties); if (checkProps) { property = (ctx as RateProperties).type; } break; case NavigationTypes.Track: checkProps = validateTrackEvent(ctx as TrackProperties); if (checkProps) { name = toSnakeCase((ctx as TrackProperties).name); property = (ctx as TrackProperties).property; ctx = (ctx as TrackProperties).meta; } break; case NavigationTypes.ModuleSelection: checkProps = validateModuleSelectionEvent( ctx as ModuleSelectionProperties ); if (checkProps) { property = (ctx as ModuleSelectionProperties).type; } break; } if (!checkProps) { return; // Exit if the properties validation failed } if ( name === NavigationTypes.Media && (ctx as MediaProperties).type === MediaType.Image ) { (ctx as MediaProperties).seek_time = 0; } injectEvent(name, property, ctx, sendNow, "", eventTime); }; const logCatalogEvent = ( subjectId: string, coreCatalogType: CoreCatalogType, catalogProps: UserCatalog | MediaCatalog | SiteCatalog ) => { switch (coreCatalogType) { case CoreCatalogType.User: validateAndSaveUserCatalog(subjectId, catalogProps as UserCatalog); break; case CoreCatalogType.Site: validateAndSaveSiteCatalog(subjectId, catalogProps as SiteCatalog); break; case CoreCatalogType.Media: validateAndSaveMediaCatalog(subjectId, catalogProps as MediaCatalog); break; case CoreCatalogType.Other: break; } }; const validateModuleSelectionEvent = ( properties: ModuleSelectionProperties ): boolean => { if (!properties.type) { new CfExceptionHandler().throwAndLogError( NavigationTypes.ModuleSelection, "module type is required" ); return false; } return true; }; /** * Whenever the user interacts with the App, log that action with this method */ const validateAppEvent = (properties: AppProperties): boolean => { if (properties.action == undefined) { new CfExceptionHandler().throwAndLogError( NavigationTypes.App, "app action is required" ); return false; } return true; }; /** * SDK tracks automatically page changes by inspecting the URL. However, * depending on the app implementation, it may happen that the URL does not * change when a new view is presented to the user. For these cases, use this method */ const validatePageEvent = (properties: PageProperties): boolean => { return properties.duration !== undefined && properties.duration >= 0; }; /** * Whenever the user interacts with a media element (Video, Audio, Image), log that * action with this method */ const validateMediaEvent = (properties: MediaProperties): boolean => { properties.seek_time = properties.type === MediaType.Image ? 0 : properties.seek_time; if (properties.id == "") { new CfExceptionHandler().throwAndLogError( NavigationTypes.Media, "empty media id" ); return false; } else if (properties.type == undefined) { new CfExceptionHandler().throwAndLogError( NavigationTypes.Media, "media type is required" ); return false; } else if (properties.action == undefined) { new CfExceptionHandler().throwAndLogError( NavigationTypes.Media, "media action is required" ); return false; } else if (properties.seek_time && properties.seek_time < 0) { new CfExceptionHandler().throwAndLogError( NavigationTypes.Media, "negative media seek time" ); return false; } return true; }; /** * This function logs into the platform a new search performed by the user, * including both, the parameters and the resulting ids */ const validateSearchEvent = (properties: SearchProperties): boolean => { if (properties.page < 0) { new CfExceptionHandler().throwAndLogError( NavigationTypes.Search, "negative search page" ); return false; } else if (properties.results_list.length !== 0) { properties.results_list.every((item) => { if (item == "") { new CfExceptionHandler().throwAndLogError( NavigationTypes.Search, "empty search result item id" ); return false; } }); } return true; }; /** * This function logs rate value provided by the user */ const validateRateEvent = (properties: RateProperties): boolean => { const RATE_MIN_VALUE = 0; const RATE_MAX_VALUE = 5; if (properties.rate_value < 0) { new CfExceptionHandler().throwAndLogError( NavigationTypes.Rate, "negative rate value" ); return false; } else if ( properties.rate_value < RATE_MIN_VALUE || properties.rate_value > RATE_MAX_VALUE ) { new CfExceptionHandler().throwAndLogError( NavigationTypes.Rate, "invalid rate range 0-5" ); return false; } else if (properties.type == undefined) { new CfExceptionHandler().throwAndLogError( NavigationTypes.Rate, "rate type is required" ); return false; } else if ( properties.subject_id == undefined || properties.subject_id == "" ) { new CfExceptionHandler().throwAndLogError( NavigationTypes.Rate, "rate subject id is required" ); return false; } return true; }; const validateTrackEvent = (properties: TrackProperties): boolean => { const snakeCaseName = toSnakeCase(properties.name); // 1. Empty check if (!properties.name || properties.name.trim() === "") { new CfExceptionHandler().throwAndLogError( NavigationTypes.Track, "name for track event is required" ); return false; } // 2. Collision check with GlobalSDKEventTypes const reservedNames = new Set<string>(Object.values(GlobalSDKEventTypes)); if (reservedNames.has(snakeCaseName)) { new CfExceptionHandler().throwAndLogError( NavigationTypes.Track, `The event name "${properties.name}" is reserved and cannot be used.` ); return false; } return true; }; /** * @internal * * @param properties * @param sendNow */ const logPushNotificationEvent = ( properties: NudgeResponseProperties, sendNow = true, eventTime?: string ): void => { injectEvent( NavigationTypes.ActionResponse, properties.response, properties, sendNow, "", eventTime ); }; export default { logIdentifyEvent, logIngestEvent, logCatalogEvent, logPushNotificationEvent, // Legacy functions logSearchEvent, };