@cside.dev/device-intelligence
v1.1.0
Published
cside Device Intelligence: collect client telemetry and exchange it for a device token
Readme
@cside.dev/device-intelligence
Collect client telemetry and exchange it for a cside device token, with types. The collection code is bundled into the package, so there is no remote script to load.
Installation
# npm
npm i @cside.dev/device-intelligence
# yarn
yarn add @cside.dev/device-intelligence
# pnpm
pnpm i @cside.dev/device-intelligenceUsage
import { initDeviceIntelligence } from "@cside.dev/device-intelligence";
const { sendClientTelemetry } = await initDeviceIntelligence({
teamID: "your-team-id",
});
const { token, errors } = await sendClientTelemetry({ userId: "u_123" });initDeviceIntelligence starts passive signal collection and behavior tracking, so a later sendClientTelemetry() call only has the per-call probes left to run. Send the returned token to your backend to look up the device. Telemetry is posted to the cside edge, which attributes it by the page origin.
sendClientTelemetry never rejects: when telemetry could not be collected it resolves with { token: null, errors }. Collection needs a browser; see Server-side rendering for what happens elsewhere.
React
Calling it from an effect is fine, including under StrictMode, where the effect runs twice: collection only starts once per page, and both calls resolve with the same function.
import {
initDeviceIntelligence,
type SendClientTelemetry,
} from "@cside.dev/device-intelligence";
import { useEffect, useState } from "react";
export function useDeviceIntelligence(teamID: string) {
const [send, setSend] = useState<SendClientTelemetry>();
useEffect(() => {
let active = true;
initDeviceIntelligence({ teamID }).then(({ sendClientTelemetry }) => {
// Wrapped in a callback so React stores the function itself.
if (active) setSend(() => sendClientTelemetry);
});
return () => {
active = false;
};
}, [teamID]);
return send;
}Options
| Option | Type | Default | Description |
| -------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| teamID | string | required | Your cside team ID. Collection does not use it yet; a later release will use it to fetch the team's dynamic configuration. |
API
initDeviceIntelligence(options)
Returns a Promise<{ sendClientTelemetry }>.
- Starts passive signal collection and behavior tracking. Calling it again is safe: collection only starts once per page, and every call resolves with the same
sendClientTelemetry. - Rejects with a
TypeErrorwhenteamIDis missing or empty. - It returns a promise even though nothing is awaited today, so the dynamic configuration fetch planned for
teamIDcan land without a breaking change.
Also available as the default export.
sendClientTelemetry(externalIds?)
Collects device signals and exchanges them for a device token.
const { token, errors } = await sendClientTelemetry({ userId: "u_123" });externalIds is an optional map of your own identifiers to attach to the collected signals. Resolves with { token } on success and { token: null, errors } on failure.
Server-side rendering
The package can be imported anywhere a server renderer or edge runtime evaluates client code. Its exports map points the react-server, edge-light, workerd, worker and node conditions at a small server entry with the same API, so no environment checks are needed in your code and none exist in the browser bundle. On the server, initDeviceIntelligence resolves with a sendClientTelemetry that returns { token: null, errors } instead of collecting anything. Browser builds (the browser and default conditions) get the real bundle.
Content-Security-Policy
The package posts telemetry to the cside edge. No script is loaded from a team subdomain:
Every telemetry request also carries the package version as the x-v-di request header; the edge allows it in its CORS preflight.
connect-src https://edge.csidefd.com;Build artifacts
This package produces two outputs from one codebase:
dist/index.jsanddist/index.cjs: the npm library documented above (obfuscated, no readable source).dist-cdn/fingerprint.js: the CDN artifact deployed to the edge and served to browsers. Same collection pipeline, exposed as a global instead of an npm export.
Both run the same seeded orchestration bytecode. The readable source of truth is src/bytecode/operations.ts and spec.ts; add handlers and readable steps there rather than editing numeric output. Each step declares whether it runs when the script loads or when the page asks for a token, and the specification emits one program per phase over a shared slot space. Rollup generates a seeded physical layout for each release; the seed, source commit, bytecode version, and program hash are written to the ignored dist-cdn/.device-intelligence-bytecode.metadata.json build record. Release CI should set DEVICE_INTELLIGENCE_BYTECODE_SEED once per artifact; local development and unit tests use fixed defaults.
This provides reverse-engineering resistance, not secrecy. The browser still receives the handlers and generated program. The direct implementation in bytecode/reference.ts is test-only and is never a production fallback.
Development
pnpm dev at the repo root rebuilds on change and serves the html folder under localhost:8069/di/ (the web-script pages sit at the root of the same server); pnpm test runs the vitest and Playwright suites against a server on port 1993. The Playwright suite covers both artifacts: device-intelligence.spec.ts loads the CDN script, and library.spec.ts imports the npm bundle (served as /lib/index.js) through html/library.html. The local dev/test server lives in packages/core/dev, shared with web-script.
Documentation
See the cside docs for more.
Minimum release age
We recommend configuring a minimum release age so your package manager only installs versions that have been published for at least 7 days. Compromised package versions are typically detected and unpublished within days of publication, so a cooldown protects you from npm supply chain attacks. This applies to all your dependencies, not just this package.
# npm (.npmrc, in days)
min-release-age=7# yarn (.yarnrc.yml, as a duration string)
npmMinimalAgeGate: "7d"# pnpm (pnpm-workspace.yaml, in minutes)
minimumReleaseAge: 10080