Skip to main content

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

ResponsibilityDetail
Meta generationCalls Theia.getMeta() (synchronous, crypto-backed)
Property enrichmentAwaits Theia.getProperties() for extended signals
SubmissionUses useAPI with { domain: 'fsp', path: '/dfp/col', method: 'post' }
Rate limitingWrites a 24h expirable flag (elsCmnTheiaIsCollected) in ExpirableLocalStorage
Environment gatingOnly runs if CryptoKit.isProtectionSupported() returns true
LoggingStructured warnings for fetch failures and general errors

Mount Conditions

Collection triggers if BOTH are true:

  1. ExpirableLocalStorage.get(elsCmnTheiaIsCollected) !== '1'
  2. CryptoKit.isProtectionSupported() is true

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

KeyScenario
theia.collector_fetcher_errorAPI call returned non-success (includes statusCode)
theia.collector_general_errorUnexpected 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

FailureEffectRetry Behavior
Network / 5xxLogs fetcher_errorNext page load after 24h window or manual removal of flag
Local exceptionLogs general_errorSame as above
Unsupported protection (CryptoKit)No attemptRe-evaluated on each mount
Meta / properties fallbackSends reduced signal setStill 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