@apptus/esales-api
Version:
Library for making requests to Elevate 4 API v3
123 lines (105 loc) • 3.99 kB
text/typescript
import { assert, assertString, once, isString } from './util/mod.ts';
import type { Session, SessionMetadata } from './config.ts';
/** Helps retrieve and update session metadata and persist it in LocalStorage. */
export interface LocalStorageSession extends Session {
/**
* Method for retrieving session information synchronously
* from `LocalStorage`. Returns a `SessionMetadata` object that
* contains `customerKey` and `sessionKey`.
*
* @example
* ```ts
* declare const session: LocalStorageSession;
*
* const { customerKey, sessionKey } = session();
* ```
*/
(): SessionMetadata;
/**
* Generates new customer- & sessionKeys via `crypto.randomUUID()` and persists them.
* Suitable to use e.g. when a visitor logs out.
*/
reset(): void;
/**
* Updates the `customerKey` to the provided value and persists it.
* Suitable to use e.g. when a visitor is identified by signing in to their account.
*/
updateCustomerKey(key: string): void;
}
const __storage: Storage = /* @__PURE__ */ (() => globalThis.localStorage)();
/** @internal exported for testing */
export const __sessionMetadataCache = new Map<string, SessionMetadata>();
/**
* Create a `Session` compatible object for retrieving and mutating session metadata.
* This method is only intended to be used in Browsers.
*
* Storage is backed by LocalStorage, where reads/writes will happen. Reads are cached
* in memory, and invalidated by `storage` events on Window. This method can be called
* multiple times, but shares Window listener between them.
*
* If session information does not exist, customer- & sessionKeys will be generated
* automatically via `crypto.randomUUID()`.
*
* @param storageKey - The key that will be used to store session metadata. Defaults to `'voyado.session'`.
*
* @example
* ```ts
* import { elevate, localStorageBackedSession } from '@apptus/esales-api';
*
* const api = elevate({
* // ...
* session: localStorageBackedSession()
* });
* ```
* @example
* ```ts
* const session = localStorageBackedSession();
*
* // Set a specific customerKey (e.g. on visitor sign-in)
* session.updateCustomerKey(user.id);
*
* // Generate new customer/sessionKeys (e.g. on visitor signout)
* session.reset();
* ```
*/
export function localStorageBackedSession(storageKey = 'voyado.session'): LocalStorageSession {
enableCachePruning();
const fetcher = () => read(storageKey);
fetcher.updateCustomerKey = (customerKey: string) => update(storageKey, { ...fetcher(), customerKey });
fetcher.reset = () => update(storageKey, generateSession());
return fetcher;
}
const enableCachePruning = /* @__PURE__ */ once(() => {
globalThis.addEventListener('storage', ({ key, storageArea }: StorageEvent) => {
if (key && storageArea === __storage) __sessionMetadataCache.delete(key);
});
});
function generateSession(): SessionMetadata {
return { customerKey: crypto.randomUUID(), sessionKey: crypto.randomUUID() };
}
function read(key: string): SessionMetadata {
// Update cached value by reading from LocalStorage if needed
if (!__sessionMetadataCache.has(key)) {
const strData = __storage.getItem(key);
const session = generateSession();
try {
assertString(strData);
const d: unknown = JSON.parse(strData);
assert(d && typeof d === 'object');
if ('customerKey' in d && isString(d.customerKey)) session.customerKey = d.customerKey;
if ('sessionKey' in d && isString(d.sessionKey)) session.sessionKey = d.sessionKey;
update(key, session, strData);
} catch {
update(key, session);
}
}
return __sessionMetadataCache.get(key)!;
}
function update(key: string, data: SessionMetadata, prevData = '') {
updateCache(key, data);
const currData = JSON.stringify(data);
if (prevData !== currData) __storage.setItem(key, currData);
}
function updateCache(key: string, data: SessionMetadata) {
__sessionMetadataCache.set(key, data);
}