// 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 }); });