b5648eaabd
Replace window.sendRuntimeMessage existence check with URL-based detection. The sendRuntimeMessage function exists in both extension and non-extension contexts (e.g., downloaded HTML artifacts), causing incorrect behavior. Changes: - Add window.__isExtensionContext() helper to RuntimeMessageBridge that checks: - chrome-extension:// URLs (Chrome) - moz-extension:// URLs (Firefox) - about:srcdoc (sandbox iframes) - Update all runtime providers to use __isExtensionContext() instead: - FileDownloadRuntimeProvider: Correctly falls back to browser download - ConsoleRuntimeProvider: Only sends messages in extension context - ArtifactsRuntimeProvider: Properly detects offline/read-only mode This fixes the issue where downloaded HTML artifacts incorrectly try to communicate with the extension when opened from disk.
97 lines
3.1 KiB
TypeScript
97 lines
3.1 KiB
TypeScript
/**
|
|
* Generates sendRuntimeMessage() function for injection into execution contexts.
|
|
* Provides unified messaging API that works in both sandbox iframe and user script contexts.
|
|
*/
|
|
|
|
export type MessageType = "request-response" | "fire-and-forget";
|
|
|
|
export interface RuntimeMessageBridgeOptions {
|
|
context: "sandbox-iframe" | "user-script";
|
|
sandboxId: string;
|
|
}
|
|
|
|
// biome-ignore lint/complexity/noStaticOnlyClass: fine
|
|
export class RuntimeMessageBridge {
|
|
/**
|
|
* Generate sendRuntimeMessage() function as injectable string.
|
|
* Returns the function source code to be injected into target context.
|
|
*/
|
|
static generateBridgeCode(options: RuntimeMessageBridgeOptions): string {
|
|
if (options.context === "sandbox-iframe") {
|
|
return RuntimeMessageBridge.generateSandboxBridge(options.sandboxId);
|
|
} else {
|
|
return RuntimeMessageBridge.generateUserScriptBridge(options.sandboxId);
|
|
}
|
|
}
|
|
|
|
private static generateSandboxBridge(sandboxId: string): string {
|
|
// Returns stringified function that uses window.parent.postMessage
|
|
return `
|
|
window.__completionCallbacks = [];
|
|
// Check if we're in an extension context by examining the URL
|
|
window.__isExtensionContext = () => {
|
|
const url = window.location.href;
|
|
return url.startsWith('chrome-extension://') ||
|
|
url.startsWith('moz-extension://') ||
|
|
url === 'about:srcdoc';
|
|
};
|
|
window.sendRuntimeMessage = async (message) => {
|
|
const messageId = 'msg_' + Date.now() + '_' + Math.random().toString(36).substring(2, 9);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const handler = (e) => {
|
|
if (e.data.type === 'runtime-response' && e.data.messageId === messageId) {
|
|
window.removeEventListener('message', handler);
|
|
if (e.data.success) {
|
|
resolve(e.data);
|
|
} else {
|
|
reject(new Error(e.data.error || 'Operation failed'));
|
|
}
|
|
}
|
|
};
|
|
|
|
window.addEventListener('message', handler);
|
|
|
|
window.parent.postMessage({
|
|
...message,
|
|
sandboxId: ${JSON.stringify(sandboxId)},
|
|
messageId: messageId
|
|
}, '*');
|
|
|
|
// Timeout after 30s
|
|
setTimeout(() => {
|
|
window.removeEventListener('message', handler);
|
|
reject(new Error('Runtime message timeout'));
|
|
}, 30000);
|
|
});
|
|
};
|
|
window.onCompleted = (callback) => {
|
|
window.__completionCallbacks.push(callback);
|
|
};
|
|
`.trim();
|
|
}
|
|
|
|
private static generateUserScriptBridge(sandboxId: string): string {
|
|
// Returns stringified function that uses chrome.runtime.sendMessage
|
|
return `
|
|
window.__completionCallbacks = [];
|
|
// Check if we're in an extension context by examining the URL
|
|
window.__isExtensionContext = () => {
|
|
const url = window.location.href;
|
|
return url.startsWith('chrome-extension://') ||
|
|
url.startsWith('moz-extension://') ||
|
|
url === 'about:srcdoc';
|
|
};
|
|
window.sendRuntimeMessage = async (message) => {
|
|
return await chrome.runtime.sendMessage({
|
|
...message,
|
|
sandboxId: ${JSON.stringify(sandboxId)}
|
|
});
|
|
};
|
|
window.onCompleted = (callback) => {
|
|
window.__completionCallbacks.push(callback);
|
|
};
|
|
`.trim();
|
|
}
|
|
}
|