diff --git a/src/lib/messaging.ts b/src/lib/messaging.ts index 0596d7db..fdb7ec6e 100644 --- a/src/lib/messaging.ts +++ b/src/lib/messaging.ts @@ -11,6 +11,7 @@ export type TabMessageType = | "finding_content" | "commandline_cmd" | "commandline_frame" + | "state" | "lock" export type NonTabMessageType = @@ -152,7 +153,10 @@ export async function messageAllTabs( try { responses.push(await messageTab(tab.id, type, command, args)) } catch (e) { - logger.error(e) + // Skip errors caused by tabs we aren't running on + if (e.message != "Could not establish connection. Receiving end does not exist.") { + logger.error(e) + } } } return responses diff --git a/src/state.ts b/src/state.ts index e51ab064..0456ff69 100644 --- a/src/state.ts +++ b/src/state.ts @@ -14,7 +14,9 @@ If this turns out to be expensive there are improvements available. */ +import * as locks from "@src/lib/locks" import Logger from "@src/lib/logging" +import * as messaging from "@src/lib/messaging" const logger = new Logger("state") class State { @@ -54,19 +56,33 @@ const state = (new Proxy(overlay, { } }, - /** Persist sets to storage immediately */ + /** Persist sets to storage "immediately" */ set(target, property, value) { - logger.debug("State changed!", property, value) - target[property] = value - browser.storage.local.set({ state: target } as any) + locks.withlock("state", async () => { + logger.debug("State changed!", property, value) + target[property] = value + browser.storage.local.set({ state: target } as any) + + // Wait for reply from each script to say that they have updated their own state + await Promise.all([ + // dispatch message to all content state.ts's + messaging.messageAllTabs("state", "stateUpdate", [{state: target}]), + + // Ideally this V would use Farnoy's typed messages but + // I haven't had time to get my head around them + browser.runtime.sendMessage({type: "state", command: "stateUpdate", args: [{state: target}]}), + ]) + }) + return true }, })) -browser.storage.onChanged.addListener((changes, areaname) => { - if (areaname === "local" && "state" in changes) { - Object.assign(overlay, changes.state.newValue) - } +// Keep instances of state.ts synchronised with each other +messaging.addListener("state", (message, sender, sendResponse) => { + if (message.command !== "stateUpdate") throw("Unsupported message to state, type " + message.command) + Object.assign(overlay, message.args[0].state) + sendResponse(true) }) export { state as default }