mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-08 08:39:59 +00:00
Refactor native messaging wrapper
This commit is contained in:
@@ -4,7 +4,7 @@ import semver from "semver";
|
|||||||
|
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
import { Port } from "../messaging";
|
import { Port } from "../messaging";
|
||||||
import nativeMessaging from "./nativeMessaging";
|
import * as nativeMessaging from "./nativeMessaging";
|
||||||
import options from "./options";
|
import options from "./options";
|
||||||
|
|
||||||
export const BRIDGE_TIMEOUT = 5000;
|
export const BRIDGE_TIMEOUT = 5000;
|
||||||
|
|||||||
@@ -8,108 +8,95 @@ import { Message, Port } from "../messaging";
|
|||||||
type DisconnectListener = (port: Port) => void;
|
type DisconnectListener = (port: Port) => void;
|
||||||
type MessageListener = (message: Message) => void;
|
type MessageListener = (message: Message) => void;
|
||||||
|
|
||||||
function connectNative(application: string): Port {
|
/**
|
||||||
/**
|
* Create backup server URL from configured options.
|
||||||
* In order to preserve the synchronous API, messages are
|
*/
|
||||||
* queued before either the native messaging host or the
|
async function getBackupServerUrl() {
|
||||||
* WebSocket connection is ready to send data.
|
const { bridgeBackupHost, bridgeBackupPort, bridgeBackupPassword } =
|
||||||
*/
|
await options.getAll();
|
||||||
let messageQueue: object[] = [];
|
|
||||||
|
|
||||||
/**
|
const url = new URL(`ws://${bridgeBackupHost}:${bridgeBackupPort}`);
|
||||||
* Set once the native messaging host is known to be either
|
if (bridgeBackupPassword) {
|
||||||
* present/missing. Determines whether messages go to the
|
url.searchParams.append("password", bridgeBackupPassword);
|
||||||
* message queue.
|
}
|
||||||
*/
|
|
||||||
let isNativeHostStatusKnown = false;
|
|
||||||
|
|
||||||
const port = browser.runtime.connectNative(application);
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
let socket: WebSocket;
|
/**
|
||||||
|
* `browser.runtime.connectNative()` wrapper.
|
||||||
|
*/
|
||||||
|
export function connectNative(application: string): Port {
|
||||||
|
/** Whether native host or backup is ready for messages. */
|
||||||
|
let isNativeHostReady = false;
|
||||||
|
|
||||||
const onDisconnectListeners = new Set<DisconnectListener>();
|
let backupSocket: Nullable<WebSocket> = null;
|
||||||
const onMessageListeners = new Set<MessageListener>();
|
let backupMessageQueue: Message[] = [];
|
||||||
|
|
||||||
|
// Make initial connection to native host
|
||||||
|
const port = browser.runtime.connectNative(application); //
|
||||||
|
|
||||||
|
const messageListeners = new Set<MessageListener>();
|
||||||
|
const disconnectListeners = new Set<DisconnectListener>();
|
||||||
|
|
||||||
// Port proxy API
|
|
||||||
const portObject: Port = {
|
const portObject: Port = {
|
||||||
name: "",
|
name: "",
|
||||||
|
|
||||||
onDisconnect: {
|
onDisconnect: {
|
||||||
addListener(cb: DisconnectListener) {
|
addListener(cb: DisconnectListener) {
|
||||||
onDisconnectListeners.add(cb);
|
disconnectListeners.add(cb);
|
||||||
},
|
},
|
||||||
removeListener(cb: DisconnectListener) {
|
removeListener(cb: DisconnectListener) {
|
||||||
onDisconnectListeners.delete(cb);
|
disconnectListeners.delete(cb);
|
||||||
},
|
},
|
||||||
hasListener(cb: DisconnectListener) {
|
hasListener(cb: DisconnectListener) {
|
||||||
return onDisconnectListeners.has(cb);
|
return disconnectListeners.has(cb);
|
||||||
},
|
},
|
||||||
hasListeners() {
|
hasListeners() {
|
||||||
return onDisconnectListeners.size > 0;
|
return disconnectListeners.size > 0;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onMessage: {
|
onMessage: {
|
||||||
addListener(cb: MessageListener) {
|
addListener(cb: MessageListener) {
|
||||||
onMessageListeners.add(cb);
|
messageListeners.add(cb);
|
||||||
},
|
},
|
||||||
removeListener(cb: MessageListener) {
|
removeListener(cb: MessageListener) {
|
||||||
onMessageListeners.delete(cb);
|
messageListeners.delete(cb);
|
||||||
},
|
},
|
||||||
hasListener(cb: MessageListener) {
|
hasListener(cb: MessageListener) {
|
||||||
return onMessageListeners.has(cb);
|
return messageListeners.has(cb);
|
||||||
},
|
},
|
||||||
hasListeners() {
|
hasListeners() {
|
||||||
return onMessageListeners.size > 0;
|
return messageListeners.size > 0;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
disconnect() {
|
disconnect() {
|
||||||
if (socket) {
|
if (backupSocket) {
|
||||||
socket.close();
|
backupSocket.close();
|
||||||
} else {
|
} else {
|
||||||
port.disconnect();
|
port.disconnect();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
postMessage(message) {
|
postMessage(message) {
|
||||||
if (socket) {
|
if (!isNativeHostReady) {
|
||||||
switch (socket.readyState) {
|
// Queue messages until ready
|
||||||
case WebSocket.CONNECTING: {
|
backupMessageQueue.push(message);
|
||||||
// Queue message until WebSocket is ready
|
} else if (backupSocket) {
|
||||||
messageQueue.push(message);
|
backupSocket.send(JSON.stringify(message));
|
||||||
break;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
case WebSocket.OPEN: {
|
|
||||||
socket.send(JSON.stringify(message));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!isNativeHostStatusKnown) {
|
|
||||||
// Queue message until native messaging host is ready
|
|
||||||
messageQueue.push(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
port.postMessage(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
port.postMessage(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
port.onDisconnect.addListener(async () => {
|
port.onDisconnect.addListener(async () => {
|
||||||
const {
|
const bridgeBackupEnabled = await options.get("bridgeBackupEnabled");
|
||||||
bridgeBackupEnabled,
|
|
||||||
bridgeBackupHost,
|
|
||||||
bridgeBackupPort,
|
|
||||||
bridgeBackupPassword
|
|
||||||
} = await options.getAll();
|
|
||||||
|
|
||||||
if (!bridgeBackupEnabled) {
|
if (!bridgeBackupEnabled) {
|
||||||
portObject.error = {
|
portObject.error = { message: "" };
|
||||||
message: ""
|
for (const listener of disconnectListeners) {
|
||||||
};
|
|
||||||
|
|
||||||
for (const listener of onDisconnectListeners) {
|
|
||||||
listener(portObject);
|
listener(portObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,38 +105,35 @@ function connectNative(application: string): Port {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (port.error && !isNativeHostStatusKnown) {
|
/**
|
||||||
isNativeHostStatusKnown = true;
|
* If port disconnected because of an error and native host
|
||||||
|
* status had not already been resolved.
|
||||||
|
*/
|
||||||
|
if (port.error && !isNativeHostReady) {
|
||||||
|
backupSocket = new WebSocket(await getBackupServerUrl());
|
||||||
|
|
||||||
const url = new URL(`ws://${bridgeBackupHost}:${bridgeBackupPort}`);
|
backupSocket.addEventListener("open", () => {
|
||||||
if (bridgeBackupPassword) {
|
isNativeHostReady = true;
|
||||||
url.searchParams.append("password", bridgeBackupPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
socket = new WebSocket(url.href);
|
|
||||||
|
|
||||||
socket.addEventListener("open", () => {
|
|
||||||
// Send all messages in queue
|
// Send all messages in queue
|
||||||
while (messageQueue.length) {
|
while (backupMessageQueue.length) {
|
||||||
const message = messageQueue.pop();
|
backupSocket?.send(
|
||||||
socket.send(JSON.stringify(message));
|
JSON.stringify(backupMessageQueue.shift())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
backupSocket.addEventListener("message", ev => {
|
||||||
socket.addEventListener("message", ev => {
|
for (const listener of messageListeners) {
|
||||||
for (const listener of onMessageListeners) {
|
|
||||||
listener(JSON.parse(ev.data));
|
listener(JSON.parse(ev.data));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
backupSocket.addEventListener("close", ev => {
|
||||||
socket.addEventListener("close", ev => {
|
// If not a normal closure, set error message
|
||||||
if (ev.code !== 1000) {
|
if (ev.code !== 1000) {
|
||||||
portObject.error = {
|
portObject.error = { message: ev.reason };
|
||||||
message: ev.reason
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const listener of onDisconnectListeners) {
|
for (const listener of disconnectListeners) {
|
||||||
listener(portObject);
|
listener(portObject);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -157,12 +141,12 @@ function connectNative(application: string): Port {
|
|||||||
});
|
});
|
||||||
|
|
||||||
port.onMessage.addListener((message: Message) => {
|
port.onMessage.addListener((message: Message) => {
|
||||||
if (!isNativeHostStatusKnown) {
|
if (!isNativeHostReady) {
|
||||||
isNativeHostStatusKnown = true;
|
isNativeHostReady = true;
|
||||||
messageQueue = [];
|
backupMessageQueue = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const listener of onMessageListeners) {
|
for (const listener of messageListeners) {
|
||||||
listener(message);
|
listener(message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -170,30 +154,27 @@ function connectNative(application: string): Port {
|
|||||||
return portObject;
|
return portObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendNativeMessage(application: string, message: Message) {
|
/**
|
||||||
|
* `browser.runtime.sendNativeMessage()` wrapper.
|
||||||
|
*/
|
||||||
|
export async function sendNativeMessage(application: string, message: Message) {
|
||||||
try {
|
try {
|
||||||
return await browser.runtime.sendNativeMessage(application, message);
|
return await browser.runtime.sendNativeMessage(application, message);
|
||||||
} catch {
|
} catch {
|
||||||
const {
|
const bridgeBackupEnabled = await options.get("bridgeBackupEnabled");
|
||||||
bridgeBackupEnabled,
|
|
||||||
bridgeBackupHost,
|
|
||||||
bridgeBackupPort,
|
|
||||||
bridgeBackupPassword
|
|
||||||
} = await options.getAll();
|
|
||||||
|
|
||||||
if (!bridgeBackupEnabled) {
|
if (!bridgeBackupEnabled) {
|
||||||
throw logger.error(
|
throw logger.error(
|
||||||
"Bridge connection failed and backup not enabled."
|
"Bridge connection failed and backup not enabled."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(`http://${bridgeBackupHost}:${bridgeBackupPort}`);
|
const backupServerUrl = await getBackupServerUrl();
|
||||||
if (bridgeBackupPassword) {
|
|
||||||
url.searchParams.append("password", bridgeBackupPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch(url.href);
|
const backupServerHttpUrl = new URL(backupServerUrl);
|
||||||
if (res.status === 401) {
|
backupServerHttpUrl.protocol = "http";
|
||||||
|
|
||||||
|
// Send HTTP request to check authentication
|
||||||
|
if ((await fetch(backupServerHttpUrl)).status === 401) {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Bridge daemon connection failed due to authentication error."
|
"Bridge daemon connection failed due to authentication error."
|
||||||
);
|
);
|
||||||
@@ -201,29 +182,20 @@ async function sendNativeMessage(application: string, message: Message) {
|
|||||||
throw 401;
|
throw 401;
|
||||||
}
|
}
|
||||||
|
|
||||||
url.protocol = "ws";
|
|
||||||
|
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
const ws = new WebSocket(url.href);
|
const backupSocket = new WebSocket(backupServerUrl);
|
||||||
|
|
||||||
ws.addEventListener("open", () => {
|
backupSocket.addEventListener("open", () => {
|
||||||
ws.send(JSON.stringify(message));
|
backupSocket.send(JSON.stringify(message));
|
||||||
});
|
});
|
||||||
|
backupSocket.addEventListener("message", ev => {
|
||||||
ws.addEventListener("message", ev => {
|
backupSocket.close();
|
||||||
ws.close();
|
|
||||||
resolve(JSON.parse(ev.data));
|
resolve(JSON.parse(ev.data));
|
||||||
});
|
});
|
||||||
|
backupSocket.addEventListener("error", () => {
|
||||||
ws.addEventListener("error", () => {
|
|
||||||
logger.error("Bridge daemon connection error.");
|
logger.error("Bridge daemon connection error.");
|
||||||
reject();
|
reject();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
|
||||||
connectNative,
|
|
||||||
sendNativeMessage
|
|
||||||
};
|
|
||||||
|
|||||||
Reference in New Issue
Block a user