178 lines
4.6 KiB
JavaScript
178 lines
4.6 KiB
JavaScript
"use strict";
|
|
|
|
const HOST_NAME = "com.easygoingaming.firefox_agent_bridge";
|
|
const RECONNECT_MS = 2000;
|
|
const DEFAULT_PORT = 17634;
|
|
|
|
const ALLOWED = {
|
|
"meta.ping": async () => ({ ok: true, extension: "bookmarks-api" }),
|
|
"meta.methods": async () => Object.keys(ALLOWED).sort(),
|
|
"bookmarks.getTree": () => browser.bookmarks.getTree(),
|
|
"bookmarks.getSubTree": (id) => browser.bookmarks.getSubTree(id),
|
|
"bookmarks.getChildren": (id) => browser.bookmarks.getChildren(id),
|
|
"bookmarks.get": (idOrIds) => browser.bookmarks.get(idOrIds),
|
|
"bookmarks.search": (query) => browser.bookmarks.search(query),
|
|
"bookmarks.create": (details) => browser.bookmarks.create(details),
|
|
"bookmarks.update": (id, changes) => browser.bookmarks.update(id, changes),
|
|
"bookmarks.move": (id, destination) => browser.bookmarks.move(id, destination),
|
|
"bookmarks.remove": (id) => browser.bookmarks.remove(id),
|
|
"bookmarks.removeTree": (id) => browser.bookmarks.removeTree(id),
|
|
"bookmarks.getRecent": (numberOfItems) => browser.bookmarks.getRecent(numberOfItems),
|
|
};
|
|
|
|
let port = null;
|
|
let reconnectTimer = null;
|
|
let hostUp = false;
|
|
let listenPort = DEFAULT_PORT;
|
|
let bindError = "";
|
|
let configWaiter = null;
|
|
|
|
function normalizePort(value) {
|
|
const n = Number(value);
|
|
if (!Number.isInteger(n) || n < 1024 || n > 65535) {
|
|
throw new Error("Port must be an integer from 1024 to 65535");
|
|
}
|
|
return n;
|
|
}
|
|
|
|
async function loadListenPort() {
|
|
const data = await browser.storage.local.get("listenPort");
|
|
try {
|
|
return normalizePort(data.listenPort == null ? DEFAULT_PORT : data.listenPort);
|
|
} catch (err) {
|
|
return DEFAULT_PORT;
|
|
}
|
|
}
|
|
|
|
async function pushConfig() {
|
|
if (!port) {
|
|
return;
|
|
}
|
|
listenPort = await loadListenPort();
|
|
await new Promise((resolve) => {
|
|
configWaiter = resolve;
|
|
port.postMessage({ type: "config", port: listenPort });
|
|
setTimeout(resolve, 2500);
|
|
});
|
|
}
|
|
|
|
function statusInfo() {
|
|
return {
|
|
hostUp,
|
|
port: listenPort,
|
|
listenPort,
|
|
bindError,
|
|
};
|
|
}
|
|
|
|
function asArray(value) {
|
|
if (value === undefined || value === null) {
|
|
return [];
|
|
}
|
|
return Array.isArray(value) ? value : [value];
|
|
}
|
|
|
|
async function dispatchBookmark(message) {
|
|
const method = message && message.method;
|
|
const fn = ALLOWED[method];
|
|
if (!fn) {
|
|
throw new Error(`method not allowed: ${method || "(missing)"}`);
|
|
}
|
|
return fn(...asArray(message.args));
|
|
}
|
|
|
|
function attach(nextPort) {
|
|
port = nextPort;
|
|
hostUp = true;
|
|
port.onMessage.addListener(async (message) => {
|
|
if (message && message.type === "config-result") {
|
|
listenPort = message.port || listenPort;
|
|
bindError = message.ok ? "" : (message.error || "could not bind port");
|
|
if (configWaiter) {
|
|
const done = configWaiter;
|
|
configWaiter = null;
|
|
done();
|
|
}
|
|
return;
|
|
}
|
|
if (message && message.type === "auth") {
|
|
try {
|
|
const client = await findClientByTokenHash(message.token_hash);
|
|
port.postMessage({
|
|
type: "auth-result",
|
|
id: message.id,
|
|
ok: Boolean(client),
|
|
client: client || null,
|
|
});
|
|
} catch (err) {
|
|
port.postMessage({
|
|
type: "auth-result",
|
|
id: message.id,
|
|
ok: false,
|
|
error: err && err.message ? err.message : String(err),
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
if (message && message.type) {
|
|
return;
|
|
}
|
|
const id = message && message.id;
|
|
try {
|
|
const result = await dispatchBookmark(message);
|
|
port.postMessage({ id, ok: true, result });
|
|
} catch (err) {
|
|
port.postMessage({
|
|
id,
|
|
ok: false,
|
|
error: err && err.message ? err.message : String(err),
|
|
});
|
|
}
|
|
});
|
|
port.onDisconnect.addListener(() => {
|
|
port = null;
|
|
hostUp = false;
|
|
bindError = "";
|
|
scheduleReconnect();
|
|
});
|
|
pushConfig();
|
|
}
|
|
|
|
function scheduleReconnect() {
|
|
if (reconnectTimer) {
|
|
return;
|
|
}
|
|
reconnectTimer = setTimeout(() => {
|
|
reconnectTimer = null;
|
|
connect();
|
|
}, RECONNECT_MS);
|
|
}
|
|
|
|
function connect() {
|
|
try {
|
|
attach(browser.runtime.connectNative(HOST_NAME));
|
|
} catch (err) {
|
|
hostUp = false;
|
|
console.error("native host connect failed", err);
|
|
scheduleReconnect();
|
|
}
|
|
}
|
|
|
|
browser.runtime.onMessage.addListener((message) => {
|
|
if (message && message.type === "status") {
|
|
return Promise.resolve(statusInfo());
|
|
}
|
|
if (message && message.type === "set-port") {
|
|
return (async () => {
|
|
const n = normalizePort(message.port);
|
|
await browser.storage.local.set({ listenPort: n });
|
|
listenPort = n;
|
|
await pushConfig();
|
|
return statusInfo();
|
|
})();
|
|
}
|
|
return undefined;
|
|
});
|
|
|
|
connect();
|