fix: prevent ServiceWorker registration errors in VS Code webview

- Add ServiceWorker prevention utility to block registration attempts in webview context
- Initialize prevention before any third-party libraries load
- Update PostHog configuration to disable features that might use ServiceWorkers
- Add comprehensive tests for ServiceWorker prevention functionality

Fixes #9903
This commit is contained in:
Roo Code 2025-12-07 17:30:17 +00:00
parent 946fd0390f
commit a4496a6d85
4 changed files with 241 additions and 0 deletions

View file

@ -6,6 +6,11 @@ import App from "./App"
import "../node_modules/@vscode/codicons/dist/codicon.css"
import { getHighlighter } from "./utils/highlighter"
import { initServiceWorkerPrevention } from "./utils/serviceWorkerPrevention"
// Prevent ServiceWorker registration in VS Code webview environment
// This must be done before any third-party libraries are initialized
initServiceWorkerPrevention()
// Initialize Shiki early to hide initialization latency (async)
getHighlighter().catch((error: Error) => console.error("Failed to initialize Shiki highlighter:", error))

View file

@ -20,6 +20,11 @@ class TelemetryClient {
capture_pageview: false,
capture_pageleave: false,
autocapture: false,
// Disable features that might use ServiceWorkers
disable_persistence: false, // Keep localStorage persistence but avoid advanced features
bootstrap: {}, // Avoid bootstrapping that might trigger ServiceWorker
// Disable session recording which might use ServiceWorkers
disable_session_recording: true,
})
} else {
TelemetryClient.telemetryEnabled = false

View file

@ -0,0 +1,167 @@
import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"
import {
isVSCodeWebview,
disableServiceWorkerRegistration,
initServiceWorkerPrevention,
} from "../serviceWorkerPrevention"
describe("serviceWorkerPrevention", () => {
let originalAcquireVsCodeApi: any
beforeEach(() => {
// Store original values
originalAcquireVsCodeApi = (global as any).acquireVsCodeApi
// Mock navigator.serviceWorker for each test
const mockServiceWorker = {
register: vi.fn(),
getRegistration: vi.fn(),
getRegistrations: vi.fn(),
ready: Promise.resolve(),
controller: null,
oncontrollerchange: null,
onmessage: null,
onmessageerror: null,
}
// Use vi.stubGlobal for navigator
vi.stubGlobal("navigator", {
serviceWorker: mockServiceWorker,
})
})
afterEach(() => {
// Restore original values
vi.unstubAllGlobals()
if (originalAcquireVsCodeApi !== undefined) {
;(global as any).acquireVsCodeApi = originalAcquireVsCodeApi
} else {
delete (global as any).acquireVsCodeApi
}
})
describe("isVSCodeWebview", () => {
it("should return true when acquireVsCodeApi is defined", () => {
// Mock VS Code webview environment
;(global as any).acquireVsCodeApi = vi.fn()
expect(isVSCodeWebview()).toBe(true)
})
it("should return false when acquireVsCodeApi is undefined", () => {
// Ensure it's undefined
delete (global as any).acquireVsCodeApi
expect(isVSCodeWebview()).toBe(false)
})
})
describe("disableServiceWorkerRegistration", () => {
it("should override navigator.serviceWorker.register in VS Code webview", async () => {
// Mock VS Code webview environment
;(global as any).acquireVsCodeApi = vi.fn()
disableServiceWorkerRegistration()
// Try to register a service worker
await expect(navigator.serviceWorker.register("/sw.js")).rejects.toThrow(
"ServiceWorker registration is not allowed in VS Code webview",
)
})
it("should not override navigator.serviceWorker when not in VS Code webview", () => {
// Ensure we're not in VS Code webview
delete (global as any).acquireVsCodeApi
const originalRegister = navigator.serviceWorker.register
disableServiceWorkerRegistration()
// Should still be the original function
expect(navigator.serviceWorker.register).toBe(originalRegister)
})
it("should provide mock getRegistration that returns undefined", async () => {
// Mock VS Code webview environment
;(global as any).acquireVsCodeApi = vi.fn()
disableServiceWorkerRegistration()
const registration = await navigator.serviceWorker.getRegistration()
expect(registration).toBeUndefined()
})
it("should provide mock getRegistrations that returns empty array", async () => {
// Mock VS Code webview environment
;(global as any).acquireVsCodeApi = vi.fn()
disableServiceWorkerRegistration()
const registrations = await navigator.serviceWorker.getRegistrations()
expect(registrations).toEqual([])
})
it("should handle missing navigator.serviceWorker gracefully", () => {
// Mock VS Code webview environment
;(global as any).acquireVsCodeApi = vi.fn()
// Remove serviceWorker from navigator
vi.stubGlobal("navigator", {})
// Should not throw
expect(() => disableServiceWorkerRegistration()).not.toThrow()
})
})
describe("initServiceWorkerPrevention", () => {
it("should initialize without throwing errors", () => {
// Mock VS Code webview environment
;(global as any).acquireVsCodeApi = vi.fn()
expect(() => initServiceWorkerPrevention()).not.toThrow()
})
it("should not throw even if prevention fails", () => {
// Mock VS Code webview environment
;(global as any).acquireVsCodeApi = vi.fn()
// Mock console.error
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
// Create a scenario where defineProperty might fail
// by stubbing a read-only navigator
const readOnlyNavigator = {
get serviceWorker() {
throw new Error("Cannot access serviceWorker")
},
}
vi.stubGlobal("navigator", readOnlyNavigator)
// Should not throw even if there's an error
expect(() => initServiceWorkerPrevention()).not.toThrow()
consoleErrorSpy.mockRestore()
})
it("should successfully prevent ServiceWorker registration when called", async () => {
// Mock VS Code webview environment
;(global as any).acquireVsCodeApi = vi.fn()
// Mock console.warn to check for warning message
const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
initServiceWorkerPrevention()
// Try to register a service worker
await expect(navigator.serviceWorker.register("/sw.js")).rejects.toThrow()
// Should have logged a warning
expect(consoleWarnSpy).toHaveBeenCalledWith(
"ServiceWorker registration blocked in VS Code webview environment",
)
consoleWarnSpy.mockRestore()
})
})
})

View file

@ -0,0 +1,64 @@
/**
* Prevents ServiceWorker registration in VS Code webview environment
* This is necessary because VS Code webviews have security restrictions
* that don't allow ServiceWorker registration, which can cause errors
* with third-party libraries like PostHog that attempt to use them.
*/
/**
* Checks if we're running in a VS Code webview environment
*/
export function isVSCodeWebview(): boolean {
// Check for VS Code webview-specific global
return typeof acquireVsCodeApi !== "undefined"
}
/**
* Disables ServiceWorker registration by overriding the navigator.serviceWorker API
* This prevents third-party libraries from attempting to register ServiceWorkers
* which would fail in the VS Code webview environment.
*/
export function disableServiceWorkerRegistration(): void {
if (!isVSCodeWebview()) {
// Only disable in VS Code webview environment
return
}
// Override navigator.serviceWorker to prevent registration attempts
if (typeof navigator !== "undefined" && "serviceWorker" in navigator) {
// Create a mock serviceWorker object that prevents registration
const mockServiceWorker = {
register: () => {
console.warn("ServiceWorker registration blocked in VS Code webview environment")
return Promise.reject(new Error("ServiceWorker registration is not allowed in VS Code webview"))
},
getRegistration: () => Promise.resolve(undefined),
getRegistrations: () => Promise.resolve([]),
ready: new Promise(() => {
// Never resolves, as no ServiceWorker will be ready
}),
controller: null,
oncontrollerchange: null,
onmessage: null,
onmessageerror: null,
}
// Override the serviceWorker property
Object.defineProperty(navigator, "serviceWorker", {
get: () => mockServiceWorker,
configurable: true,
})
}
}
/**
* Initialize ServiceWorker prevention
* This should be called as early as possible in the application lifecycle
*/
export function initServiceWorkerPrevention(): void {
try {
disableServiceWorkerRegistration()
} catch (error) {
console.error("Failed to initialize ServiceWorker prevention:", error)
}
}