@docx-editor.dev/telemetry 0.0.1 → 2.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +89 -7
  2. package/THIRD_PARTY_NOTICES.md +22 -0
  3. package/dist/chunk-6BTB6Y3H.cjs +1 -0
  4. package/dist/chunk-7SYLCJFQ.cjs +1 -0
  5. package/dist/chunk-AL2YERWY.cjs +1 -0
  6. package/dist/chunk-DT2KMPUJ.js +1 -0
  7. package/dist/chunk-E2HR5DHZ.cjs +2 -0
  8. package/dist/chunk-E3FTXDLH.js +1 -0
  9. package/dist/chunk-JM4KJB6O.js +4 -0
  10. package/dist/chunk-PZZC5AXX.cjs +1 -0
  11. package/dist/chunk-QDOUVRQA.cjs +4 -0
  12. package/dist/chunk-RSWAOCAT.js +1 -0
  13. package/dist/chunk-W5HFB5EV.js +1 -0
  14. package/dist/chunk-WGWKNKUT.cjs +1 -0
  15. package/dist/chunk-WHVIRIUW.js +1 -0
  16. package/dist/chunk-WLWS5CZT.js +2 -0
  17. package/dist/hocuspocus/index.cjs +1 -0
  18. package/dist/hocuspocus/index.d.cts +118 -0
  19. package/dist/hocuspocus/index.d.ts +118 -0
  20. package/dist/hocuspocus/index.js +1 -0
  21. package/dist/index.cjs +1 -0
  22. package/dist/index.d.cts +355 -0
  23. package/dist/index.d.ts +355 -0
  24. package/dist/index.js +1 -0
  25. package/dist/ingest/index.cjs +1 -0
  26. package/dist/ingest/index.d.cts +116 -0
  27. package/dist/ingest/index.d.ts +116 -0
  28. package/dist/ingest/index.js +1 -0
  29. package/dist/otel/index.cjs +1 -0
  30. package/dist/otel/index.d.cts +57 -0
  31. package/dist/otel/index.d.ts +57 -0
  32. package/dist/otel/index.js +1 -0
  33. package/dist/pipeline-CCq2Ng0B.d.cts +229 -0
  34. package/dist/pipeline-CCq2Ng0B.d.ts +229 -0
  35. package/dist/prometheus/index.cjs +1 -0
  36. package/dist/prometheus/index.d.cts +88 -0
  37. package/dist/prometheus/index.d.ts +88 -0
  38. package/dist/prometheus/index.js +1 -0
  39. package/dist/sentry/index.cjs +1 -0
  40. package/dist/sentry/index.d.cts +134 -0
  41. package/dist/sentry/index.d.ts +134 -0
  42. package/dist/sentry/index.js +1 -0
  43. package/dist/server/index.cjs +1 -0
  44. package/dist/server/index.d.cts +156 -0
  45. package/dist/server/index.d.ts +156 -0
  46. package/dist/server/index.js +1 -0
  47. package/package.json +100 -16
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ 'use strict';var chunk6BTB6Y3H_cjs=require('./chunk-6BTB6Y3H.cjs'),chunkAL2YERWY_cjs=require('./chunk-AL2YERWY.cjs'),chunkE2HR5DHZ_cjs=require('./chunk-E2HR5DHZ.cjs'),chunk7SYLCJFQ_cjs=require('./chunk-7SYLCJFQ.cjs');require('./chunk-WGWKNKUT.cjs');var core=require('@docx-editor.dev/core');var X={documentOpen:true,documentSave:true,sessions:true,editing:true,trackedChanges:true,fonts:true,history:true,customNodes:true,collaborationClient:true,performance:true},Y=new Set(["edit.tracking-required","edit.suggesting-needs-author","edit.tracking-mode-unsupported","edit.tracking-author-required","edit.tracking-unsupported-host"]);function Z(e){switch(e.kind){case "document.open":return "documentOpen";case "document.save":return "documentSave";case "session":return "sessions";case "edit.rejected":return e.tracked||Y.has(e.code)?"trackedChanges":"editing";case "review.skipped":return "trackedChanges";case "font.failed":return "fonts";case "history":return "history";case "custom-node":return "customNodes";case "collab.status":case "collab.participants":return "collaborationClient";case "performance":return "performance";default:return null}}function J(e){return e.kind==="collab.participants"?`${e.editorSessionId}|participants`:e.kind==="edit.rejected"?[e.editorSessionId,e.code,e.cause,e.errorName,e.stage,e.origin,e.tracked,e.command].join("|"):e.kind==="font.failed"?`${e.editorSessionId}|font|${e.code}`:e.kind==="history"||e.kind==="custom-node"?`${e.editorSessionId}|${e.kind}|${e.code}`:e.kind==="collab.status"?`${e.editorSessionId}|status|${e.status}|${e.code??""}|${e.recovering}`:null}function Q(e){let t=chunk6BTB6Y3H_cjs.c(e),m=e.metricLabels??[];function n(o){let r={};try{let a=typeof e.attributes=="function"?e.attributes():e.attributes;r=chunk7SYLCJFQ_cjs.c(a??{},"attributes");}catch{}return Object.freeze({...r,...o})}function c(o){let r={};for(let a of m)o[a]!==void 0&&(r[a]=o[a]);return r}let f=e.include??X,l=e.dedupeWindowMs??1e4,g=o=>{try{if(e.filter&&!e.filter(o))return}catch{return}t.event(o);},E=e.documentShape==="detailed",y=new Map,S=false,k=()=>{},T=false;function D(o){let r=f[o];return r?r===true?1:Math.min(1,Math.max(0,r.sampleRate??1)):null}let h=new Map;function w(o,r,a){if(a>=1)return true;if(r!=="performance"&&r!=="sessions")return Math.random()<a;let p=`${r}|${o.editorSessionId}`,b=h.get(p);return b===void 0&&(h.size>=1e3&&h.clear(),b=Math.random()<a,h.set(p,b)),b}function M(){if(S)return;S=true;let o=globalThis;if(typeof o.addEventListener!="function")return;let r=()=>{t.setPageClosing(true),u();},a=()=>{let p=globalThis.document?.visibilityState==="hidden";t.setPageClosing(p),p&&u();};o.addEventListener("pagehide",r),o.addEventListener("visibilitychange",a),k=()=>{o.removeEventListener?.("pagehide",r),o.removeEventListener?.("visibilitychange",a);};}function v(o){let r=y.get(o);if(r){if(y.delete(o),clearTimeout(r.timer),r.latest){let a=r.event;r.latest.count!==a.count&&g(I(F(r.latest,E),t,r.event.attributes));}else if(r.repeated>0){let{connectMs:a,...p}=r.event;g({...p,repeated:Math.min(r.repeated,chunk7SYLCJFQ_cjs.a)});}}}function i(o,r={}){if(T||!t.consented())return;let a=n(r),p=chunk7SYLCJFQ_cjs.b(I(o,t,a));if("dropped"in p){t.metric(chunkAL2YERWY_cjs.a.droppedEvents,1,{...c(a),reason:p.dropped,"docx_editor.version":chunk6BTB6Y3H_cjs.b});return}let b=Z(o),A=b?D(b):null;if(A===null||b==="performance"&&A===0)return;M(),ee(t,p.event,c(a));let x=l>0?J(o):null,O=x?y.get(x):void 0;if(O){o.kind==="collab.participants"?O.latest=o:O.repeated+=1;return}let _=o.kind==="session"&&o.phase==="end";if(!w(o,b,A)){_&&u();return}let L=I(F(o,E),t,a);if(g(L),_&&u(),x){let R=setTimeout(()=>v(x),l);R.unref?.(),y.set(x,{event:L,repeated:0,timer:R});}}function d(o){return Object.freeze({id:"telemetry",onDiagnostic:r=>i(r,o),wantsDiagnostic:r=>!T&&(r==="session"?D("sessions")!==null:r!=="performance"||(D("performance")??0)>0&&t.consented())})}async function u(){for(let o of [...y.keys()])v(o);await t.flush();}return {module:d({}),moduleFor(o){return d(chunk7SYLCJFQ_cjs.c(o,"moduleFor"))},record:i,flush:u,async shutdown(){for(let o of [...y.keys()])v(o);T=true,k(),await t.shutdown();}}}function F(e,t){if(t||e.kind!=="document.open"||e.outcome!=="opened"||!("shape"in e))return e;let{shape:m,...n}=e;return n}function I(e,t,m){let n="code"in e?e.code:void 0;return {...e,...n?core.explainDiagnostic(n):{},engineVersion:chunk6BTB6Y3H_cjs.b,resource:t.resource,attributes:m}}function ee(e,t,m){let n={metric:(c,f,l={})=>e.metric(c,f,{...l,...m,"docx_editor.version":chunk6BTB6Y3H_cjs.b})};switch(t.kind){case "document.open":t.outcome==="opened"?(n.metric(chunkAL2YERWY_cjs.a.documentOpens,1,{outcome:"opened",code:"none",cause:"none",part:"none"}),n.metric(chunkAL2YERWY_cjs.a.documentOpenDuration,t.durationMs/1e3)):n.metric(chunkAL2YERWY_cjs.a.documentOpens,1,{outcome:"rejected",code:t.code,cause:t.cause??"none",part:t.part??"none"});return;case "edit.rejected":n.metric(chunkAL2YERWY_cjs.a.editRejections,1,{code:t.code,cause:t.cause??"none",origin:t.origin,tracked:t.tracked});return;case "review.skipped":n.metric(chunkAL2YERWY_cjs.a.reviewSkipped,t.count,{code:t.code,cause:t.cause??"none",operation:t.operation??"none",change_kind:t.changeKind??"none"});return;case "font.failed":n.metric(chunkAL2YERWY_cjs.a.fontFailures,1,{code:t.code});return;case "history":n.metric(chunkAL2YERWY_cjs.a.historyDiagnostics,1,{code:t.code});return;case "custom-node":n.metric(chunkAL2YERWY_cjs.a.customNodeDiagnostics,1,{code:t.code});return;case "collab.status":n.metric(chunkAL2YERWY_cjs.a.collabClientStatusTransitions,1,{status:t.status,code:t.code??"none"}),t.connectMs!==void 0&&n.metric(chunkAL2YERWY_cjs.a.collabClientConnectDuration,t.connectMs/1e3);return;case "collab.participants":n.metric(chunkAL2YERWY_cjs.a.collabClientParticipants,t.count);return;case "performance":{let c=t.pagesBucket??"unknown";N(n,chunkAL2YERWY_cjs.a.editorInteractionDuration,c,t.interaction),N(n,chunkAL2YERWY_cjs.a.editorLayoutDuration,c,t.layout),N(n,chunkAL2YERWY_cjs.a.editorPaintDuration,c,t.paint);return}case "document.save":n.metric(chunkAL2YERWY_cjs.a.documentSaves,1,{outcome:t.outcome,code:t.outcome==="rejected"?t.code:"none"}),t.outcome==="saved"&&n.metric(chunkAL2YERWY_cjs.a.documentSaveDuration,t.durationMs/1e3);return;case "session":te(n,t);}}function te(e,t){if(t.phase==="start"){e.metric(chunkAL2YERWY_cjs.a.editorSessions,1,{phase:"start"});return}if(t.phase==="end"){e.metric(chunkAL2YERWY_cjs.a.editorSessions,1,{phase:"end"}),e.metric(chunkAL2YERWY_cjs.a.editorSessionDuration,t.durationMs/1e3);return}t.activeMs>0&&e.metric(chunkAL2YERWY_cjs.a.editorActiveTime,t.activeMs/1e3);for(let[m,n]of [["input",t.inputEdits],["command",t.commandEdits],["automation",t.automationEdits]])n>0&&e.metric(chunkAL2YERWY_cjs.a.edits,n,{origin:m});}function N(e,t,m,n){if(!n)return;let c=t.buckets??[];n.bands.forEach((f,l)=>{let g=c[l],E=l===0?0:c[l-1],y=g===void 0?Math.max(n.maxMs/1e3,E+1e-6):(E+g)/2;for(let S=0;S<f;S+=1)e.metric(t,y,{pages:m});});}var oe=new TextEncoder,ne=e=>oe.encode(e).byteLength;function re(e){let t=Math.min(50,Math.max(1,Math.floor(e.batchSize??20))),m=e.flushIntervalMs??1e4,n=[],c,f,l=new Set,g=i=>{try{e.onError?.(i);}catch{}},E=(i,d)=>{if(typeof fetch!="function")return;let u=fetch(e.url,{method:"POST",headers:{"Content-Type":"application/json",...e.headers},body:i,keepalive:d}).then(o=>{if(!o.ok)throw new Error(`telemetry endpoint answered ${o.status}`)}).catch(g).finally(()=>l.delete(u));l.add(u);},y=i=>{let d=globalThis.navigator;if(!e.headers&&typeof d?.sendBeacon=="function")try{if(d.sendBeacon(e.url,new Blob([i],{type:"application/json"})))return}catch{}E(i,true);},S=i=>{let d=Math.min(t,n.length);for(;d>0;){let u=JSON.stringify(n.slice(0,d)),o=ne(u);if(o<=i)return {body:u,bytes:o,count:d};if(d===1)return null;d=Math.ceil(d/2);}return null},k=0,T=false,D=i=>{if(c!==void 0&&clearTimeout(c),c=void 0,f&&!f.consented()){n=[];return}(!i||!T)&&(k=0),T=i;let d=0;for(;n.length;){let u=S(61440-(i?k:0));if(!u){d+=i?n.length:1,n=i?[]:n.slice(1);continue}n=n.slice(u.count),i?(k+=u.bytes,y(u.body)):E(u.body,false);}d&&g(new Error(`telemetry dropped ${d} events that did not fit`));},h=()=>{let i=f?.pageClosing()??false;return i||(T=false),i},w=globalThis.document,M=()=>{w?.visibilityState==="visible"&&(T=false);},v=()=>w?.removeEventListener?.("visibilitychange",M);return {name:"http",attach(i){f=i,w?.addEventListener?.("visibilitychange",M);},event(i){n.push(i),n.length>=t?D(h()):c===void 0&&(c=setTimeout(()=>D(h()),m),c.unref?.());},async flush(){D(h()),await Promise.all(l);},async shutdown(){v(),D(h()),await Promise.all(l);}}}function ie(e={}){return ()=>{let t=globalThis;if(!t.window||!t.document||!t.navigator||t.navigator.globalPrivacyControl===true||t.navigator.doNotTrack==="1")return false;if(!e.optOutKey)return true;try{return globalThis.localStorage?.getItem(e.optOutKey)!=="off"}catch{return true}}}Object.defineProperty(exports,"ENGINE_VERSION",{enumerable:true,get:function(){return chunk6BTB6Y3H_cjs.b}});Object.defineProperty(exports,"MAX_METRIC_LABEL_VALUES",{enumerable:true,get:function(){return chunk6BTB6Y3H_cjs.a}});Object.defineProperty(exports,"METRICS",{enumerable:true,get:function(){return chunkAL2YERWY_cjs.a}});Object.defineProperty(exports,"prometheusName",{enumerable:true,get:function(){return chunkAL2YERWY_cjs.b}});Object.defineProperty(exports,"callbackExporter",{enumerable:true,get:function(){return chunkE2HR5DHZ_cjs.a}});Object.defineProperty(exports,"consoleExporter",{enumerable:true,get:function(){return chunkE2HR5DHZ_cjs.e}});Object.defineProperty(exports,"failureCount",{enumerable:true,get:function(){return chunkE2HR5DHZ_cjs.d}});Object.defineProperty(exports,"isFailure",{enumerable:true,get:function(){return chunkE2HR5DHZ_cjs.c}});Object.defineProperty(exports,"memoryExporter",{enumerable:true,get:function(){return chunkE2HR5DHZ_cjs.f}});Object.defineProperty(exports,"explainDiagnostic",{enumerable:true,get:function(){return core.explainDiagnostic}});exports.browserPrivacySignals=ie;exports.createTelemetry=Q;exports.httpExporter=re;
@@ -0,0 +1,355 @@
1
+ /**
2
+ * Anonymous diagnostics, metrics, and error reporting for editors and collaboration servers.
3
+ *
4
+ * - `@docx-editor.dev/telemetry`: {@link createTelemetry} for editors, the console, callback,
5
+ * memory, and HTTP exporters, and {@link browserPrivacySignals}.
6
+ * - `@docx-editor.dev/telemetry/ingest`: a route that receives telemetry from `httpExporter`
7
+ * and forwards it to a log service.
8
+ * - `@docx-editor.dev/telemetry/otel`: OpenTelemetry metrics and traces through your SDK.
9
+ * - `@docx-editor.dev/telemetry/sentry`: Sentry issues and breadcrumbs.
10
+ * - `@docx-editor.dev/telemetry/prometheus`: a `/metrics` endpoint.
11
+ * - `@docx-editor.dev/telemetry/server`: room telemetry for any Yjs server.
12
+ * - `@docx-editor.dev/telemetry/hocuspocus`: room telemetry for Hocuspocus in one call.
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+ import { EditorModule, EditorDiagnostic } from '@docx-editor.dev/core';
17
+ export { ByteSizeBucket, CollaborationDiagnosticCode, CollaborationParticipantsDiagnostic, CollaborationStatusDiagnostic, CountBucket, CustomNodeDiagnosticCode, CustomNodeDiagnosticEvent, DiagnosticBase, DiagnosticCode, DiagnosticErrorName, DiagnosticExplanation, DiagnosticFailureDetail, DocumentOpenDiagnostic, DocumentOpenDiagnosticCode, DocumentPartKind, DocumentSaveDiagnostic, DocumentSaveDiagnosticCode, DocumentShape, EditDiagnosticCode, EditRejectedDiagnostic, EditRejectionOrigin, EditorDiagnostic, EditorDiagnosticKind, FailureStage, FontDiagnosticCode, FontFailedDiagnostic, FormattingChangeKind, HistoryDiagnosticCode, HistoryDiagnosticEvent, PerformanceDiagnostic, PerformanceDiagnosticCode, ReviewChangeKind, ReviewDiagnosticCode, ReviewOperation, ReviewSkippedDiagnostic, SessionActivity, SessionDiagnostic, TimingSummary, explainDiagnostic } from '@docx-editor.dev/core';
18
+ import { A as Attributes, T as TelemetryPipelineOptions, a as TelemetryEvent, b as TelemetryExporter, M as MetricRecord } from './pipeline-CCq2Ng0B.cjs';
19
+ export { c as AttributeValue, E as ENGINE_VERSION, d as MAX_METRIC_LABEL_VALUES, e as METRICS, f as MetricDefinition, g as MetricId, h as MetricKind, i as TelemetryEventContext, j as TelemetryExporterContext, p as prometheusName } from './pipeline-CCq2Ng0B.cjs';
20
+
21
+ /**
22
+ * The areas you can switch on. Each one selects diagnostic kinds:
23
+ *
24
+ * - `documentOpen`: documents opened and refused (`document.open`).
25
+ * - `editing`: edits and commands refused while changes are not tracked (`edit.rejected`).
26
+ * - `trackedChanges`: refusals while suggesting, refusals about tracking itself, and skipped
27
+ * tracked changes
28
+ * (`edit.rejected` with `tracked: true`, `review.skipped`).
29
+ * - `fonts`: fonts that failed and were substituted (`font.failed`).
30
+ * - `history`: undo steps that did not group as the gesture intended (`history`).
31
+ * - `customNodes`: custom node payloads that could not be read (`custom-node`).
32
+ * - `collaborationClient`: collaboration status changes and room size (`collab.*`).
33
+ * - `performance`: how fast the editor responds: key presses and clicks to the next frame,
34
+ * and engine layout and paint times, as one summary per minute of use (`performance`).
35
+ * - `documentSave`: calls to `save()` that returned the document or failed (`document.save`).
36
+ * - `sessions`: when each editor starts and ends, how long it is used, and how many edits it
37
+ * commits, as one summary per minute of use (`session`).
38
+ *
39
+ * @public
40
+ */
41
+ type TelemetryArea = 'documentOpen' | 'documentSave' | 'sessions' | 'editing' | 'trackedChanges' | 'fonts' | 'history' | 'customNodes' | 'collaborationClient' | 'performance';
42
+ /** Options for one area. @public */
43
+ interface TelemetryAreaOptions {
44
+ /**
45
+ * Share of events to send, from 0 to 1. Metrics count every event, except that a
46
+ * `performance` rate of 0 turns performance off, metrics included. Defaults to 1.
47
+ * Each event is sampled on its own, except in `performance` and `sessions`: there the choice
48
+ * is made once for each editor, which sends all of its summaries or none.
49
+ */
50
+ readonly sampleRate?: number;
51
+ }
52
+ /** Which areas to record. An area that is absent or `false` records nothing. @public */
53
+ type TelemetryInclude = {
54
+ readonly [A in TelemetryArea]?: boolean | TelemetryAreaOptions;
55
+ };
56
+ /** Options for {@link createTelemetry}. @public */
57
+ interface TelemetryOptions extends TelemetryPipelineOptions {
58
+ /**
59
+ * The areas to record. Omitted, every area is on.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * include: { documentOpen: true, trackedChanges: true, editing: { sampleRate: 0.2 } }
64
+ * ```
65
+ */
66
+ readonly include?: TelemetryInclude;
67
+ /**
68
+ * Identical refusals inside this window are sent once, then summarized with `repeated`.
69
+ * Holding Backspace on a locked drawing is one event, not hundreds. Defaults to 10 000 ms.
70
+ * Set 0 to send every event.
71
+ */
72
+ readonly dedupeWindowMs?: number;
73
+ /**
74
+ * How much of an opened document's shape to send. `'basic'`, the default, sends the size and
75
+ * page buckets. `'detailed'` also sends part, paragraph, review item, and embedded font
76
+ * buckets: useful to group open times, but several counts together describe a document more
77
+ * closely, so ask for it only when you need it.
78
+ */
79
+ readonly documentShape?: 'basic' | 'detailed';
80
+ /**
81
+ * Your identifiers on every event, such as a tenant or a pseudonymous user id. A function is
82
+ * read at each event, so it can follow sign-in and tenant switches. Events only: to label
83
+ * metrics with a key, list it in `metricLabels`. For values that belong to one editor, such as
84
+ * its room or document, use {@link Telemetry.moduleFor}.
85
+ *
86
+ * These values are yours and go only to your exporters. Keys follow attribute rules: letters,
87
+ * digits, `_`, `.`, and `-`, up to 64 characters. Values are strings, numbers, or booleans;
88
+ * strings are cut at 256 characters.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * attributes: () => ({ 'tenant.id': session.tenantId, 'user.id': session.pseudonymousId })
93
+ * ```
94
+ */
95
+ readonly attributes?: Attributes | (() => Attributes);
96
+ /**
97
+ * Which events go to your exporters, after the privacy check and sampling. Metrics still count
98
+ * every event. A filter that throws drops the event.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * // Failures and performance summaries only: no event for an open that succeeded.
103
+ * filter: (event) => isFailure(event) || event.kind === 'performance'
104
+ * ```
105
+ */
106
+ readonly filter?: (event: TelemetryEvent) => boolean;
107
+ }
108
+ /** A configured telemetry client. Create it once, outside any component. @public */
109
+ interface Telemetry {
110
+ /**
111
+ * Register this in the editor's `modules`. It observes every editor created with it, from
112
+ * the first open on.
113
+ *
114
+ * @example
115
+ * ```tsx
116
+ * const MODULES = [reviewModule(), telemetry.module];
117
+ * <DocxEditor document={bytes} modules={MODULES} />;
118
+ * ```
119
+ */
120
+ readonly module: EditorModule;
121
+ /**
122
+ * A module for one editor, whose events also carry `attributes`, such as its collaboration
123
+ * room, document, or workspace. Create one for each editor, and keep it stable across renders.
124
+ * Use it in place of `module`, not next to it.
125
+ *
126
+ * @example
127
+ * ```tsx
128
+ * const modules = useMemo(
129
+ * () => [reviewModule(), telemetry.moduleFor({ 'room.id': roomId, 'document.id': documentId })],
130
+ * [roomId, documentId]
131
+ * );
132
+ * ```
133
+ */
134
+ moduleFor(attributes: Attributes): EditorModule;
135
+ /**
136
+ * Record one diagnostic yourself, for example from a runtime you created without `module`.
137
+ * Use `module` or `record`, not both, or every event is recorded twice. `attributes` are added
138
+ * to this event, as `moduleFor` adds them.
139
+ */
140
+ record(event: EditorDiagnostic, attributes?: Attributes): void;
141
+ /** Send summaries of open deduplication windows, then flush every exporter. */
142
+ flush(): Promise<void>;
143
+ /** Flush, then stop. Later events are dropped. */
144
+ shutdown(): Promise<void>;
145
+ }
146
+ /**
147
+ * Create a telemetry client for editors.
148
+ *
149
+ * Nothing is sent unless you pass an exporter. With `consent`, nothing is sent while it is
150
+ * false; without it, events are sent. Every event is anonymous: codes, enums, and bucketed
151
+ * numbers only.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * import { createTelemetry, consoleExporter } from '@docx-editor.dev/telemetry';
156
+ *
157
+ * export const telemetry = createTelemetry({
158
+ * exporters: [consoleExporter()],
159
+ * include: { documentOpen: true, trackedChanges: true },
160
+ * });
161
+ * ```
162
+ *
163
+ * @public
164
+ */
165
+ declare function createTelemetry(options: TelemetryOptions): Telemetry;
166
+
167
+ /**
168
+ * Sending telemetry to your own HTTP endpoint from a browser, and reading the browser's privacy
169
+ * signals. No dependency: `fetch`, `sendBeacon`, and `localStorage` only.
170
+ */
171
+
172
+ /** Options for {@link httpExporter}. @public */
173
+ interface HttpExporterOptions {
174
+ /**
175
+ * Where to POST. Use a route on your own origin, which then forwards to your log service, so
176
+ * the service's token never reaches the browser. See `@docx-editor.dev/telemetry/ingest`.
177
+ */
178
+ readonly url: string;
179
+ /**
180
+ * Events per request, from 1 to 50. Defaults to 20. A full batch is sent at once. The limit
181
+ * matches the default `maxEvents` of `telemetryRoute`, which refuses a longer list.
182
+ */
183
+ readonly batchSize?: number;
184
+ /** How long an event waits for its batch to fill, in milliseconds. Defaults to 10 000. */
185
+ readonly flushIntervalMs?: number;
186
+ /**
187
+ * Extra request headers. A browser cannot set headers on `sendBeacon`, so with headers, the
188
+ * batches sent while the page closes use `fetch` with `keepalive` instead.
189
+ */
190
+ readonly headers?: Readonly<Record<string, string>>;
191
+ /**
192
+ * Called when a request fails, or when batches are dropped because the page closed with more
193
+ * than the browser lets it send. Telemetry never throws into your app.
194
+ */
195
+ readonly onError?: (error: unknown) => void;
196
+ }
197
+ /**
198
+ * Batch events and POST them as a JSON array to your own endpoint.
199
+ *
200
+ * While the page is open, events wait up to `flushIntervalMs` for a batch of `batchSize`, and
201
+ * each batch goes out with an ordinary `fetch`; so does a `telemetry.flush()` you call. When the
202
+ * page is hidden or closes, everything waiting goes out with `sendBeacon`. A closing page may
203
+ * send only about 64 KiB in all, so delivery while closing is best effort: batches beyond that
204
+ * are dropped and reported to `onError`. If consent is withdrawn, events still waiting are
205
+ * discarded.
206
+ *
207
+ * @example
208
+ * ```ts
209
+ * import { createTelemetry, httpExporter } from '@docx-editor.dev/telemetry';
210
+ *
211
+ * export const telemetry = createTelemetry({
212
+ * exporters: [httpExporter({ url: '/api/telemetry' })],
213
+ * include: { documentOpen: true, editing: true, trackedChanges: true, performance: true },
214
+ * });
215
+ * ```
216
+ *
217
+ * @public
218
+ */
219
+ declare function httpExporter(options: HttpExporterOptions): TelemetryExporter;
220
+ /** Options for {@link browserPrivacySignals}. @public */
221
+ interface BrowserPrivacySignalsOptions {
222
+ /**
223
+ * A `localStorage` key that opts the visitor out when its value is `off`, such as one your
224
+ * privacy settings page writes. Omit it to read only the browser signals.
225
+ */
226
+ readonly optOutKey?: string;
227
+ }
228
+ /**
229
+ * A `consent` function that follows the browser's privacy signals: it returns `false` when the
230
+ * visitor sends Global Privacy Control or Do Not Track, or when `optOutKey` is set to `off`.
231
+ * Outside a browser page, such as during server rendering, it returns `false`. Combine it with
232
+ * your consent manager when you have one.
233
+ *
234
+ * @example
235
+ * ```ts
236
+ * import { browserPrivacySignals, createTelemetry } from '@docx-editor.dev/telemetry';
237
+ *
238
+ * const privacy = browserPrivacySignals({ optOutKey: 'my-app:telemetry' });
239
+ * export const telemetry = createTelemetry({
240
+ * exporters: [],
241
+ * consent: () => privacy() && consentManager.analytics,
242
+ * });
243
+ * ```
244
+ *
245
+ * @public
246
+ */
247
+ declare function browserPrivacySignals(options?: BrowserPrivacySignalsOptions): () => boolean;
248
+
249
+ /**
250
+ * Send each event to your own function: Datadog RUM, PostHog, a logger, or an API route.
251
+ *
252
+ * `onMetric` receives every measurement. A performance summary arrives as one histogram sample
253
+ * for each timing in it, at the middle of the timing's band, so the bucket counts are exact.
254
+ * To chart percentiles, read `p50Ms` and `p95Ms` from the `performance` event instead.
255
+ *
256
+ * @example
257
+ * ```ts
258
+ * callbackExporter((event) =>
259
+ * isFailure(event)
260
+ * ? datadogRum.addError(new Error(event.kind), event)
261
+ * : datadogRum.addAction(event.kind, event)
262
+ * );
263
+ * ```
264
+ *
265
+ * @public
266
+ */
267
+ declare function callbackExporter(onEvent: (event: TelemetryEvent) => void, onMetric?: (record: MetricRecord) => void): TelemetryExporter;
268
+ /** Options for {@link consoleExporter}. @public */
269
+ interface ConsoleExporterOptions {
270
+ /** Also print every metric measurement. Defaults to `false`. */
271
+ readonly metrics?: boolean;
272
+ /** Where to print. Defaults to the global `console`. */
273
+ readonly console?: Pick<Console, 'info' | 'warn'>;
274
+ }
275
+ /**
276
+ * Whether an event is a failure: a refused open, a refused edit other than an expected one
277
+ * (a stale range, a read-only document, protected content, a session or refresh not ready
278
+ * yet), an unreadable payload, a font that did not load, or a collaboration error. A missing
279
+ * font that was substituted and a tracked change someone else already reviewed are not. Use it
280
+ * in a callback to send failures as errors and the rest as actions or logs.
281
+ *
282
+ * @example
283
+ * ```ts
284
+ * callbackExporter((event) => {
285
+ * const code = 'code' in event ? event.code : undefined;
286
+ * if (isFailure(event)) myLogger.error(event.kind, { code, hint: event.hint });
287
+ * else myLogger.info(event.kind);
288
+ * });
289
+ * ```
290
+ *
291
+ * A deduplication summary returns `false`: it repeats an event already reported. To total
292
+ * failures with summaries included, use {@link failureCount}.
293
+ *
294
+ * @public
295
+ */
296
+ declare function isFailure(event: TelemetryEvent): boolean;
297
+ /**
298
+ * How many failures an event adds to a failure total: `0` when it is not a failure, `1` for a
299
+ * failure, and `repeated` for a deduplication summary of one. The sum over every event a
300
+ * client sends equals the number of failures the editor reported, which {@link isFailure}
301
+ * alone undercounts. Sampling still applies: a sampled area sends a share of its events.
302
+ *
303
+ * @example
304
+ * ```ts
305
+ * let failures = 0;
306
+ * callbackExporter((event) => {
307
+ * failures += failureCount(event);
308
+ * });
309
+ * ```
310
+ *
311
+ * @public
312
+ */
313
+ declare function failureCount(event: TelemetryEvent): number;
314
+ /**
315
+ * Print each event with its hint and docs link. Use it during development to see why a
316
+ * document did not open or why a tracked change was refused.
317
+ *
318
+ * @example
319
+ * ```ts
320
+ * createTelemetry({ exporters: [consoleExporter()] });
321
+ * // An agent deletes a picture while changes are tracked:
322
+ * // [docx-editor] edit refused: edit.tracked-operation-unsupported (automation, tracked)
323
+ * // This editing API operation cannot be recorded as a tracked change, so nothing was ...
324
+ * // https://docx-editor.dev/docs/2.x/guides/diagnostics#edit-tracked-operation-unsupported
325
+ * ```
326
+ *
327
+ * @public
328
+ */
329
+ declare function consoleExporter(options?: ConsoleExporterOptions): TelemetryExporter;
330
+ /** An exporter that keeps what it receives. Returned by {@link memoryExporter}. @public */
331
+ interface MemoryExporter extends TelemetryExporter {
332
+ readonly events: readonly TelemetryEvent[];
333
+ readonly metrics: readonly MetricRecord[];
334
+ /** Forget everything received so far. */
335
+ clear(): void;
336
+ }
337
+ /**
338
+ * Keep events and metrics in memory, for tests in your own suite.
339
+ *
340
+ * @example
341
+ * ```ts
342
+ * const memory = memoryExporter();
343
+ * const telemetry = createTelemetry({ exporters: [memory] });
344
+ * // ...open a file that fails...
345
+ * expect(memory.events).toContainEqual(
346
+ * expect.objectContaining({ code: 'open.missing-main-document' })
347
+ * );
348
+ * ```
349
+ *
350
+ * @public
351
+ */
352
+ declare function memoryExporter(): MemoryExporter;
353
+
354
+ export { Attributes, MetricRecord, TelemetryEvent, TelemetryExporter, TelemetryPipelineOptions, browserPrivacySignals, callbackExporter, consoleExporter, createTelemetry, failureCount, httpExporter, isFailure, memoryExporter };
355
+ export type { BrowserPrivacySignalsOptions, ConsoleExporterOptions, HttpExporterOptions, MemoryExporter, Telemetry, TelemetryArea, TelemetryAreaOptions, TelemetryInclude, TelemetryOptions };