Files
emoji-chrome-extension/background.js
2026-02-17 13:15:07 +07:00

133 lines
5.5 KiB
JavaScript
Executable File

// Let Chrome open the panel when the toolbar icon is clicked
// Guard: some Chromium variants may not expose sidePanel
const hasSidePanel = !!chrome.sidePanel?.setOptions;
const GCM_PROJECT_NUMBER = '1088502361802';
const EXT_TOKEN_KEY = 'dewemojiExtToken';
const GCM_LOCK_KEY = 'dewemojiExtTokenLockTs';
const GCM_LAST_ERR_KEY = 'dewemojiExtTokenLastError';
const GCM_LAST_ERR_TS_KEY = 'dewemojiExtTokenLastErrorTs';
const GCM_LOCK_WINDOW_MS = 60000;
let gcmRegisterPromise = null;
let gcmInMemoryLockTs = 0;
async function storeExtensionToken(token) {
try { await chrome.storage.local.set({ [EXT_TOKEN_KEY]: token }); } catch {}
}
async function getStoredToken() {
try {
const got = await chrome.storage.local.get([EXT_TOKEN_KEY]);
return got[EXT_TOKEN_KEY] || null;
} catch {
return null;
}
}
function registerGcmToken() {
if (gcmRegisterPromise) return gcmRegisterPromise;
if (!chrome?.gcm?.register) return Promise.resolve(null);
gcmRegisterPromise = new Promise((resolve) => {
(async () => {
if (gcmInMemoryLockTs && Date.now() - gcmInMemoryLockTs < GCM_LOCK_WINDOW_MS) {
return resolve(null);
}
// Avoid repeated register attempts within a short window
const lock = await chrome.storage.local.get([GCM_LOCK_KEY]);
const lastTs = Number(lock[GCM_LOCK_KEY] || 0);
if (Date.now() - lastTs < GCM_LOCK_WINDOW_MS) {
return resolve(null);
}
const now = Date.now();
gcmInMemoryLockTs = now;
await chrome.storage.local.set({ [GCM_LOCK_KEY]: now });
})().then(() => {
chrome.gcm.register([GCM_PROJECT_NUMBER], async (registrationId) => {
if (chrome.runtime.lastError || !registrationId) {
const msg = chrome.runtime.lastError?.message || 'no registrationId';
try { await chrome.storage.local.set({ [GCM_LAST_ERR_KEY]: msg, [GCM_LAST_ERR_TS_KEY]: Date.now() }); } catch {}
if (!/asynchronous operation is pending/i.test(msg)) {
console.warn('GCM register failed', msg);
}
// back off on pending errors
if (/asynchronous operation is pending/i.test(msg)) {
const now = Date.now();
gcmInMemoryLockTs = now;
try { await chrome.storage.local.set({ [GCM_LOCK_KEY]: now }); } catch {}
}
return resolve(null);
}
try { await chrome.storage.local.remove([GCM_LAST_ERR_KEY, GCM_LAST_ERR_TS_KEY]); } catch {}
await storeExtensionToken(registrationId);
console.log('GCM token stored', registrationId.slice(0, 8) + '…');
resolve(registrationId);
});
}).catch(() => resolve(null));
}).finally(() => {
gcmRegisterPromise = null;
});
return gcmRegisterPromise;
}
chrome.runtime.onInstalled.addListener(() => {
if (hasSidePanel) chrome.sidePanel.setPanelBehavior?.({ openPanelOnActionClick: true });
});
// Intentionally do NOT auto-register on startup to avoid concurrent GCM calls.
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
if (msg?.type !== 'dewemoji_get_ext_token') return;
(async () => {
const force = !!msg.force;
let token = await getStoredToken();
if (!token || force) {
await registerGcmToken();
// best-effort wait for storage to be populated
await new Promise(r => setTimeout(r, 300));
token = await getStoredToken();
}
sendResponse({ ok: !!token, token });
})();
return true;
});
// If you still want to ensure the correct path on click, setOptions ONLY:
chrome.action.onClicked.addListener(async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) return;
// inject content.js before the panel opens (best effort; ignore errors)
try {
const pong = await chrome.tabs.sendMessage(tab.id, { type: 'dewemoji_ping' });
if (!pong?.ok) {
await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['content.js'] });
}
} catch {
// No content script: inject it
try { await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['content.js'] }); } catch {}
}
await chrome.sidePanel.setOptions({ tabId: tab.id, path: "panel.html" });
try { await chrome.sidePanel.open({ tabId: tab.id }); } catch {}
});
// Keyboard shortcut: we do open() here (this is a user gesture)
chrome.commands.onCommand.addListener(async (command) => {
if (command !== "toggle-panel") return;
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) return;
// inject content.js before we open the panel with keyboard
try {
const pong = await chrome.tabs.sendMessage(tab.id, { type: 'dewemoji_ping' });
if (!pong?.ok) {
await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['content.js'] });
}
} catch {
// No content script: inject it
try { await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['content.js'] }); } catch {}
}
await chrome.sidePanel.setOptions({ tabId: tab.id, path: "panel.html" });
await chrome.sidePanel.open({ tabId: tab.id });
});