From 0c9c9cd1e0e227c0f10c89ca8c691a20c27c085a Mon Sep 17 00:00:00 2001 From: Maxim Schuwalow Date: Fri, 15 Sep 2023 16:29:22 +0200 Subject: [PATCH] wip --- src/cache.ts | 26 +++--- src/enrichers/cache.ts | 29 +++++++ src/enrichers/call-handler.ts | 11 +++ src/{manager => enrichers}/decisions.ts | 14 ++-- src/enrichers/domain.ts | 9 ++ src/enrichers/error-pixel.ts | 10 +++ src/enrichers/identifiers-nohash.ts | 22 ++--- src/enrichers/identifiers.ts | 33 ++++---- .../live-connect-id.ts} | 20 ++--- src/enrichers/page.ts | 23 +++-- src/enrichers/people-verified-id.ts | 19 +++++ src/enrichers/people-verified.ts | 12 --- src/enrichers/privacy-config.ts | 16 ++-- src/enrichers/storage-handler.ts | 20 +++++ src/enrichers/storage-strategy.ts | 10 +++ src/events/error-pixel.ts | 4 +- src/minimal-live-connect.ts | 46 ++++++---- src/pixel/fiddler.ts | 14 +++- src/pixel/sender.ts | 2 +- src/pixel/state.ts | 10 --- src/standard-live-connect.ts | 83 +++++++++---------- src/types.ts | 9 +- 22 files changed, 274 insertions(+), 168 deletions(-) create mode 100644 src/enrichers/cache.ts create mode 100644 src/enrichers/call-handler.ts rename src/{manager => enrichers}/decisions.ts (77%) create mode 100644 src/enrichers/domain.ts create mode 100644 src/enrichers/error-pixel.ts rename src/{manager/identifiers.ts => enrichers/live-connect-id.ts} (64%) create mode 100644 src/enrichers/people-verified-id.ts delete mode 100644 src/enrichers/people-verified.ts create mode 100644 src/enrichers/storage-handler.ts create mode 100644 src/enrichers/storage-strategy.ts diff --git a/src/cache.ts b/src/cache.ts index a58d8200..eaf245b5 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -19,19 +19,19 @@ export type StorageHandlerBackedCacheOpts = { defaultExpirationHours?: number } -export type MakeCacheOpts = StorageHandlerBackedCacheOpts & { - strategy: StorageStrategy, -} - -export function makeCache(opts: MakeCacheOpts): DurableCache { - if (!strEqualsIgnoreCase(opts.strategy, StorageStrategies.cookie) && strEqualsIgnoreCase(opts.strategy, StorageStrategies.none)) { - return NoOpCache - } else { - // TODO: Remove once we validate config properly - const strategyWithDefault = opts.strategy ?? StorageStrategies.cookie - return new StorageHandlerBackedCache({ ...opts, strategy: strategyWithDefault }) - } -} +// export type MakeCacheOpts = StorageHandlerBackedCacheOpts & { +// strategy: StorageStrategy, +// } + +// export function makeCache(opts: MakeCacheOpts): DurableCache { +// if (!strEqualsIgnoreCase(opts.strategy, StorageStrategies.cookie) && strEqualsIgnoreCase(opts.strategy, StorageStrategies.none)) { +// return NoOpCache +// } else { +// // TODO: Remove once we validate config properly +// const strategyWithDefault = opts.strategy ?? StorageStrategies.cookie +// return new StorageHandlerBackedCache({ ...opts, strategy: strategyWithDefault }) +// } +// } export class StorageHandlerBackedCache implements DurableCache { private handler diff --git a/src/enrichers/cache.ts b/src/enrichers/cache.ts new file mode 100644 index 00000000..a0612046 --- /dev/null +++ b/src/enrichers/cache.ts @@ -0,0 +1,29 @@ +import { strEqualsIgnoreCase } from "live-connect-common"; +import { DurableCache, NoOpCache, StorageHandlerBackedCache } from "../cache"; +import { WrappedStorageHandler } from "../handlers/storage-handler"; +import { StorageStrategies, StorageStrategy } from "../model/storage-strategy"; +import { Enricher } from "../types"; + +type Input = { domain: string, storageHandler: WrappedStorageHandler, storageStrategy: StorageStrategy } +type Output = { cache: DurableCache } + +export const enrichCache: Enricher = state => { + let cache + + if (strEqualsIgnoreCase(state.storageStrategy, StorageStrategies.cookie) && strEqualsIgnoreCase(state.storageStrategy, StorageStrategies.none)) { + cache = NoOpCache + } else if (strEqualsIgnoreCase(state.storageStrategy, StorageStrategies.ls)) { + cache = new StorageHandlerBackedCache({ + strategy: 'ls', + storageHandler: state.storageHandler, + domain: state.domain, + }) + } else { + cache = new StorageHandlerBackedCache({ + strategy: 'cookie', + storageHandler: state.storageHandler, + domain: state.domain, + }) + } + return { ...state, cache } +} diff --git a/src/enrichers/call-handler.ts b/src/enrichers/call-handler.ts new file mode 100644 index 00000000..14ccf0a5 --- /dev/null +++ b/src/enrichers/call-handler.ts @@ -0,0 +1,11 @@ +import { CallHandler, EventBus } from "live-connect-common" +import { WrappedCallHandler } from "../handlers/call-handler" +import { Enricher } from "../types" + +type Input = { callHandler: CallHandler, eventBus: EventBus } +type Output = { callHandler: WrappedCallHandler } + +export const enrichCallHandler: Enricher = state => { + const callHandler = new WrappedCallHandler(state.callHandler, state.eventBus) + return { ...state, callHandler } +} diff --git a/src/manager/decisions.ts b/src/enrichers/decisions.ts similarity index 77% rename from src/manager/decisions.ts rename to src/enrichers/decisions.ts index a2003353..4a6c3eb2 100644 --- a/src/manager/decisions.ts +++ b/src/enrichers/decisions.ts @@ -1,6 +1,6 @@ import { getQueryParameter, ParsedParam } from '../utils/url' import { trim, isUUID, expiresInDays } from 'live-connect-common' -import { EventBus, State } from '../types' +import { Enricher, EventBus, State } from '../types' import { WrappedStorageHandler } from '../handlers/storage-handler' const DEFAULT_DECISION_ID_COOKIE_EXPIRES = expiresInDays(30) @@ -10,7 +10,11 @@ const DECISION_ID_COOKIE_NAMESPACE = 'lidids.' const _onlyUnique = (value: string, index: number, self: string[]) => self.indexOf(value) === index const _nonEmpty = (value: string) => value && trim(value).length > 0 -export function resolve(state: State, storageHandler: WrappedStorageHandler, eventBus: EventBus): State { +type Input = { storageHandler: WrappedStorageHandler, eventBus: EventBus, pageUrl?: string, domain: string } +type Output = { decisionIds: string[] } + +export const enrichDecisionIds: Enricher = state => { + const { storageHandler, eventBus, pageUrl, domain } = state function _addDecisionId(key: string, cookieDomain?: string) { if (key) { storageHandler.setCookie( @@ -34,7 +38,7 @@ export function resolve(state: State, storageHandler: WrappedStorageHandler, eve const freshDecisions = _orElseEmtpy( 'Error while extracting new decision ids', () => { - const extractedFreshDecisions = ([] as ParsedParam[]).concat((state.pageUrl && getQueryParameter(state.pageUrl, DECISION_ID_QUERY_PARAM_NAME)) || []) + const extractedFreshDecisions = ([] as ParsedParam[]).concat((pageUrl && getQueryParameter(pageUrl, DECISION_ID_QUERY_PARAM_NAME)) || []) return extractedFreshDecisions .map(trim) .filter(_nonEmpty) @@ -56,11 +60,11 @@ export function resolve(state: State, storageHandler: WrappedStorageHandler, eve freshDecisions.forEach(decision => { try { - _addDecisionId(decision, state.domain) + _addDecisionId(decision, domain) } catch (e) { eventBus.emitErrorWithMessage('DecisionsResolve', 'Error while storing new decision id', e) } }) - return { decisionIds: freshDecisions.concat(storedDecisions).filter(_onlyUnique) } + return { ...state, decisionIds: freshDecisions.concat(storedDecisions).filter(_onlyUnique) } } diff --git a/src/enrichers/domain.ts b/src/enrichers/domain.ts new file mode 100644 index 00000000..1fbe3875 --- /dev/null +++ b/src/enrichers/domain.ts @@ -0,0 +1,9 @@ +import { WrappedStorageHandler } from "../handlers/storage-handler" +import { Enricher } from "../types" +import { determineHighestAccessibleDomain } from "../utils/domain" + +type Input = { storageHandler: WrappedStorageHandler } +type Output = { domain: string } + +export const enrichDomain: Enricher = state => + ({ ...state, domain: determineHighestAccessibleDomain(state.storageHandler) }) diff --git a/src/enrichers/error-pixel.ts b/src/enrichers/error-pixel.ts new file mode 100644 index 00000000..b3bb6d41 --- /dev/null +++ b/src/enrichers/error-pixel.ts @@ -0,0 +1,10 @@ +import { EventBus } from "live-connect-common"; +import { Enricher } from "../types"; +import { register } from "../events/error-pixel"; +import { WrappedCallHandler } from "../handlers/call-handler"; + +type Input = { eventBus: EventBus, domain: string, callHandler: WrappedCallHandler } + +export const enrichErrorPixel: Enricher = ({ eventBus, domain, callHandler }) => { + register(eventBus, domain, callHandler) +} diff --git a/src/enrichers/identifiers-nohash.ts b/src/enrichers/identifiers-nohash.ts index 2857fb06..df5b44ff 100644 --- a/src/enrichers/identifiers-nohash.ts +++ b/src/enrichers/identifiers-nohash.ts @@ -1,20 +1,22 @@ import { containsEmailField, isEmail } from '../utils/email' import { safeToString, isArray, trim } from 'live-connect-common' -import { EventBus, RetrievedIdentifier, State } from '../types' +import { Enricher, EventBus, RetrievedIdentifier, State } from '../types' import { WrappedReadOnlyStorageHandler } from '../handlers/storage-handler' -export function enrich(state: State, storageHandler: WrappedReadOnlyStorageHandler, eventBus: EventBus): State { +type Input = {identifiersToResolve: string | string[], storageHandler: WrappedReadOnlyStorageHandler, eventBus: EventBus} +type Output = {retrievedIdentifiers: RetrievedIdentifier[]} + +export const enrichIdentifiers: Enricher = state => { try { - return _parseIdentifiersToResolve(state, storageHandler) + return { ...state, retrievedIdentifiers: resolveIdentifiers(state.identifiersToResolve, state.storageHandler) } } catch (e) { - eventBus.emitError('IdentifiersEnrich', e) - return {} + state.eventBus.emitError('IdentifiersEnrich', e) + return { ...state, retrievedIdentifiers: [] } } } -function _parseIdentifiersToResolve(state: State, storageHandler: WrappedReadOnlyStorageHandler): State { - state.identifiersToResolve = state.identifiersToResolve || [] - const cookieNames = isArray(state.identifiersToResolve) ? state.identifiersToResolve : safeToString(state.identifiersToResolve).split(',') +function resolveIdentifiers(identifiersToResolve: string | string[], storageHandler: WrappedReadOnlyStorageHandler): RetrievedIdentifier[] { + const cookieNames = isArray(identifiersToResolve) ? identifiersToResolve : safeToString(identifiersToResolve).split(',') const identifiers: RetrievedIdentifier[] = [] for (let i = 0; i < cookieNames.length; i++) { const identifierName = trim(cookieNames[i]) @@ -26,7 +28,5 @@ function _parseIdentifiersToResolve(state: State, storageHandler: WrappedReadOnl }) } } - return { - retrievedIdentifiers: identifiers - } + return identifiers } diff --git a/src/enrichers/identifiers.ts b/src/enrichers/identifiers.ts index 5eedb999..637e308c 100644 --- a/src/enrichers/identifiers.ts +++ b/src/enrichers/identifiers.ts @@ -1,26 +1,27 @@ import { replaceEmailsWithHashes } from '../utils/email' import { safeToString, isString, isArray } from 'live-connect-common' -import { EventBus, HashedEmail, State, RetrievedIdentifier } from '../types' +import { EventBus, HashedEmail, State, RetrievedIdentifier, Enricher } from '../types' import { WrappedReadOnlyStorageHandler } from '../handlers/storage-handler' -export function enrich(state: State, storageHandler: WrappedReadOnlyStorageHandler, eventBus: EventBus): State { +type Input = { identifiersToResolve: string | string[], storageHandler: WrappedReadOnlyStorageHandler, eventBus: EventBus } +type Output = { retrievedIdentifiers: RetrievedIdentifier[], hashesFromIdentifiers: HashedEmail[] } + +export const enrichIdentifiers: Enricher = state => { try { - return _getIdentifiers(_parseIdentifiersToResolve(state), storageHandler) + return { ...state, ...getIdentifiers(parseIdentifiersToResolve(state.identifiersToResolve), state.storageHandler) } } catch (e) { - if (eventBus) { - eventBus.emitError('IdentifiersEnricher', e) - } - return {} + state.eventBus.emitError('IdentifiersEnricher', e) + return { ...state, retrievedIdentifiers: [], hashesFromIdentifiers: [] } } } -function _parseIdentifiersToResolve(state: State): string[] { +function parseIdentifiersToResolve(identifiersToResolve: string | string[]): string[] { let cookieNames: string[] = [] - if (state.identifiersToResolve) { - if (isArray(state.identifiersToResolve)) { - cookieNames = state.identifiersToResolve as string[] - } else if (isString(state.identifiersToResolve)) { - cookieNames = (state.identifiersToResolve as string).split(',') + if (identifiersToResolve) { + if (isArray(identifiersToResolve)) { + cookieNames = identifiersToResolve as string[] + } else if (isString(identifiersToResolve)) { + cookieNames = (identifiersToResolve as string).split(',') } } for (let i = 0; i < cookieNames.length; i++) { @@ -29,7 +30,7 @@ function _parseIdentifiersToResolve(state: State): string[] { return cookieNames } -function _getIdentifiers(cookieNames: string[], storageHandler: WrappedReadOnlyStorageHandler): State { +function getIdentifiers(cookieNames: string[], storageHandler: WrappedReadOnlyStorageHandler): Output { const identifiers: RetrievedIdentifier[] = [] let hashes: HashedEmail[] = [] for (let i = 0; i < cookieNames.length; i++) { @@ -46,11 +47,11 @@ function _getIdentifiers(cookieNames: string[], storageHandler: WrappedReadOnlyS } return { retrievedIdentifiers: identifiers, - hashesFromIdentifiers: _deduplicateHashes(hashes) + hashesFromIdentifiers: deduplicateHashes(hashes) } } -function _deduplicateHashes(hashes: HashedEmail[]): HashedEmail[] { +function deduplicateHashes(hashes: HashedEmail[]): HashedEmail[] { const seen = new Set() const result: HashedEmail[] = [] for (let i = 0; i < hashes.length; i++) { diff --git a/src/manager/identifiers.ts b/src/enrichers/live-connect-id.ts similarity index 64% rename from src/manager/identifiers.ts rename to src/enrichers/live-connect-id.ts index bb5d67e8..d90df1f5 100644 --- a/src/manager/identifiers.ts +++ b/src/enrichers/live-connect-id.ts @@ -1,28 +1,27 @@ import { ulid } from '../utils/ulid' -import { loadedDomain } from '../utils/page' import { domainHash } from '../utils/hash' import { expiresInDays } from 'live-connect-common' import { PEOPLE_VERIFIED_LS_ENTRY } from '../utils/consts' -import { EventBus, State } from '../types' +import { Enricher, EventBus } from '../types' import { WrappedStorageHandler } from '../handlers/storage-handler' import { DurableCache } from '../cache' const NEXT_GEN_FP_NAME = '_lc2_fpi' const DEFAULT_EXPIRATION_DAYS = 730 -export function resolve( - state: { expirationDays?: number, domain: string }, - storageHandler: WrappedStorageHandler, - cache: DurableCache, - eventBus: EventBus -): State { - const expiry = state.expirationDays || DEFAULT_EXPIRATION_DAYS +type Input = { expirationDays?: number, domain: string, cache: DurableCache, storageHandler: WrappedStorageHandler } +type Output = { liveConnectId?: string, peopleVerifiedId?: string } + +export const enrichLiveConnectId: Enricher = state => { + const {expirationDays, domain, storageHandler, cache } = state + + const expiry = expirationDays || DEFAULT_EXPIRATION_DAYS const oldValue = cache.get(NEXT_GEN_FP_NAME)?.data if (oldValue) { cache.set(NEXT_GEN_FP_NAME, oldValue, expiresInDays(expiry)) } else { - const newValue = `${domainHash(state.domain)}--${ulid()}` + const newValue = `${domainHash(domain)}--${ulid()}` cache.set(NEXT_GEN_FP_NAME, newValue, expiresInDays(expiry)) } @@ -34,6 +33,7 @@ export function resolve( } return { + ...state, liveConnectId: liveConnectIdentifier, peopleVerifiedId: liveConnectIdentifier } diff --git a/src/enrichers/page.ts b/src/enrichers/page.ts index 012d14e2..aaf6aae7 100644 --- a/src/enrichers/page.ts +++ b/src/enrichers/page.ts @@ -1,18 +1,15 @@ -import { State } from '../types' +import { Enricher } from '../types' import { getPage, getReferrer, getContextElements } from '../utils/page' -let _currentPage: State | null = null +type Input = { privacyMode: boolean, contextSelectors: string, contextElementsLength: number } +type Output = { pageUrl?: string, referrer?: string, contextElements: string } -export function enrich(state: State): State { - if (_currentPage) { - return _currentPage - } else { - const result = { - pageUrl: getPage(), - referrer: getReferrer(), - contextElements: getContextElements(state.privacyMode, state.contextSelectors, state.contextElementsLength) - } - _currentPage = result - return result +export const enrichPage: Enricher = state => { + return { + ...state, + pageUrl: getPage(), + referrer: getReferrer(), + contextElements: getContextElements(state.privacyMode, state.contextSelectors, state.contextElementsLength) } } + diff --git a/src/enrichers/people-verified-id.ts b/src/enrichers/people-verified-id.ts new file mode 100644 index 00000000..556916c6 --- /dev/null +++ b/src/enrichers/people-verified-id.ts @@ -0,0 +1,19 @@ +import { PEOPLE_VERIFIED_LS_ENTRY } from '../utils/consts' +import { Enricher, EventBus } from '../types' +import { WrappedReadOnlyStorageHandler } from '../handlers/storage-handler' + +type Input = { peopleVerifiedId?: string, storageHandler: WrappedReadOnlyStorageHandler, eventBus: EventBus } +type Output = { peopleVerifiedId?: string } + +export const enrichPeopleVerifiedId: Enricher = state => { + try { + return { + ...state, + peopleVerifiedId: state.peopleVerifiedId || state.storageHandler.getDataFromLocalStorage(PEOPLE_VERIFIED_LS_ENTRY) || undefined + } + } catch (e) { + state.eventBus.emitError('PeopleVerifiedEnrich', e) + return state + } +} + diff --git a/src/enrichers/people-verified.ts b/src/enrichers/people-verified.ts deleted file mode 100644 index 6a9f9c23..00000000 --- a/src/enrichers/people-verified.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { PEOPLE_VERIFIED_LS_ENTRY } from '../utils/consts' -import { EventBus, State } from '../types' -import { DurableCache } from '../cache' - -export function enrich(state: State, cache: DurableCache, eventBus: EventBus): State { - try { - return { peopleVerifiedId: state.peopleVerifiedId || cache.get(PEOPLE_VERIFIED_LS_ENTRY)?.data || undefined } - } catch (e) { - eventBus.emitError('PeopleVerifiedEnrich', e) - return {} - } -} diff --git a/src/enrichers/privacy-config.ts b/src/enrichers/privacy-config.ts index 41b62caf..7884881a 100644 --- a/src/enrichers/privacy-config.ts +++ b/src/enrichers/privacy-config.ts @@ -1,11 +1,7 @@ -import { State } from '../types' -import { isNonEmpty } from 'live-connect-common' +import { Enricher } from '../types' -export function enrich(state: State): State { - if (isNonEmpty(state) && isNonEmpty(state.gdprApplies)) { - const privacyMode = !!state.gdprApplies - return { - privacyMode - } - } else return {} -} +type Input = { gdprApplies?: boolean } +type Output = { privacyMode: boolean } + +export const enrichPrivacyMode: Enricher = state => + ({ ...state, privacyMode: !!state.gdprApplies }) diff --git a/src/enrichers/storage-handler.ts b/src/enrichers/storage-handler.ts new file mode 100644 index 00000000..01142bd9 --- /dev/null +++ b/src/enrichers/storage-handler.ts @@ -0,0 +1,20 @@ +import { EventBus, ReadOnlyStorageHandler, StorageHandler } from "live-connect-common"; +import { StorageStrategy } from "../model/storage-strategy"; +import { Enricher } from "../types"; +import { WrappedReadOnlyStorageHandler, WrappedStorageHandler } from "../handlers/storage-handler"; + +type InputReadOnly = { storageStrategy: StorageStrategy, storageHandler: ReadOnlyStorageHandler, eventBus: EventBus } +type OutputReadOnly = { storageHandler: WrappedReadOnlyStorageHandler } + +export const enrichReadOnlyStorageHandler: Enricher = state => { + const storageHandler = WrappedReadOnlyStorageHandler.make(state.storageStrategy, state.storageHandler, state.eventBus) + return { ...state, storageHandler } +} + +type Input = { storageStrategy: StorageStrategy, storageHandler: StorageHandler, eventBus: EventBus } +type Output = { storageHandler: WrappedStorageHandler } + +export const enrichStorageHandler: Enricher = state => { + const storageHandler = WrappedStorageHandler.make(state.storageStrategy, state.storageHandler, state.eventBus) + return { ...state, storageHandler } +} diff --git a/src/enrichers/storage-strategy.ts b/src/enrichers/storage-strategy.ts new file mode 100644 index 00000000..eba6863a --- /dev/null +++ b/src/enrichers/storage-strategy.ts @@ -0,0 +1,10 @@ +import { StorageStrategies, StorageStrategy } from "../model/storage-strategy"; +import { Enricher } from "../types"; + +type Input = { privacyMode: boolean, storageStrategy?: StorageStrategy } +type Output = { storageStrategy: StorageStrategy } + +export const enrichStorageStrategy: Enricher = state => { + const storageStrategy = state.privacyMode ? StorageStrategies.disabled : (state.storageStrategy || StorageStrategies.cookie) + return { ...state, storageStrategy } +} diff --git a/src/events/error-pixel.ts b/src/events/error-pixel.ts index 17c2abaa..67ec486b 100644 --- a/src/events/error-pixel.ts +++ b/src/events/error-pixel.ts @@ -51,12 +51,12 @@ export function asErrorDetails(e: unknown): State { } } -export function register(state: State, callHandler: WrappedCallHandler, eventBus: EventBus): void { +export function register(state: {}, callHandler: WrappedCallHandler, eventBus: EventBus): void { try { const pixelSender = new PixelSender(state, callHandler, eventBus) eventBus.on(ERRORS_CHANNEL, (error) => { - pixelSender.sendPixel(new StateWrapper(asErrorDetails(error), eventBus).combineWith(state || {}).combineWith(page.enrich({}))) + pixelSender.sendPixel(new StateWrapper(asErrorDetails(error), eventBus).combineWith(state || {})) }) } catch (e) { console.error('handlers.error.register', e) diff --git a/src/minimal-live-connect.ts b/src/minimal-live-connect.ts index 099c1618..61d0ad35 100644 --- a/src/minimal-live-connect.ts +++ b/src/minimal-live-connect.ts @@ -1,39 +1,51 @@ // @ts-nocheck /* eslint-disable */ -import { mergeObjects } from './pixel/fiddler' +import { EnrichmentContext } from './pixel/fiddler' import { IdentityResolver } from './idex' -import { enrich as peopleVerified } from './enrichers/people-verified' -import { enrich as additionalIdentifiers } from './enrichers/identifiers-nohash' -import { enrich as privacyConfig } from './enrichers/privacy-config' +import { enrichIdentifiers } from './enrichers/identifiers-nohash' +import { enrichPrivacyMode } from './enrichers/privacy-config' import { removeInvalidPairs } from './config-validators/remove-invalid-pairs' -import { WrappedReadOnlyStorageHandler } from './handlers/storage-handler' -import { WrappedCallHandler } from './handlers/call-handler' -import { StorageStrategies } from './model/storage-strategy' import { EventBus, ILiveConnect, LiveConnectConfig } from './types' import { LocalEventBus } from './events/event-bus' import { ReadOnlyStorageHandler, CallHandler, isObject } from 'live-connect-common' +import { enrichCallHandler } from './enrichers/call-handler' +import { enrichReadOnlyStorageHandler } from './enrichers/storage-handler' +import { enrichStorageStrategy } from './enrichers/storage-strategy' +import { enrichPeopleVerifiedId } from './enrichers/people-verified-id' function _minimalInitialization(liveConnectConfig: LiveConnectConfig, externalStorageHandler: ReadOnlyStorageHandler, externalCallHandler: CallHandler, eventBus: EventBus, push: (event: unknown) => void): ILiveConnect { try { - const callHandler = new WrappedCallHandler(externalCallHandler, eventBus) const validLiveConnectConfig = removeInvalidPairs(liveConnectConfig, eventBus) - const configWithPrivacy = mergeObjects(validLiveConnectConfig, privacyConfig(validLiveConnectConfig)) - const storageStrategy = configWithPrivacy.privacyMode ? StorageStrategies.disabled : configWithPrivacy.storageStrategy - const storageHandler = WrappedReadOnlyStorageHandler.make(storageStrategy, externalStorageHandler, eventBus) - const peopleVerifiedData = mergeObjects(configWithPrivacy, peopleVerified(configWithPrivacy, storageHandler, eventBus)) - const peopleVerifiedDataWithAdditionalIds = mergeObjects(peopleVerifiedData, additionalIdentifiers(peopleVerifiedData, storageHandler, eventBus)) - const resolver = IdentityResolver.makeNoCache(peopleVerifiedDataWithAdditionalIds, callHandler, eventBus) + + const stateBuilder = new EnrichmentContext({ + ...validLiveConnectConfig, + identifiersToResolve: validLiveConnectConfig.identifiersToResolve || [], + eventBus: eventBus, + storageHandler: externalStorageHandler, + callHandler: externalCallHandler + }) + + const enrichedState = stateBuilder + .via(enrichCallHandler) + .via(enrichPrivacyMode) + .via(enrichStorageStrategy) + .via(enrichReadOnlyStorageHandler) + .via(enrichPeopleVerifiedId) + .via(enrichIdentifiers) + .data + + const resolver = IdentityResolver.makeNoCache(enrichedState, enrichedState.callHandler, eventBus) + return { push: (arg) => push(arg), fire: () => push({}), - peopleVerifiedId: peopleVerifiedDataWithAdditionalIds.peopleVerifiedId, + peopleVerifiedId: enrichedState.peopleVerifiedId, ready: true, resolve: resolver.resolve.bind(resolver), resolutionCallUrl: resolver.getUrl.bind(resolver), config: validLiveConnectConfig, eventBus: eventBus, - storageHandler: storageHandler, - callHandler: callHandler + storageHandler: enrichedState.storageHandler } } catch (x) { console.error(x) diff --git a/src/pixel/fiddler.ts b/src/pixel/fiddler.ts index f2608824..4b28b930 100644 --- a/src/pixel/fiddler.ts +++ b/src/pixel/fiddler.ts @@ -1,4 +1,4 @@ -import { State } from '../types' +import { Enricher, State } from '../types' import { extractEmail } from '../utils/email' import { decodeValue } from '../utils/url' import { extractHashValue, hashEmail, isHash } from '../utils/hash' @@ -75,3 +75,15 @@ export function mergeObjects(obj1: A, obj2: }) return res } + +export class EnrichmentContext { + data: A + + constructor(state: A) { + this.data = state + } + + via(enricher: Enricher): EnrichmentContext { + return new EnrichmentContext({...this.data, ...enricher(this.data)}) + } +} diff --git a/src/pixel/sender.ts b/src/pixel/sender.ts index e317582d..0c711b9e 100644 --- a/src/pixel/sender.ts +++ b/src/pixel/sender.ts @@ -1,6 +1,6 @@ import { isArray, isFunction } from 'live-connect-common' import { asStringParam } from '../utils/params' -import { LiveConnectConfig, EventBus } from '../types' +import { LiveConnectConfig, EventBus, Enricher } from '../types' import { StateWrapper } from './state' import { WrappedCallHandler } from '../handlers/call-handler' diff --git a/src/pixel/state.ts b/src/pixel/state.ts index a17d668a..6b3b7672 100644 --- a/src/pixel/state.ts +++ b/src/pixel/state.ts @@ -108,16 +108,6 @@ export class StateWrapper { return new StateWrapper(mergeObjects(this.data, newInfo), this.eventBus) } - enrich(enrichers: Enricher[]): void { - enrichers.forEach((enricher) => { - try { - enricher(this.data) - } catch (e) { - this.eventBus.emitErrorWithMessage('StateEnrich', 'Error while enriching state', e) - } - }) - } - sendsPixel() { const source = isObject(this.data.eventSource) ? this.data.eventSource : {} const eventKeys = Object.keys(source) diff --git a/src/standard-live-connect.ts b/src/standard-live-connect.ts index fd1bcca2..6dc25f2d 100644 --- a/src/standard-live-connect.ts +++ b/src/standard-live-connect.ts @@ -1,25 +1,24 @@ // @ts-nocheck /* eslint-disable */ import { PixelSender } from './pixel/sender' -import * as errorHandler from './events/error-pixel' import * as C from './utils/consts' import { StateWrapper } from './pixel/state' -import { mergeObjects } from './pixel/fiddler' -import { resolve as idResolve } from './manager/identifiers' -import { resolve as decisionsResolve } from './manager/decisions' -import { enrich as pageEnrich } from './enrichers/page' -import { enrich as identifiersEnrich } from './enrichers/identifiers' -import { enrich as privacyConfig } from './enrichers/privacy-config' +import { EnrichmentContext, mergeObjects } from './pixel/fiddler' +import { enrichPage } from './enrichers/page' +import { enrichIdentifiers } from './enrichers/identifiers' +import { enrichPrivacyMode } from './enrichers/privacy-config' import { removeInvalidPairs } from './config-validators/remove-invalid-pairs' import { isArray, isObject, CallHandler, StorageHandler } from 'live-connect-common' import { IdentityResolver } from './idex' -import { WrappedStorageHandler } from './handlers/storage-handler' -import { WrappedCallHandler } from './handlers/call-handler' -import { StorageStrategies } from './model/storage-strategy' import { ConfigMismatch, EventBus, ILiveConnect, LiveConnectConfig, State } from './types' import { LocalEventBus, getAvailableBus } from './events/event-bus' -import { determineHighestAccessibleDomain } from './utils/domain' -import { makeCache } from './cache' +import { enrichDomain } from './enrichers/domain' +import { enrichStorageHandler } from './enrichers/storage-handler' +import { enrichStorageStrategy } from './enrichers/storage-strategy' +import { enrichDecisionIds } from './enrichers/decisions' +import { enrichLiveConnectId } from './enrichers/live-connect-id' +import { enrichCache } from './enrichers/cache' +import { enrichCallHandler } from './enrichers/call-handler' const hemStore: State = {} function _pushSingleEvent (event: any, pixelClient: PixelSender, enrichedState: StateWrapper, eventBus: EventBus) { @@ -88,50 +87,48 @@ function _standardInitialization (liveConnectConfig: LiveConnectConfig, external // TODO: proper config validation const validLiveConnectConfig = removeInvalidPairs(liveConnectConfig, eventBus) - const callHandler = new WrappedCallHandler(externalCallHandler, eventBus) - - const configWithPrivacy = mergeObjects(validLiveConnectConfig, privacyConfig(validLiveConnectConfig)) - const domain = determineHighestAccessibleDomain(storageHandler) - const configWithDomain = mergeObjects(configWithPrivacy, { domain: domain }) - - errorHandler.register(configWithDomain, callHandler, eventBus) - - const storageStrategy = configWithPrivacy.privacyMode ? StorageStrategies.disabled : (configWithPrivacy.storageStrategy || StorageStrategies.cookie) - const storageHandler = WrappedStorageHandler.make(storageStrategy, externalStorageHandler, eventBus) - - const cache = makeCache({ - strategy: storageStrategy, - storageHandler: storageHandler, - domain: domain, + const stateBuilder = new EnrichmentContext({ + ...validLiveConnectConfig, + identifiersToResolve: validLiveConnectConfig.identifiersToResolve || [], + contextSelectors: validLiveConnectConfig.contextSelectors || "", + contextElementsLength: validLiveConnectConfig.contextElementsLength || 0, + callHandler: externalCallHandler, + storageHandler: externalStorageHandler, + eventBus }) - const reducer = (accumulator, func) => accumulator.combineWith(func(accumulator.data, storageHandler, eventBus)) - - const enrichers = [pageEnrich, identifiersEnrich] - const managers = [idResolve, decisionsResolve] - - const enrichedState = enrichers.reduce(reducer, new StateWrapper(configWithPrivacy, eventBus)) - const postManagedState = managers.reduce(reducer, enrichedState) - - const syncContainerData = mergeObjects(configWithPrivacy, { peopleVerifiedId: postManagedState.data.peopleVerifiedId }) - const onPixelLoad = () => eventBus.emit(C.PIXEL_SENT_PREFIX, syncContainerData) + const enrichedState = stateBuilder + .via(enrichPrivacyMode) + .via(enrichStorageStrategy) + .via(enrichStorageHandler) + .via(enrichDomain) + .via(enrichCache) + .via(enrichPage) + .via(enrichIdentifiers) + .via(enrichDecisionIds) + .via(enrichLiveConnectId) + .via(enrichCallHandler) + .data + + const onPixelLoad = () => eventBus.emit(C.PIXEL_SENT_PREFIX, enrichedState) const onPixelPreload = () => eventBus.emit(C.PRELOAD_PIXEL, '0') - const pixelClient = new PixelSender(configWithPrivacy, callHandler, eventBus, onPixelLoad, onPixelPreload) - const resolver = IdentityResolver.make(postManagedState.data, storageHandler, callHandler, eventBus) - const _push = (...args: any[]) => _processArgs(args, pixelClient, postManagedState, eventBus) + const pixelClient = new PixelSender(enrichedState, enrichedState.callHandler, eventBus, onPixelLoad, onPixelPreload) + const resolver = IdentityResolver.make(enrichedState, enrichedState.storageHandler, enrichedState.callHandler, eventBus) + + const _push = (...args: any[]) => _processArgs(args, pixelClient, new StateWrapper(enrichedState, enrichedState.eventBus), eventBus) return { push: _push, fire: () => _push({}), - peopleVerifiedId: postManagedState.data.peopleVerifiedId, + peopleVerifiedId: enrichedState.peopleVerifiedId, ready: true, resolve: resolver.resolve.bind(resolver), resolutionCallUrl: resolver.getUrl.bind(resolver), config: validLiveConnectConfig, eventBus: eventBus, - storageHandler: storageHandler, - callHandler: callHandler, + storageHandler: enrichedState.storageHandler, + callHandler: enrichedState.callHandler, } } catch (x) { console.error(x) diff --git a/src/types.ts b/src/types.ts index 9a8fcaeb..4d1a412a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,7 +4,8 @@ import { StorageStrategy } from './model/storage-strategy' import { UrlCollectionMode } from './model/url-collection-mode' import { ErrorDetails } from 'live-connect-common' -export type Enricher = (state: State) => void // should mutate state in-place +export type Enricher = + (state: ActualIn) => ActualIn & Out export interface IdentityResolutionConfig { url?: string @@ -13,8 +14,6 @@ export interface IdentityResolutionConfig { source?: string publisherId?: number requestedAttributes?: string[] - contextSelectors?: string - contextElementsLength?: number } export interface LiveConnectConfig { @@ -32,7 +31,9 @@ export interface LiveConnectConfig { globalVarName?: string urlCollectionMode?: UrlCollectionMode queryParametersFilter?: string - ajaxTimeout?: number + ajaxTimeout?: number, + contextSelectors?: string + contextElementsLength?: number } export type ResolutionParams = Record