@docx-editor.dev/telemetry/ingest
Receive editor telemetry on your server and forward it to a log service, such as Better Stack, Axiom, or Datadog Logs. Pair it with httpExporter in the browser: the browser posts to your route, and only your server holds the log service's token.
Anyone can POST to a public route, so every event is checked again with the same closed lists the browser used, and fields the client chose itself are dropped unless you allow them. No dependency: Fetch API Request and Response only, so it runs in Next.js route handlers, Hono, Bun, Deno, and Cloudflare Workers.
Functions (3)
telemetryRoutefunctionSource ↗
A Fetch-style handler for telemetry from httpExporter: it accepts POST only, checks the origin, reads at most maxBodyBytes, validates every event, and calls forward with what passes. It answers 204 to every request, so a telemetry problem never surfaces in the editor; use onError to see failures. It does not rate-limit; put it behind your platform's limits.
declare function telemetryRoute(options: TelemetryRouteOptions): (request: Request) => Promise<Response>;```ts
// Next.js: app/api/telemetry/route.ts
import { telemetryRoute, toLogRecord } from '@docx-editor.dev/telemetry/ingest';
export const POST = telemetryRoute({
forward: (events) =>
fetch(`https://${process.env.BETTERSTACK_INGESTING_HOST}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.BETTERSTACK_SOURCE_TOKEN}`,
},
body: JSON.stringify(events.map(toLogRecord)),
}),
});
```toLogRecordfunctionSource ↗
An event as a log record: dt for the time, message for the live tail, and every field of the event except the fixed hint and docs text, which the code already identifies. Add your own fixed fields, such as a service name, to the result.
declare function toLogRecord(event: TelemetryEvent): TelemetryLogRecord;```ts
body: JSON.stringify(events.map((event) => ({ ...toLogRecord(event), service: 'my-app' })))
```validateTelemetryEventsfunctionSource ↗
The telemetry events in an untrusted request body, checked again with the package's own closed lists. An event with an unknown kind or code, or without the fields its kind needs, is dropped. A field outside its list is removed. The client's resource and attributes keep only the keys in attributeKeys. Returns an empty list for a body that is not an array or is longer than maxEvents.
The check proves that every value comes from the package's lists or is a number. It cannot prove that a client told the truth: a script can send a well-formed event that never happened. It can never send text.
declare function validateTelemetryEvents(body: unknown, options?: ValidateTelemetryEventsOptions): TelemetryEvent[];```ts
const events = validateTelemetryEvents(await request.json(), { attributeKeys: ['app.version'] });
```Interfaces (2)
TelemetryRouteOptionsinterfaceSource ↗
Options for [telemetryRoute](telemetryRoute).
interface TelemetryRouteOptions extends ValidateTelemetryEventsOptions| Member | Type | Summary |
|---|---|---|
| allowedOrigins? | readonly string[] | Origins allowed to post, such as `['https://app.example.com']`. Omit to allow any. |
| forward | | Send the checked events on, such as to your log service. Errors are caught and reported. |
| maxBodyBytes? | number | The largest body accepted, in bytes. Defaults to 64 KiB. |
| onError? | (error: unknown) => void | Called when `forward` throws or rejects, or the log service refuses. Defaults to nothing. |
ValidateTelemetryEventsOptionsinterfaceSource ↗
Options for [validateTelemetryEvents](validateTelemetryEvents).
interface ValidateTelemetryEventsOptions| Member | Type | Summary |
|---|---|---|
| attributeKeys? | readonly string[] | Keys of the browser's `resource` and `attributes` to keep, such as `['app.version']`. Each kept value must be a number, a boolean, or an identifier string. Defaults to none: a value the client chose cannot be checked for meaning, so add your fixed fields on the server. |
| maxEvents? | number | The most events one request may carry. Defaults to 50; a longer list is refused whole. |
Type aliases (2)
TelemetryEventtypeSource ↗
A diagnostic as an exporter receives it. Switch on kind to narrow it.
type TelemetryEvent = EditorDiagnostic & TelemetryEventContext;TelemetryLogRecordtypeSource ↗
A log record with the fields log services read for time and summary.
type TelemetryLogRecord = Record<string, unknown> & {
readonly dt: number;
readonly message: string;
};