fix: comprehensive extension context invalidated handling
Some checks failed
CI — Test & Build / Test & Build (push) Has been cancelled
Some checks failed
CI — Test & Build / Test & Build (push) Has been cancelled
This commit is contained in:
@@ -204,7 +204,7 @@ export default defineContentScript({
|
||||
|
||||
function isExtensionValid(): boolean {
|
||||
try {
|
||||
return !!chrome.runtime?.id;
|
||||
return typeof chrome !== 'undefined' && !!chrome.runtime?.id;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
@@ -223,6 +223,25 @@ export default defineContentScript({
|
||||
setTimeout(() => toast.remove(), 4000);
|
||||
}
|
||||
|
||||
// ─── Safe chrome.runtime.sendMessage wrapper ──────────────────────────────
|
||||
|
||||
async function safeSendMessage(payload: Record<string, unknown>): Promise<unknown> {
|
||||
if (!isExtensionValid()) {
|
||||
showErrorToast('LexAI was updated — please refresh this page.');
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return await chrome.runtime.sendMessage(payload);
|
||||
} catch (err) {
|
||||
if (String(err).includes('Extension context invalidated') ||
|
||||
String(err).includes('message channel closed')) {
|
||||
showErrorToast('LexAI was updated — please refresh this page.');
|
||||
hideToolbar();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── LLM call ─────────────────────────────────────────────────────────────
|
||||
|
||||
async function runAction(action: string) {
|
||||
@@ -251,13 +270,15 @@ export default defineContentScript({
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
const response = await safeSendMessage({
|
||||
type: 'ANALYZE_TEXT',
|
||||
payload: { text: textToProcess, action },
|
||||
});
|
||||
}) as { error?: string; result?: string } | null;
|
||||
|
||||
hideToolbar();
|
||||
|
||||
if (response === null) return; // safeSendMessage already handled the error
|
||||
|
||||
if (response?.error) {
|
||||
showModal(`❌ ${response.error}`, null);
|
||||
} else {
|
||||
@@ -265,8 +286,9 @@ export default defineContentScript({
|
||||
}
|
||||
} catch (err) {
|
||||
hideToolbar();
|
||||
if (String(err).includes('Extension context invalidated')) {
|
||||
showErrorToast('LexAI was updated. Please refresh the page.');
|
||||
if (String(err).includes('Extension context invalidated') ||
|
||||
String(err).includes('message channel closed')) {
|
||||
showErrorToast('LexAI was updated — please refresh this page.');
|
||||
} else {
|
||||
showModal(`❌ Error: ${String(err)}`, null);
|
||||
}
|
||||
@@ -440,6 +462,25 @@ export default defineContentScript({
|
||||
document.body.appendChild(modal);
|
||||
}
|
||||
|
||||
// ─── Global extension context error handlers ──────────────────────────────
|
||||
|
||||
window.addEventListener('error', (e) => {
|
||||
if (e.message?.includes('Extension context invalidated')) {
|
||||
e.preventDefault(); // suppress console error
|
||||
hideToolbar();
|
||||
showErrorToast('LexAI was updated — please refresh this page.');
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', (e) => {
|
||||
if (String(e.reason)?.includes('Extension context invalidated') ||
|
||||
String(e.reason)?.includes('message channel closed')) {
|
||||
e.preventDefault();
|
||||
hideToolbar();
|
||||
showErrorToast('LexAI was updated — please refresh this page.');
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Event listeners ──────────────────────────────────────────────────────
|
||||
|
||||
document.addEventListener('mouseup', (e) => {
|
||||
|
||||
@@ -151,6 +151,30 @@ const styles = {
|
||||
} as React.CSSProperties,
|
||||
};
|
||||
|
||||
// ─── Safe chrome storage wrappers ────────────────────────────────────────────
|
||||
|
||||
function safeStorageGet(keys: string[], callback: (result: Record<string, string>) => void) {
|
||||
try {
|
||||
if (typeof chrome === 'undefined' || !chrome.runtime?.id) return;
|
||||
chrome.storage.local.get(keys, callback);
|
||||
} catch (err) {
|
||||
console.warn('LexAI: Extension context invalidated', err);
|
||||
}
|
||||
}
|
||||
|
||||
function safeStorageSet(data: Record<string, string>, callback?: () => void) {
|
||||
try {
|
||||
if (typeof chrome === 'undefined' || !chrome.runtime?.id) return;
|
||||
if (callback) {
|
||||
chrome.storage.local.set(data, callback);
|
||||
} else {
|
||||
chrome.storage.local.set(data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('LexAI: Extension context invalidated', err);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Component ────────────────────────────────────────────────────────────────
|
||||
|
||||
function OptionsPage() {
|
||||
@@ -162,7 +186,7 @@ function OptionsPage() {
|
||||
const apiKeyRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
chrome.storage.local.get(["provider", "apiKey", "model"], (result: Record<string, string>) => {
|
||||
safeStorageGet(["provider", "apiKey", "model"], (result) => {
|
||||
if (result.provider) setProvider(result.provider);
|
||||
if (result.apiKey) setApiKey(result.apiKey);
|
||||
if (result.model) setModel(result.model);
|
||||
@@ -183,7 +207,7 @@ function OptionsPage() {
|
||||
return;
|
||||
}
|
||||
setSaveStatus('saving');
|
||||
chrome.storage.local.set({ provider, apiKey: apiKey.trim(), model }, () => {
|
||||
safeStorageSet({ provider, apiKey: apiKey.trim(), model }, () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
setSaveStatus('error');
|
||||
setTimeout(() => setSaveStatus('idle'), 3000);
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
// ─── Safe chrome storage wrapper ─────────────────────────────────────────────
|
||||
|
||||
function safeStorageGet(keys: string[], callback: (result: Record<string, string>) => void) {
|
||||
try {
|
||||
if (typeof chrome === 'undefined' || !chrome.runtime?.id) return;
|
||||
chrome.storage.local.get(keys, callback);
|
||||
} catch (err) {
|
||||
console.warn('LexAI: Extension context invalidated', err);
|
||||
}
|
||||
}
|
||||
|
||||
function Popup() {
|
||||
const [configured, setConfigured] = useState(false);
|
||||
const [provider, setProvider] = useState('');
|
||||
const [model, setModel] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
chrome.storage.local.get(["provider", "apiKey", "model"], (result: Record<string, string>) => {
|
||||
safeStorageGet(["provider", "apiKey", "model"], (result) => {
|
||||
if (result.apiKey) {
|
||||
setConfigured(true);
|
||||
setProvider(result.provider || 'openai');
|
||||
@@ -16,7 +27,15 @@ function Popup() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const openSettings = () => chrome.runtime.openOptionsPage();
|
||||
const openSettings = () => {
|
||||
try {
|
||||
if (typeof chrome !== 'undefined' && chrome.runtime?.id) {
|
||||
chrome.runtime.openOptionsPage();
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('LexAI: Extension context invalidated', err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: 16, fontFamily: 'system-ui, sans-serif', color: '#1e1e2e' }}>
|
||||
|
||||
Reference in New Issue
Block a user