Skip to content

Commit c336a24

Browse files
committed
fix: audit health data and update health notification
This commit adds a fallback mechanism for health tracking initialization using `localStorage` to enable fast boot-time access to the health tracking flag (`Phoenix.healthTrackingDisabled`) before persistent preferences are fully loaded. Key changes: - `Phoenix.healthTrackingDisabled` is initialized from `localStorage` at boot. - Introduced `Phoenix._setHealthTrackingDisabled()` to update both the in-memory flag and localStorage. - Added a safety check in `HealthDataManager.js` to detect mismatches between boot-time localStorage and actual persisted preferences. If a mismatch is detected, a one-time `"disableErr"` metric is raised to track potential early metric leakage. - Updated Bugsnag and metrics initialization logic to respect `Phoenix.healthTrackingDisabled`. - Updated the health data opt-out UI and strings to reflect necessary/always-collected data. - Updated test runner to mimic `localStorage`-based health flag boot logic. This ensures **eventual consistency** between boot-time behavior and persisted user preferences, while maintaining **privacy guarantees** by deferring any personal or anonymous health reporting until health tracking is explicitly enabled.
1 parent 4cc755b commit c336a24

File tree

8 files changed

+57
-14
lines changed

8 files changed

+57
-14
lines changed

src/extensions/default/HealthData/HealthDataManager.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,19 @@ define(function (require, exports, module) {
109109
isPowerUserFn: isPowerUser
110110
});
111111
healthDataDisabled = !prefs.get("healthDataTracking");
112+
if (healthDataDisabled && !Phoenix.healthTrackingDisabled) {
113+
// Phoenix.healthTrackingDisabled is initialized at boot using localStorage.
114+
// However, there's a theoretical edge case where the browser may have cleared
115+
// localStorage, causing a mismatch between the boot-time flag and the actual
116+
// persisted user preference.
117+
//
118+
// This means we might unintentionally log some metrics during the short window
119+
// before the real preference is loaded and applied.
120+
//
121+
// To track this discrepancy, we emit a one-time metric just before disabling tracking,
122+
// so we’re aware of this inconsistency and can address it if needed.
123+
Metrics.countEvent(Metrics.PLATFORM, "metricBoot", "disableErr");
124+
}
112125
Metrics.setDisabled(healthDataDisabled);
113126
SendToAnalytics.sendPlatformMetrics();
114127
SendToAnalytics.sendThemesMetrics();

src/extensions/default/HealthData/SendToAnalytics.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,16 @@
2121
*
2222
*/
2323

24-
/*global Phoenix*/
24+
/*global AppConfig*/
2525
define(function (require, exports, module) {
2626
const Metrics = brackets.getModule("utils/Metrics"),
2727
PreferencesManager = brackets.getModule("preferences/PreferencesManager"),
2828
PerfUtils = brackets.getModule("utils/PerfUtils"),
2929
NodeUtils = brackets.getModule("utils/NodeUtils"),
3030
themesPref = PreferencesManager.getExtensionPrefs("themes");
3131

32+
const BugsnagPerformance = window.BugsnagPerformance;
33+
3234
const PLATFORM = Metrics.EVENT_TYPE.PLATFORM,
3335
PERFORMANCE = Metrics.EVENT_TYPE.PERFORMANCE,
3436
STORAGE = Metrics.EVENT_TYPE.STORAGE;
@@ -133,17 +135,34 @@ define(function (require, exports, module) {
133135
_sendStorageMetrics();
134136
}
135137

138+
let bugsnagPerformanceInited = false;
139+
function _initBugsnagPerformance() {
140+
bugsnagPerformanceInited = true;
141+
BugsnagPerformance.start({
142+
apiKey: '94ef94f4daf871ca0f2fc912c6d4764d',
143+
appVersion: AppConfig.version,
144+
releaseStage: window.__TAURI__ ?
145+
`tauri-${AppConfig.config.bugsnagEnv}-${Phoenix.platform}` : AppConfig.config.bugsnagEnv,
146+
autoInstrumentRouteChanges: false,
147+
autoInstrumentNetworkRequests: false,
148+
autoInstrumentFullPageLoads: false
149+
});
150+
}
151+
136152
function _bugsnagPerformance(key, valueMs) {
137-
if(Metrics.isDisabled() || !window.BugsnagPerformance || Phoenix.isTestWindow){
153+
if(Metrics.isDisabled() || !BugsnagPerformance || Phoenix.isTestWindow){
138154
return;
139155
}
156+
if(!bugsnagPerformanceInited) {
157+
_initBugsnagPerformance();
158+
}
140159
let activityStartTime = new Date();
141160
let activityEndTime = new Date(activityStartTime.getTime() + valueMs);
142-
window.BugsnagPerformance
161+
BugsnagPerformance
143162
.startSpan(key, { startTime: activityStartTime })
144163
.end(activityEndTime);
145164
}
146-
165+
147166
// Performance
148167
function sendStartupPerformanceMetrics() {
149168
const healthReport = PerfUtils.getHealthReport();

src/extensions/default/HealthData/htmlContent/healthdata-preview-dialog.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ <h1 class="dialog-title">{{Strings.HEALTH_DATA_PREVIEW}}</h1>
99
<input type="checkbox" data-target="hdPref" {{#hdPref}}checked{{/hdPref}} />
1010
{{Strings.HEALTH_DATA_DO_TRACK}}
1111
</label>
12+
<div style="display: flex; align-items: flex-start; gap: 8px; padding: 8px 10px; border-left: 3px solid #aaa; font-size: 13px; color: #666; margin: 8px 0;">
13+
<span style="flex-shrink: 0;">ℹ️</span>
14+
<span>{{Strings.HEALTH_DATA_PREVIEW_NECESSARY}}</span>
15+
</div>
1216
</div>
1317
<div class="dialog-message preview-content-container">
1418
<p class="preview-content">{{{content}}}</p>

src/index.html

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@
341341
} else if (navigator.platform && navigator.platform.indexOf("Linux") >= 0) {
342342
platform = "linux";
343343
}
344+
// window.Phoenix should never be reassigned as code may cache references to this object.
344345
window.Phoenix = {
345346
PHOENIX_INSTANCE_ID: "PH-" + Math.round( Math.random()*1000000000000),
346347
browser: getBrowserDetails(),
@@ -372,6 +373,12 @@
372373
Phoenix.isSupportedBrowser = Phoenix.isNativeApp ||
373374
(Phoenix.browser.isDeskTop && ("serviceWorker" in navigator));
374375
window.testEnvironment = window.Phoenix.isTestWindow;
376+
const healthDisabled = localStorage.getItem("PH_HEALTH_DISABLED");
377+
window.Phoenix.healthTrackingDisabled = (healthDisabled === "true");
378+
window.Phoenix._setHealthTrackingDisabled = function (isDisabled) {
379+
window.Phoenix.healthTrackingDisabled = isDisabled;
380+
localStorage.setItem("PH_HEALTH_DISABLED", String(isDisabled));
381+
};
375382

376383
// now setup PhoenixBaseURL, which if of the form https://phcode.dev/ or tauri://localhost/
377384
const url = new URL(window.location.href);
@@ -394,14 +401,6 @@
394401
<script src="appConfig.js"></script>
395402
<script type="module">
396403
import BugsnagPerformance from "./thirdparty/bugsnag-performance.min.js";
397-
BugsnagPerformance.start({
398-
apiKey: '94ef94f4daf871ca0f2fc912c6d4764d',
399-
appVersion: AppConfig.version,
400-
releaseStage: window.__TAURI__ ? `tauri-${AppConfig.config.bugsnagEnv}-${Phoenix.platform}` : AppConfig.config.bugsnagEnv,
401-
autoInstrumentRouteChanges: false,
402-
autoInstrumentNetworkRequests: false,
403-
autoInstrumentFullPageLoads: false
404-
});
405404
window.BugsnagPerformance = BugsnagPerformance;
406405
</script>
407406
<script src="loggerSetup.js"></script>

src/loggerSetup.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
}
4141
}
4242
const urlParams = new URLSearchParams(window.location.search || "");
43-
const isBugsnagEnabled = (!window.testEnvironment && isBugsnagLoggableURL);
43+
const isBugsnagEnabled = (!window.testEnvironment && isBugsnagLoggableURL && !Phoenix.healthTrackingDisabled);
4444
const MAX_ERR_SENT_RESET_INTERVAL = 60000,
4545
MAX_ERR_SENT_FIRST_MINUTE = 10,
4646
MAX_ERR_ALLOWED_IN_MINUTE = 2;

src/nls/root/strings.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,7 @@ define({
891891
"HEALTH_DATA_NOTIFICATION_MESSAGE": "{APP_NAME} <strong>does not collect or process any personally identifiable information</strong>, but <strong>collects anonymous usage statistics</strong> to guard your privacy. Anonymous data is exempt from GDPR/CCPA notification requirements, but we believe you need to have a choice to opt out of anonymous data collection as well.<br><br> You can see your data or <strong>choose not to share any anonymous data</strong> by selecting <strong>Help > Health Report</strong>. These <strong>anonymous</strong> app usage statistics and error reports helps prioritize features, find bugs, and spot usability issues for improving your experience with {APP_NAME}. Without this data, we would not know what features it is worth building for you! <br>",
892892
"HEALTH_DATA_PREVIEW": "{APP_NAME} Health Report",
893893
"HEALTH_DATA_PREVIEW_INTRO": "<p>{APP_NAME} <strong>does not collect or process any personally identifiable information</strong>, but <strong>collects anonymous usage statistics</strong> to guard your privacy. These <strong>anonymous</strong> app usage statistics and error reports helps prioritize features, find bugs, and spot usability issues for improving your experience with {APP_NAME}.</p> <p>Below is a preview of the data that will be sent in your next Health Report <em>if</em> it is enabled. (Also see developer console for error logs marked 'Caught Critical error'.)</p>",
894+
"HEALTH_DATA_PREVIEW_NECESSARY": "Security/app updates, analytics library initialization, user counts, and usage time are always anonymously collected as necessary app health indicators. These are aggregate statistics and no personal data is sent/logged.",
894895

895896
// extensions/default/InlineTimingFunctionEditor
896897
"INLINE_TIMING_EDITOR_TIME": "Time",
@@ -1556,4 +1557,4 @@ define({
15561557

15571558
// surveys
15581559
"SURVEY_TITLE_VOTE_FOR_FEATURES_YOU_WANT": "Vote for the features you want to see next!"
1559-
});
1560+
});

src/utils/Metrics.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ define(function (require, exports, module) {
403403
}
404404

405405
function setDisabled(shouldDisable) {
406+
Phoenix._setHealthTrackingDisabled(shouldDisable);
406407
disabled = shouldDisable;
407408
}
408409

test/SpecRunner.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,12 @@
293293
Phoenix.isSupportedBrowser = Phoenix.isNativeApp ||
294294
(Phoenix.browser.isDeskTop && ("serviceWorker" in navigator));
295295
window.testEnvironment = window.Phoenix.isTestWindow;
296+
const healthDisabled = localStorage.getItem("PH_HEALTH_DISABLED");
297+
window.Phoenix.healthTrackingDisabled = (healthDisabled === "true");
298+
window.Phoenix._setHealthTrackingDisabled = function (isDisabled) {
299+
window.Phoenix.healthTrackingDisabled = isDisabled;
300+
localStorage.setItem("PH_HEALTH_DISABLED", String(isDisabled));
301+
};
296302

297303
// now setup PhoenixBaseURL, which if of the form https://phcode.dev/ or tauri://localhost/
298304
const url = new URL(window.location.href);

0 commit comments

Comments
 (0)