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>
72 lines
1.9 KiB
TypeScript
72 lines
1.9 KiB
TypeScript
/**
|
|
* 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
|
|
* - blue: Connected, idle
|
|
* - green: Syncing (with count badge)
|
|
* - red: Error / disconnected
|
|
*/
|
|
import { action, storage } from "./compat";
|
|
|
|
type IconColor = "gray" | "blue" | "green" | "red";
|
|
|
|
function iconPath(color: IconColor, size: number): string {
|
|
return `src/icons/icon-${color}-${size}.png`;
|
|
}
|
|
|
|
function iconSet(color: IconColor): Record<string, string> {
|
|
return {
|
|
"16": iconPath(color, 16),
|
|
"48": iconPath(color, 48),
|
|
"128": iconPath(color, 128),
|
|
};
|
|
}
|
|
|
|
export async function setIconState(
|
|
state: "not_logged_in" | "connected" | "syncing" | "error",
|
|
syncCount?: number,
|
|
) {
|
|
switch (state) {
|
|
case "not_logged_in":
|
|
await action.setIcon({ path: iconSet("gray") });
|
|
await action.setBadgeText({ text: "" });
|
|
break;
|
|
|
|
case "connected":
|
|
await action.setIcon({ path: iconSet("blue") });
|
|
await action.setBadgeText({ text: "" });
|
|
break;
|
|
|
|
case "syncing":
|
|
await action.setIcon({ path: iconSet("green") });
|
|
if (syncCount && syncCount > 0) {
|
|
await action.setBadgeText({
|
|
text: syncCount > 99 ? "99+" : String(syncCount),
|
|
});
|
|
await action.setBadgeBackgroundColor({ color: "#22C55E" });
|
|
}
|
|
break;
|
|
|
|
case "error":
|
|
await action.setIcon({ path: iconSet("red") });
|
|
await action.setBadgeText({ text: "!" });
|
|
await action.setBadgeBackgroundColor({ color: "#EF4444" });
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** Clear the sync badge after a delay. */
|
|
export function clearSyncBadge(delayMs = 3000) {
|
|
setTimeout(async () => {
|
|
const state = await storage.get(["apiToken"]);
|
|
if (state.apiToken) {
|
|
await setIconState("connected");
|
|
} else {
|
|
await setIconState("not_logged_in");
|
|
}
|
|
}, delayMs);
|
|
}
|