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