TidGi-Desktop/docs/Analytics.md
2026-05-01 18:32:48 +08:00

3.5 KiB

Analytics in TidGi Desktop

This document explains how analytics works in TidGi Desktop, what is intentionally tracked, what is intentionally blocked, and how plugin authors should integrate with the analytics service.

Goals

TidGi uses analytics to understand coarse product usage without collecting user content.

The current design goals are:

  • Keep all network delivery in the Electron main process
  • Never expose the analytics API key to renderer or plugin code
  • Track only coarse, product-level behavior
  • Reject free-form content, note text, file paths, URLs, tokens, and other sensitive payloads
  • Give plugin authors a stable way to emit custom product events without bypassing privacy guardrails

Architecture

  1. Renderer code and TiddlyWiki plugins call the TidGi analytics service through the existing IPC proxy layer
  2. The analytics service runs in the main process
  3. The main process sends events to Rybbit over HTTP

Delivery model

  • Analytics is enabled only when all of the following are true:
    • analyticsEnabled preference is true
    • analyticsHost is configured
    • analyticsSiteId is configured
    • a main-process-only analytics API key is configured
  • Unsent events may be queued temporarily in memory
  • The queue is dropped immediately when the user disables analytics
  • The first-run disclosure is tracked separately from normal product events

Privacy constraints

TidGi analytics must never contain:

  • note titles or note bodies
  • workspace names
  • filesystem paths
  • raw URLs
  • access tokens, OAuth codes, cookies, or API keys
  • free-form user text copied from the UI

If a proposed event depends on any of the above, do not add it to analytics.

Built-in events

The application currently emits built-in events such as:

  • app.launched
  • analytics.disclosure_dismissed
  • workspace.created
  • workspace.activated
  • preferences.analytics_updated
  • settings.opened
  • theme.changed
  • deep_link.opened
  • sync.triggered
  • sync.completed
  • sync.failed
  • updater.check_started
  • updater.update_available
  • updater.update_not_available
  • updater.check_failed
  • error.report_requested

Built-in events use an allowlist. For each built-in event, only explicitly approved property keys are retained.

That allowlist lives in src/services/analytics/index.ts.

Plugin-defined custom events

Plugin authors must not emit arbitrary event names through the low-level built-in event contract.

Instead, plugins should call:

await window.service.analytics.trackPluginEvent(pluginId, eventName, properties);

or inside a TiddlyWiki plugin:

await $tw.tidgi.service.analytics.trackPluginEvent(pluginId, eventName, properties);

Final event name format

The service converts plugin calls into this final event name:

plugin.<pluginId>.<eventName>

Example:

await window.service.analytics.trackPluginEvent('kanban-board', 'card_created', {
  source: 'toolbar',
  has_due_date: true,
});

Emits:

plugin.kanban-board.card_created

Validation rules

Plugin event names are intentionally restricted.

  • pluginId must match: ^[a-z0-9]+(?:[-_][a-z0-9]+)*$
  • eventName must match: ^[a-z0-9]+(?:[-_][a-z0-9]+)*$
  • Property keys must match: ^[a-z][a-z0-9_]{0,39}$
  • Property values must be string | number | boolean
  • String values are truncated to 120 characters

If pluginId or eventName is invalid, the event is rejected.

If all properties are invalid, the event is still allowed to be sent without properties.