88 * - Outside Thunderbird (public website): telemetry behaves as before (allowed).
99 * - Inside Thunderbird, on an add-on (moz-extension) page: read the pref via the
1010 * `browser.Telemetry` experiment API.
11- * - Inside Thunderbird but the experiment API is unavailable / errors: fail
12- * closed (no telemetry).
11+ * - Inside Thunderbird on the hosted Send dashboard (send.js runs as a web page
12+ * with no experiment API): ask the background script for the pref over the
13+ * token-bridge (issue #952).
14+ * - Inside Thunderbird but neither the experiment API nor the bridge answers:
15+ * fail closed (no telemetry).
1316 *
1417 * The `browser.Telemetry` namespace is provided by the add-on's experiment API
1518 * (packages/addon/public/api/Telemetry). The frontend types come from
1619 * @types /firefox-webext-browser, which does not know about custom experiment
1720 * APIs, hence the `@ts-ignore` accesses below (matching extension-store.ts).
1821 */
1922
23+ import {
24+ GET_TELEMETRY_STATE ,
25+ TELEMETRY_STATE_CHANGED ,
26+ TELEMETRY_STATE_RESPONSE ,
27+ } from './const' ;
28+
29+ // How long to wait for the token-bridge to answer before failing closed. The
30+ // bridge content script is injected at document_start, so it is normally
31+ // present before this runs; the timeout covers the case where it is not (e.g.
32+ // the add-on isn't installed).
33+ const BRIDGE_TIMEOUT_MS = 2000 ;
34+
2035const isInsideThunderbird = ( ) : boolean =>
2136 typeof navigator !== 'undefined' &&
2237 navigator . userAgent . includes ( 'Thunderbird' ) ;
@@ -35,6 +50,41 @@ const getTelemetryApi = (): any => {
3550 return undefined ;
3651} ;
3752
53+ /**
54+ * Requests the Thunderbird telemetry pref from the background script via the
55+ * token-bridge, for contexts (the hosted Send dashboard) that lack the
56+ * `browser.Telemetry` experiment API. Resolves false (fail closed) if the
57+ * bridge does not answer within the timeout.
58+ */
59+ function requestTelemetryStateViaBridge ( ) : Promise < boolean > {
60+ if ( typeof window === 'undefined' ) {
61+ return Promise . resolve ( false ) ;
62+ }
63+
64+ return new Promise ( ( resolve ) => {
65+ const cleanup = ( ) => {
66+ clearTimeout ( timer ) ;
67+ window . removeEventListener ( 'message' , handler ) ;
68+ } ;
69+
70+ const timer = setTimeout ( ( ) => {
71+ // Bridge absent or unresponsive — fail closed.
72+ cleanup ( ) ;
73+ resolve ( false ) ;
74+ } , BRIDGE_TIMEOUT_MS ) ;
75+
76+ const handler = ( event : MessageEvent ) => {
77+ if ( event . data ?. type === TELEMETRY_STATE_RESPONSE ) {
78+ cleanup ( ) ;
79+ resolve ( Boolean ( event . data . enabled ) ) ;
80+ }
81+ } ;
82+
83+ window . addEventListener ( 'message' , handler ) ;
84+ window . postMessage ( { type : GET_TELEMETRY_STATE } , window . location . origin ) ;
85+ } ) ;
86+ }
87+
3888/**
3989 * Resolves whether telemetry (Sentry + PostHog) is allowed to run.
4090 * Fails closed (false) when inside Thunderbird and the pref cannot be read.
@@ -46,16 +96,18 @@ export async function isTelemetryAllowed(): Promise<boolean> {
4696 }
4797
4898 const api = getTelemetryApi ( ) ;
49- if ( ! api ?. getUploadEnabled ) {
50- // Inside Thunderbird but the experiment API is unavailable: fail closed.
51- return false ;
99+ if ( api ?. getUploadEnabled ) {
100+ // moz-extension add-on page: read the pref directly.
101+ try {
102+ return Boolean ( await api . getUploadEnabled ( ) ) ;
103+ } catch {
104+ return false ;
105+ }
52106 }
53107
54- try {
55- return Boolean ( await api . getUploadEnabled ( ) ) ;
56- } catch {
57- return false ;
58- }
108+ // Inside Thunderbird without the direct experiment API (the hosted Send
109+ // dashboard runs as a web page): ask the background via the token-bridge.
110+ return requestTelemetryStateViaBridge ( ) ;
59111}
60112
61113/**
@@ -67,18 +119,32 @@ export function onTelemetryChanged(
67119 cb : ( enabled : boolean ) => void
68120) : ( ) => void {
69121 const api = getTelemetryApi ( ) ;
70- if ( ! api ?. onChanged ?. addListener ) {
71- return ( ) => { } ;
122+ if ( api ?. onChanged ?. addListener ) {
123+ // moz-extension add-on page: observe the pref directly.
124+ const listener = ( enabled : boolean ) => cb ( Boolean ( enabled ) ) ;
125+ api . onChanged . addListener ( listener ) ;
126+
127+ return ( ) => {
128+ try {
129+ api . onChanged . removeListener ?.( listener ) ;
130+ } catch {
131+ // ignore
132+ }
133+ } ;
72134 }
73135
74- const listener = ( enabled : boolean ) => cb ( Boolean ( enabled ) ) ;
75- api . onChanged . addListener ( listener ) ;
136+ // Inside Thunderbird without the direct API (the hosted Send dashboard): the
137+ // background pushes pref changes through the token-bridge as window messages.
138+ if ( isInsideThunderbird ( ) && typeof window !== 'undefined' ) {
139+ const handler = ( event : MessageEvent ) => {
140+ if ( event . data ?. type === TELEMETRY_STATE_CHANGED ) {
141+ cb ( Boolean ( event . data . enabled ) ) ;
142+ }
143+ } ;
144+ window . addEventListener ( 'message' , handler ) ;
145+ return ( ) => window . removeEventListener ( 'message' , handler ) ;
146+ }
76147
77- return ( ) => {
78- try {
79- api . onChanged . removeListener ?.( listener ) ;
80- } catch {
81- // ignore
82- }
83- } ;
148+ // Public website (outside Thunderbird): nothing to observe.
149+ return ( ) => { } ;
84150}
0 commit comments