Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
mschuwalow committed Sep 14, 2023
1 parent be374df commit c4237d0
Show file tree
Hide file tree
Showing 13 changed files with 224 additions and 158 deletions.
Binary file added live-connect-js-6.0.3-alpha-8f0b28c.0.tgz
Binary file not shown.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"release:ci:major": "release-it major --ci"
},
"dependencies": {
"live-connect-common": "^v3.0.1",
"live-connect-common": "^v3.0.2",
"tiny-hashes": "1.0.1"
},
"devDependencies": {
Expand Down
128 changes: 128 additions & 0 deletions src/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { strEqualsIgnoreCase, expiresInHours } from 'live-connect-common'
import { WrappedStorageHandler } from './handlers/storage-handler'
import { StorageStrategies, StorageStrategy } from './model/storage-strategy'

export type CacheRecord = {
data: string
expiresAt?: Date
}

export interface DurableCache {
get: (key: string) => CacheRecord | null // null is used to signal missing value
set: (key: string, value: string, expiration?: Date) => void
}

export type StorageHandlerBackedCacheOpts = {
strategy: 'cookie' | 'ls',
storageHandler: WrappedStorageHandler,
domain: string,
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 class StorageHandlerBackedCache implements DurableCache {
private handler
private storageStrategy
private defaultExpirationHours?
private domain

constructor (opts: StorageHandlerBackedCacheOpts) {
this.handler = opts.storageHandler
this.storageStrategy = opts.strategy
this.defaultExpirationHours = opts.defaultExpirationHours
this.domain = opts.domain
}

private getCookieRecord(key: string): CacheRecord | null {
let expiresAt: Date | undefined

const cookieExpirationEntry = this.handler.getCookie(expirationKey(key))
if (cookieExpirationEntry && cookieExpirationEntry.length > 0) {
expiresAt = new Date(cookieExpirationEntry)
if (expiresAt <= new Date()) {
return null
}
}

const data = this.handler.getCookie(key)
if (data) {
return { data, expiresAt }
} else {
return null
}
}

private getLSRecord(key: string): CacheRecord | null {
let expiresAt: Date | undefined
const oldLsExpirationEntry = this.handler.getDataFromLocalStorage(expirationKey(key))

if (oldLsExpirationEntry) {
expiresAt = new Date(oldLsExpirationEntry)
if (expiresAt <= new Date()) {
this.handler.removeDataFromLocalStorage(key)
this.handler.removeDataFromLocalStorage(expirationKey(key))
return null
}
}

const data = this.handler.getDataFromLocalStorage(key)
if (data) {
return { data, expiresAt }
} else {
return null
}
}

get(key: string): CacheRecord | null {
if (strEqualsIgnoreCase(this.storageStrategy, StorageStrategies.localStorage) && this.handler.localStorageIsEnabled()) {
return this.getLSRecord(key)
} else {
return this.getCookieRecord(key)
}
}

set(key: string, value: string, expires?: Date): void {
if (!expires && this.defaultExpirationHours) {
expires = expiresInHours(this.defaultExpirationHours)
}

if (strEqualsIgnoreCase(this.storageStrategy, StorageStrategies.localStorage) && this.handler.localStorageIsEnabled()) {
this.handler.setDataInLocalStorage(key, value)
if (expires) {
this.handler.setDataInLocalStorage(expirationKey(key), `${expires}`)
} else {
this.handler.removeDataFromLocalStorage(expirationKey(key))
}
} else {
this.handler.setCookie(key, value, expires, 'Lax', this.domain)
if (expires) {
this.handler.setCookie(expirationKey(key), `${expires}`, expires, 'Lax', this.domain)
} else {
// sentinel value to indicate no expiration
this.handler.setCookie(expirationKey(key), '', undefined, 'Lax', this.domain)
}
}
}
}

export const NoOpCache: DurableCache = {
get: () => null,
set: () => undefined
}

function expirationKey(baseKey: string): string {
return `${baseKey}_exp`
}
6 changes: 3 additions & 3 deletions src/enrichers/people-verified.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { PEOPLE_VERIFIED_LS_ENTRY } from '../utils/consts'
import { EventBus, State } from '../types'
import { WrappedReadOnlyStorageHandler } from '../handlers/storage-handler'
import { DurableCache } from '../cache'

export function enrich(state: State, storageHandler: WrappedReadOnlyStorageHandler, eventBus: EventBus): State {
export function enrich(state: State, cache: DurableCache, eventBus: EventBus): State {
try {
return { peopleVerifiedId: state.peopleVerifiedId || storageHandler.getDataFromLocalStorage(PEOPLE_VERIFIED_LS_ENTRY) || undefined }
return { peopleVerifiedId: state.peopleVerifiedId || cache.get(PEOPLE_VERIFIED_LS_ENTRY)?.data || undefined }
} catch (e) {
eventBus.emitError('PeopleVerifiedEnrich', e)
return {}
Expand Down
84 changes: 0 additions & 84 deletions src/handlers/storage-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@ import { StorageStrategies, StorageStrategy } from '../model/storage-strategy'
import { EventBus, ReadOnlyStorageHandler, StorageHandler, strEqualsIgnoreCase } from 'live-connect-common'
import { WrappingContext } from '../utils/wrapping'

type StorageRecord = {
data: string
expiresAt?: Date
}

const noop = () => undefined

function wrapRead<T extends object, K extends keyof T & string>(wrapper: WrappingContext<T>, storageStrategy: StorageStrategy, functionName: K) {
Expand Down Expand Up @@ -72,81 +67,6 @@ export class WrappedStorageHandler extends WrappedReadOnlyStorageHandler impleme
return handler
}

private getCookieRecord(key: string): StorageRecord | null {
let expiresAt: Date | undefined

const cookieExpirationEntry = this.getCookie(expirationKey(key))
if (cookieExpirationEntry && cookieExpirationEntry.length > 0) {
expiresAt = new Date(cookieExpirationEntry)
if (expiresAt <= new Date()) {
return null
}
}

const data = this.getCookie(key)
if (data) {
return { data, expiresAt }
} else {
return null
}
}

private getLSRecord(key: string): StorageRecord | null {
if (this.localStorageIsEnabled()) {
let expiresAt: Date | undefined
const oldLsExpirationEntry = this.getDataFromLocalStorage(expirationKey(key))

if (oldLsExpirationEntry) {
expiresAt = new Date(oldLsExpirationEntry)
if (expiresAt <= new Date()) {
this.removeDataFromLocalStorage(key)
this.removeDataFromLocalStorage(expirationKey(key))
return null
}
}

const data = this.getDataFromLocalStorage(key)
if (data) {
return { data, expiresAt }
} else {
return null
}
} else {
return null
}
}

get(key: string): StorageRecord | null {
if (strEqualsIgnoreCase(this.storageStrategy, StorageStrategies.none) || strEqualsIgnoreCase(this.storageStrategy, StorageStrategies.disabled)) {
return null
} else if (strEqualsIgnoreCase(this.storageStrategy, StorageStrategies.localStorage)) {
return this.getLSRecord(key)
} else {
return this.getCookieRecord(key)
}
}

set(key: string, value: string, expires?: Date, domain?: string): void {
if (strEqualsIgnoreCase(this.storageStrategy, StorageStrategies.none) || strEqualsIgnoreCase(this.storageStrategy, StorageStrategies.disabled)) {
// pass
} else if (strEqualsIgnoreCase(this.storageStrategy, StorageStrategies.localStorage) && this.localStorageIsEnabled()) {
this.setDataInLocalStorage(key, value)
if (expires) {
this.setDataInLocalStorage(expirationKey(key), `${expires}`)
} else {
this.removeDataFromLocalStorage(expirationKey(key))
}
} else {
this.setCookie(key, value, expires, 'Lax', domain)
if (expires) {
this.setCookie(expirationKey(key), `${expires}`, expires, 'Lax', domain)
} else {
// sentinel value to indicate no expiration
this.setCookie(expirationKey(key), '', undefined, 'Lax', domain)
}
}
}

setCookie(key: string, value: string, expires?: Date, sameSite?: string, domain?: string): void {
this.functions.setCookie(key, value, expires, sameSite, domain)
}
Expand All @@ -163,7 +83,3 @@ export class WrappedStorageHandler extends WrappedReadOnlyStorageHandler impleme
return this.functions.findSimilarCookies(substring) || []
}
}

function expirationKey(baseKey: string): string {
return `${baseKey}_exp`
}
82 changes: 23 additions & 59 deletions src/manager/identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,72 +5,36 @@ import { expiresInDays } from 'live-connect-common'
import { PEOPLE_VERIFIED_LS_ENTRY } from '../utils/consts'
import { EventBus, State } from '../types'
import { WrappedStorageHandler } from '../handlers/storage-handler'
import { DurableCache } from '../cache'

const NEXT_GEN_FP_NAME = '_lc2_fpi'
const TLD_CACHE_KEY = '_li_dcdm_c'
const DEFAULT_EXPIRATION_DAYS = 730

export function resolve(state: State, storageHandler: WrappedStorageHandler, eventBus: EventBus): State {
try {
const determineTld = () => {
const cachedDomain = storageHandler.getCookie(TLD_CACHE_KEY)
if (cachedDomain) {
return cachedDomain
}
const domain = loadedDomain()
const arr = domain.split('.')
for (let i = arr.length; i > 0; i--) {
const newD = `.${arr.slice(i - 1, arr.length).join('.')}`
storageHandler.setCookie(TLD_CACHE_KEY, newD, undefined, 'Lax', newD)
if (storageHandler.getCookie(TLD_CACHE_KEY)) {
return newD
}
}
return `.${domain}`
}
export function resolve(
state: { expirationDays?: number, domain: string },
storageHandler: WrappedStorageHandler,
cache: DurableCache,
eventBus: EventBus
): State {
const expiry = state.expirationDays || DEFAULT_EXPIRATION_DAYS
const oldValue = cache.get(NEXT_GEN_FP_NAME)?.data

const getOrAddWithExpiration = (key: string, value: string) => {
try {
const oldValue = storageHandler.get(key)?.data
const expiry = expiresInDays(storageOptions.expires)
if (oldValue) {
storageHandler.set(key, oldValue, expiry, storageOptions.domain)
} else {
storageHandler.set(key, value, expiry, storageOptions.domain)
}
return storageHandler.get(key)?.data
} catch (e) {
eventBus.emitErrorWithMessage('CookieLsGetOrAdd', 'Failed manipulating cookie jar or ls', e)
return null
}
}
if (oldValue) {
cache.set(NEXT_GEN_FP_NAME, oldValue, expiresInDays(expiry))
} else {
const newValue = `${domainHash(state.domain)}--${ulid()}`

const generateCookie = (apexDomain: string) => {
const cookie = `${domainHash(apexDomain)}--${ulid()}`
return cookie.toLocaleLowerCase()
}
cache.set(NEXT_GEN_FP_NAME, newValue, expiresInDays(expiry))
}

const liveConnectIdentifier = cache.get(NEXT_GEN_FP_NAME)?.data || undefined

const expiry = state.expirationDays || DEFAULT_EXPIRATION_DAYS
const cookieDomain = determineTld()
const storageOptions = {
expires: expiry,
domain: cookieDomain
}
const liveConnectIdentifier = getOrAddWithExpiration(
NEXT_GEN_FP_NAME,
generateCookie(cookieDomain)
) || undefined
if (liveConnectIdentifier) {
storageHandler.setDataInLocalStorage(PEOPLE_VERIFIED_LS_ENTRY, liveConnectIdentifier)
}

if (liveConnectIdentifier) {
storageHandler.setDataInLocalStorage(PEOPLE_VERIFIED_LS_ENTRY, liveConnectIdentifier)
}
return {
domain: cookieDomain,
liveConnectId: liveConnectIdentifier,
peopleVerifiedId: liveConnectIdentifier
}
} catch (e) {
eventBus.emitErrorWithMessage('IdentifiersResolve', 'Error while managing identifiers', e)
return {}
return {
liveConnectId: liveConnectIdentifier,
peopleVerifiedId: liveConnectIdentifier
}
}
12 changes: 11 additions & 1 deletion src/pixel/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { fiddle, mergeObjects } from './fiddler'
import { isObject, trim, isArray, nonNull } from 'live-connect-common'
import { asStringParam, asParamOrEmpty, asStringParamWhen, asStringParamTransform } from '../utils/params'
import { toParams } from '../utils/url'
import { EventBus, State } from '../types'
import { Enricher, EventBus, State } from '../types'
import { collectUrl } from './url-collector'

const noOpEvents = ['setemail', 'setemailhash', 'sethashedemail']
Expand Down Expand Up @@ -108,6 +108,16 @@ 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)
Expand Down
Loading

0 comments on commit c4237d0

Please sign in to comment.