feat: implement M3 multi-browser support (Firefox, Edge, Safari)
Add browser abstraction layer (compat.ts) that normalizes Chrome/Firefox/ Edge/Safari extension APIs behind a unified promise-based interface. Replace all direct chrome.* calls with compat layer across service worker, sync engine, badge, storage, popup, and options modules. - Browser-specific manifests in manifests/ (Firefox MV3 with gecko settings, Edge/Safari variants) - Multi-target build system: `npm run build` produces all four browser builds in build/<browser>/ - Per-browser build scripts: build:chrome, build:firefox, build:edge, build:safari - Auto-detects browser at runtime for platform-specific device registration - All 38 existing tests pass, typecheck clean Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Badge/icon management — updates extension icon color and badge text
|
||||
* based on connection status and sync activity.
|
||||
* Uses the browser-agnostic compat layer.
|
||||
*
|
||||
* States:
|
||||
* - gray: Not logged in / no device identity
|
||||
@@ -8,6 +9,7 @@
|
||||
* - green: Syncing (with count badge)
|
||||
* - red: Error / disconnected
|
||||
*/
|
||||
import { action, storage } from "./compat";
|
||||
|
||||
type IconColor = "gray" | "blue" | "green" | "red";
|
||||
|
||||
@@ -29,29 +31,29 @@ export async function setIconState(
|
||||
) {
|
||||
switch (state) {
|
||||
case "not_logged_in":
|
||||
await chrome.action.setIcon({ path: iconSet("gray") });
|
||||
await chrome.action.setBadgeText({ text: "" });
|
||||
await action.setIcon({ path: iconSet("gray") });
|
||||
await action.setBadgeText({ text: "" });
|
||||
break;
|
||||
|
||||
case "connected":
|
||||
await chrome.action.setIcon({ path: iconSet("blue") });
|
||||
await chrome.action.setBadgeText({ text: "" });
|
||||
await action.setIcon({ path: iconSet("blue") });
|
||||
await action.setBadgeText({ text: "" });
|
||||
break;
|
||||
|
||||
case "syncing":
|
||||
await chrome.action.setIcon({ path: iconSet("green") });
|
||||
await action.setIcon({ path: iconSet("green") });
|
||||
if (syncCount && syncCount > 0) {
|
||||
await chrome.action.setBadgeText({
|
||||
await action.setBadgeText({
|
||||
text: syncCount > 99 ? "99+" : String(syncCount),
|
||||
});
|
||||
await chrome.action.setBadgeBackgroundColor({ color: "#22C55E" });
|
||||
await action.setBadgeBackgroundColor({ color: "#22C55E" });
|
||||
}
|
||||
break;
|
||||
|
||||
case "error":
|
||||
await chrome.action.setIcon({ path: iconSet("red") });
|
||||
await chrome.action.setBadgeText({ text: "!" });
|
||||
await chrome.action.setBadgeBackgroundColor({ color: "#EF4444" });
|
||||
await action.setIcon({ path: iconSet("red") });
|
||||
await action.setBadgeText({ text: "!" });
|
||||
await action.setBadgeBackgroundColor({ color: "#EF4444" });
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -59,7 +61,7 @@ export async function setIconState(
|
||||
/** Clear the sync badge after a delay. */
|
||||
export function clearSyncBadge(delayMs = 3000) {
|
||||
setTimeout(async () => {
|
||||
const state = await chrome.storage.local.get(["apiToken"]);
|
||||
const state = await storage.get(["apiToken"]);
|
||||
if (state.apiToken) {
|
||||
await setIconState("connected");
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user