TheiaCollector
TheiaCollector is a headless React component that (once per 24h) gathers device/browser telemetry via the Theia class and submits it to the Fraud / Security Platform endpoint (fsp /dfp/col). It returns null and is intended to be mounted globally (e.g. inside _app.tsx after user/session context is available).
Responsibilities
| Responsibility | Detail |
|---|---|
| Meta generation | Calls Theia.getMeta() (synchronous, crypto-backed) |
| Property enrichment | Awaits Theia.getProperties() for extended signals |
| Submission | Uses useAPI with { domain: 'fsp', path: '/dfp/col', method: 'post' } |
| Rate limiting | Writes a 24h expirable flag (elsCmnTheiaIsCollected) in ExpirableLocalStorage |
| Environment gating | Only runs if CryptoKit.isProtectionSupported() returns true |
| Logging | Structured warnings for fetch failures and general errors |
Mount Conditions
Collection triggers if BOTH are true:
ExpirableLocalStorage.get(elsCmnTheiaIsCollected) !== '1'CryptoKit.isProtectionSupported()istrue
Otherwise the effect is a no-op.
Import & Basic Usage
import { TheiaCollector } from '@traveloka/core';
export function AppShell({ children }) {
return (
<>
<TheiaCollector />
{children}
</>
);
}
Place it high enough that it reliably mounts during user sessions, but after any required runtime config / feature flags are ready (so that useAPI is correctly configured).
Submission Payload
{
"meta": { "c": "..", "r": "..", "a": "..", "d": "..", "t": "..", "e": ".." },
"p": {
/* flattened / stringified property map */
},
}
- All values are strings
- Properties object is sparsely populated (undefined / null keys omitted)
Logging Keys
| Key | Scenario |
|---|---|
theia.collector_fetcher_error | API call returned non-success (includes statusCode) |
theia.collector_general_error | Unexpected runtime exception in collection flow |
Additional upstream keys from Theia may appear (theia.meta_fallback, theia.properties_fallback).
Rate Limiting Behavior
After a successful POST, an expirable local storage entry is set:
- Key:
elsCmnTheiaIsCollected - TTL: 24 _ 60 _ 60 * 1000 ms (24h)
Subsequent mounts within that window will skip collection instantly.
Failure Modes
| Failure | Effect | Retry Behavior |
|---|---|---|
| Network / 5xx | Logs fetcher_error | Next page load after 24h window or manual removal of flag |
| Local exception | Logs general_error | Same as above |
Unsupported protection (CryptoKit) | No attempt | Re-evaluated on each mount |
| Meta / properties fallback | Sends reduced signal set | Still counts as success if API 200 |
Testing Strategy
Mock all side-effect modules:
jest.mock('@traveloka/core', () => ({
...jest.requireActual('@traveloka/core'),
Theia: {
getMeta: () => ({ c: '1', r: '2', a: '3', d: '4', t: '5', e: '6' }),
getProperties: () => Promise.resolve({ ua: 'X' }),
},
}));
Control collection by mocking:
ExpirableLocalStorage.get = jest.fn(() => undefined);
CryptoKit.isProtectionSupported = jest.fn(() => true);
Intercept API:
useAPI.mockReturnValue(jest.fn(() => Promise.resolve({ success: true })));
Observability Checklist
Track:
- Daily count of successful submissions
- Ratio of collector errors vs successes
- Frequency of fallback meta generation
Placement Guidance
Mount once. Multiple instances in the same 24h window add minimal overhead (they abort early) but create conceptual clutter.
Last updated: 2025-09-24