From 1549544e017ee5186d5fc029e5c82543d05f2e84 Mon Sep 17 00:00:00 2001 From: aorlov Date: Fri, 13 Feb 2026 00:09:06 +0100 Subject: [PATCH 1/3] feat: update telemetry configuration to prioritize root licenseKey - Modified the telemetry initialization logic to resolve the licenseKey from the root level, allowing for a fallback to the deprecated telemetry.licenseKey. - Updated tests to verify the behavior of the new licenseKey resolution and ensure compatibility with existing configurations. - Marked the telemetry.licenseKey in the EditorConfig as deprecated, encouraging the use of the root-level licenseKey instead. --- .../src/core/Editor.telemetry.test.ts | 58 ++++++++++++++++++- packages/super-editor/src/core/Editor.ts | 10 +++- .../src/core/types/EditorConfig.ts | 4 ++ packages/superdoc/src/SuperDoc.vue | 1 + packages/superdoc/src/core/SuperDoc.js | 6 +- packages/superdoc/src/core/types/index.js | 6 ++ packages/superdoc/src/telemetry.test.ts | 34 ++++++++++- 7 files changed, 110 insertions(+), 9 deletions(-) diff --git a/packages/super-editor/src/core/Editor.telemetry.test.ts b/packages/super-editor/src/core/Editor.telemetry.test.ts index 9a9fcc2e7d..4f27803d80 100644 --- a/packages/super-editor/src/core/Editor.telemetry.test.ts +++ b/packages/super-editor/src/core/Editor.telemetry.test.ts @@ -12,7 +12,12 @@ vi.mock('@superdoc/common', () => ({ // Test the telemetry initialization logic in isolation // This mirrors the #initTelemetry method in Editor.ts function initTelemetry(options: { - telemetry?: { enabled: boolean; endpoint?: string; metadata?: Record } | null; + telemetry?: { + enabled: boolean; + endpoint?: string; + metadata?: Record; + licenseKey?: string | null; + } | null; licenseKey?: string; }): Telemetry | null { const { telemetry: telemetryConfig, licenseKey } = options; @@ -22,11 +27,15 @@ function initTelemetry(options: { return null; } + // Root-level licenseKey has a priority; fall back to deprecated telemetry.licenseKey + const resolvedLicenseKey = + licenseKey !== undefined ? licenseKey : (telemetryConfig.licenseKey ?? COMMUNITY_LICENSE_KEY); + try { return new Telemetry({ enabled: true, endpoint: telemetryConfig.endpoint, - licenseKey: licenseKey === undefined ? COMMUNITY_LICENSE_KEY : licenseKey, + licenseKey: resolvedLicenseKey, metadata: telemetryConfig.metadata, }); } catch { @@ -122,6 +131,51 @@ describe('Editor Telemetry Integration', () => { }); }); + describe('deprecated telemetry.licenseKey', () => { + it('uses telemetry.licenseKey when root licenseKey is not provided', () => { + const result = initTelemetry({ + telemetry: { enabled: true, licenseKey: 'deprecated-key' }, + }); + + expect(result).not.toBeNull(); + expect(Telemetry).toHaveBeenCalledWith({ + enabled: true, + endpoint: undefined, + licenseKey: 'deprecated-key', + metadata: undefined, + }); + }); + + it('root licenseKey wins over telemetry.licenseKey', () => { + const result = initTelemetry({ + telemetry: { enabled: true, licenseKey: 'deprecated-key' }, + licenseKey: 'root-key', + }); + + expect(result).not.toBeNull(); + expect(Telemetry).toHaveBeenCalledWith({ + enabled: true, + endpoint: undefined, + licenseKey: 'root-key', + metadata: undefined, + }); + }); + + it('falls back to COMMUNITY_LICENSE_KEY when both are absent', () => { + const result = initTelemetry({ + telemetry: { enabled: true }, + }); + + expect(result).not.toBeNull(); + expect(Telemetry).toHaveBeenCalledWith({ + enabled: true, + endpoint: undefined, + licenseKey: 'community-and-eval-agplv3', + metadata: undefined, + }); + }); + }); + describe('telemetry with custom endpoint', () => { it('passes custom endpoint to Telemetry', () => { const customEndpoint = 'https://custom.telemetry.com/v1/events'; diff --git a/packages/super-editor/src/core/Editor.ts b/packages/super-editor/src/core/Editor.ts index 2175be551e..4e18b8770c 100644 --- a/packages/super-editor/src/core/Editor.ts +++ b/packages/super-editor/src/core/Editor.ts @@ -339,8 +339,8 @@ export class Editor extends EventEmitter { // header/footer editors may have parent(main) editor set parentEditor: null, - // License key (defaults to community license) - licenseKey: COMMUNITY_LICENSE_KEY, + // License key (resolved in #initTelemetry; undefined means "not explicitly set") + licenseKey: undefined, // Telemetry configuration telemetry: { enabled: true }, @@ -493,11 +493,15 @@ export class Editor extends EventEmitter { return; } + // Root-level licenseKey has a priority; fall back to deprecated telemetry.licenseKey + const resolvedLicenseKey = + licenseKey !== undefined ? licenseKey : (telemetryConfig.licenseKey ?? COMMUNITY_LICENSE_KEY); + try { this.#telemetry = new Telemetry({ enabled: true, endpoint: telemetryConfig.endpoint, - licenseKey: licenseKey === undefined ? COMMUNITY_LICENSE_KEY : licenseKey, + licenseKey: resolvedLicenseKey, metadata: telemetryConfig.metadata, }); console.debug('[super-editor] Telemetry: enabled'); diff --git a/packages/super-editor/src/core/types/EditorConfig.ts b/packages/super-editor/src/core/types/EditorConfig.ts index 478b022514..50bbbb56d3 100644 --- a/packages/super-editor/src/core/types/EditorConfig.ts +++ b/packages/super-editor/src/core/types/EditorConfig.ts @@ -432,5 +432,9 @@ export interface EditorOptions { endpoint?: string; /** Custom metadata to include with telemetry events (optional) */ metadata?: Record; + /** + * @deprecated Use root-level `licenseKey` instead. If both are provided, root-level has priority. + */ + licenseKey?: string | null; } | null; } diff --git a/packages/superdoc/src/SuperDoc.vue b/packages/superdoc/src/SuperDoc.vue index 7a8966dd24..379f415302 100644 --- a/packages/superdoc/src/SuperDoc.vue +++ b/packages/superdoc/src/SuperDoc.vue @@ -527,6 +527,7 @@ const editorOptions = (doc) => { enabled: true, endpoint: proxy.$superdoc.config.telemetry?.endpoint, metadata: proxy.$superdoc.config.telemetry?.metadata, + licenseKey: proxy.$superdoc.config.telemetry?.licenseKey, } : null, }; diff --git a/packages/superdoc/src/core/SuperDoc.js b/packages/superdoc/src/core/SuperDoc.js index 1e1ac28d03..770a7f59cf 100644 --- a/packages/superdoc/src/core/SuperDoc.js +++ b/packages/superdoc/src/core/SuperDoc.js @@ -4,7 +4,7 @@ import { EventEmitter } from 'eventemitter3'; import { v4 as uuidv4 } from 'uuid'; import { HocuspocusProviderWebsocket } from '@hocuspocus/provider'; -import { DOCX, PDF, HTML, COMMUNITY_LICENSE_KEY } from '@superdoc/common'; +import { DOCX, PDF, HTML } from '@superdoc/common'; import { SuperToolbar, createZip } from '@superdoc/super-editor'; import { SuperComments } from '../components/CommentsLayer/commentsList/super-comments-list.js'; import { createSuperdocVueApp } from './create-app.js'; @@ -79,8 +79,8 @@ export class SuperDoc extends EventEmitter { modules: {}, // Optional: Modules to load. Use modules.ai.{your_key} to pass in your key permissionResolver: null, // Optional: Override for permission checks - // License key for organization identification (defaults to community key) - licenseKey: COMMUNITY_LICENSE_KEY, + // License key (resolved downstream; undefined means "not explicitly set") + licenseKey: undefined, // Telemetry settings telemetry: { enabled: true }, diff --git a/packages/superdoc/src/core/types/index.js b/packages/superdoc/src/core/types/index.js index 88d857adc2..6074268928 100644 --- a/packages/superdoc/src/core/types/index.js +++ b/packages/superdoc/src/core/types/index.js @@ -193,6 +193,12 @@ * @property {boolean} [isDebug=false] Whether to enable debug mode * @property {ViewOptions} [viewOptions] Document view options (OOXML ST_View compatible) * @property {string} [cspNonce] Content Security Policy nonce for dynamically injected styles + * @property {string} [licenseKey] License key for organization identification + * @property {{ enabled: boolean, endpoint?: string, metadata?: Record, licenseKey?: string }} [telemetry] Telemetry configuration + * @property {boolean} telemetry.enabled Whether telemetry is enabled + * @property {string} [telemetry.endpoint] Custom telemetry endpoint + * @property {Record} [telemetry.metadata] Custom metadata to include with telemetry events + * @property {string} [telemetry.licenseKey] @deprecated Use root-level `licenseKey` instead */ export {}; diff --git a/packages/superdoc/src/telemetry.test.ts b/packages/superdoc/src/telemetry.test.ts index 8830cb3064..7d67e70cf4 100644 --- a/packages/superdoc/src/telemetry.test.ts +++ b/packages/superdoc/src/telemetry.test.ts @@ -7,10 +7,16 @@ function transformTelemetryConfig(superdocConfig: { enabled?: boolean; endpoint?: string; metadata?: Record; + licenseKey?: string | null; } | null; licenseKey?: string | null; }): { - telemetry: { enabled: boolean; endpoint?: string; metadata?: Record } | null; + telemetry: { + enabled: boolean; + endpoint?: string; + metadata?: Record; + licenseKey?: string | null; + } | null; licenseKey?: string | null; } { return { @@ -20,6 +26,7 @@ function transformTelemetryConfig(superdocConfig: { enabled: true, endpoint: superdocConfig.telemetry?.endpoint, metadata: superdocConfig.telemetry?.metadata, + licenseKey: superdocConfig.telemetry?.licenseKey, } : null, }; @@ -144,6 +151,31 @@ describe('SuperDoc Telemetry Configuration', () => { }); }); + describe('deprecated telemetry.licenseKey passthrough', () => { + it('passes deprecated telemetry.licenseKey to editor config', () => { + const result = transformTelemetryConfig({ + telemetry: { enabled: true, licenseKey: 'deprecated-key' }, + }); + + expect(result.telemetry).toEqual({ + enabled: true, + endpoint: undefined, + metadata: undefined, + licenseKey: 'deprecated-key', + }); + }); + + it('passes both root licenseKey and deprecated telemetry.licenseKey', () => { + const result = transformTelemetryConfig({ + telemetry: { enabled: true, licenseKey: 'deprecated-key' }, + licenseKey: 'root-key', + }); + + expect(result.licenseKey).toBe('root-key'); + expect(result.telemetry?.licenseKey).toBe('deprecated-key'); + }); + }); + describe('full configuration flow', () => { it('transforms complete SuperDoc config to Editor config', () => { const superdocConfig = { From 11a0f20d5b4a073c85ccc21304334b3053f23d3f Mon Sep 17 00:00:00 2001 From: aorlov Date: Fri, 13 Feb 2026 00:13:42 +0100 Subject: [PATCH 2/3] docs: enhance telemetry documentation with self-reporting capabilities --- apps/docs/resources/telemetry.mdx | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/apps/docs/resources/telemetry.mdx b/apps/docs/resources/telemetry.mdx index 80ffdb7b45..971d1d7115 100644 --- a/apps/docs/resources/telemetry.mdx +++ b/apps/docs/resources/telemetry.mdx @@ -91,6 +91,10 @@ const editor = await Editor.open(file, { Custom key-value pairs included with every event. Use this to attach your own identifiers (customer ID, environment, etc.). + + **Deprecated.** Use the root-level `licenseKey` instead. If both are provided, the root-level key takes priority. + + ### Disabling telemetry Set `enabled: false` to turn telemetry off entirely: @@ -107,6 +111,28 @@ const superdoc = new SuperDoc({ No network requests will be made. Document metadata (GUID and timestamp) is still generated locally so files export correctly. +### Self-reporting + +You can route telemetry through your own server using `telemetry.endpoint`. SuperDoc sends the same JSON payload to whatever URL you provide — inspect it, store it, forward it, or drop it. + +```javascript +const superdoc = new SuperDoc({ + selector: '#editor', + document: yourFile, + licenseKey: 'your-license-key', + telemetry: { + enabled: true, + endpoint: 'https://your-server.com/superdoc/telemetry', + }, +}); +``` + +Your server receives a `POST` with the payload described in [Payload example](#payload-example). From there you can aggregate usage on your side — count document opens per customer, track adoption across teams, or feed the data into your own billing pipeline. + + +A dedicated self-reporting API with dashboards and usage summaries is under development and will be available soon. In the meantime, proxying via `telemetry.endpoint` gives you full control over the raw data. + + ## License key The license key identifies your organization. It's sent as an `X-License-Key` header with every telemetry request. From 1fb6a52bc4dce112a033457eadb1cdf0f6c532ff Mon Sep 17 00:00:00 2001 From: aorlov Date: Fri, 13 Feb 2026 00:44:16 +0100 Subject: [PATCH 3/3] docs: update self-reporting API availability date in telemetry documentation --- apps/docs/resources/telemetry.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/resources/telemetry.mdx b/apps/docs/resources/telemetry.mdx index 971d1d7115..05aa09d0c7 100644 --- a/apps/docs/resources/telemetry.mdx +++ b/apps/docs/resources/telemetry.mdx @@ -130,7 +130,7 @@ const superdoc = new SuperDoc({ Your server receives a `POST` with the payload described in [Payload example](#payload-example). From there you can aggregate usage on your side — count document opens per customer, track adoption across teams, or feed the data into your own billing pipeline. -A dedicated self-reporting API with dashboards and usage summaries is under development and will be available soon. In the meantime, proxying via `telemetry.endpoint` gives you full control over the raw data. +A dedicated self-reporting API with dashboards and usage summaries is under development and will be available by Feb 28, 2026. In the meantime, proxying via `telemetry.endpoint` gives you full control over the raw data. ## License key