react-native-webview-comlink
Version:
Add JavaScript interface for react-native-webview, based on Comlink
131 lines (130 loc) • 4.93 kB
JavaScript
import { fromWireValue, toWireValue, } from './message';
import { ArrayMap, FakeWeakRef } from './utils';
export class MessageHub {
constructor(name, endpoint, logger) {
this.endpoint = endpoint;
this.logger = logger;
this.localFunctionByID = {};
this.localFunctionIDByFunction = typeof Map !== 'undefined' ? new Map() : new ArrayMap();
this.resolveByRequestID = {};
this.remoteFunctionRefByID = {};
this.tag = `RNWC|${name}|`;
if (typeof FinalizationRegistry !== 'undefined') {
this.remoteFunctionRegistry = new FinalizationRegistry((cleanup) => {
cleanup();
});
}
}
sendMessage(msg) {
this.endpoint.postMessage(this.tag + JSON.stringify(msg));
}
notifyRelease(msg) {
this.sendMessage(msg);
}
requestResponse(msg) {
return new Promise((resolve) => {
this.resolveByRequestID[msg.rid] = resolve;
this.sendMessage(msg);
});
}
registerLocalFunction(id, vid, localFunction) {
if (this.localFunctionByID.hasOwnProperty(id)) {
// update only the vid if the function is already registered
this.localFunctionByID[id].vid = vid;
return;
}
this.localFunctionByID[id] = {
localFunction,
vid,
};
this.localFunctionIDByFunction.set(localFunction, id);
}
tryGetLocalFuntionID(localFunction) {
return this.localFunctionIDByFunction.get(localFunction);
}
registerRemoteFunction(id, remoteFunction) {
this.remoteFunctionRefByID[id] =
typeof WeakRef !== 'undefined'
? new WeakRef(remoteFunction)
: new FakeWeakRef(remoteFunction);
if (this.remoteFunctionRegistry) {
this.remoteFunctionRegistry.register(remoteFunction, remoteFunction.cleanup, remoteFunction);
}
}
unregisterRemoteFunction(id) {
const remoteFunction = this.tryGetRemoteFunction(id);
if (!remoteFunction) {
return;
}
delete this.remoteFunctionRefByID[id];
if (this.remoteFunctionRegistry) {
this.remoteFunctionRegistry.unregister(remoteFunction);
}
}
tryGetRemoteFunction(id) {
var _a;
return (_a = this.remoteFunctionRefByID[id]) === null || _a === void 0 ? void 0 : _a.deref();
}
canHandleMessage(msg) {
return msg === null || msg === void 0 ? void 0 : msg.startsWith(this.tag);
}
handleMessage(msg) {
var _a;
const data = JSON.parse(msg.substr(this.tag.length));
switch (data.type) {
case "REQ" /* REQUEST */: {
const { id, args, rid } = data;
Promise.resolve()
.then(() => {
var _a;
const localFunction = (_a = this.localFunctionByID[id]) === null || _a === void 0 ? void 0 : _a.localFunction;
if (!localFunction) {
throw new Error(`failed to invoke function, ${id} is missing`);
}
return localFunction.apply(null, args.map((arg) => fromWireValue(arg, this)));
})
.then((value) => {
this.sendMessage({
type: "RSP" /* RESPONSE */,
ret: toWireValue(value, this),
rid,
});
})
.catch((error) => {
const wireValue = toWireValue(error, this);
wireValue.type = "T" /* THROW */;
this.sendMessage({
type: "RSP" /* RESPONSE */,
ret: wireValue,
rid,
});
});
break;
}
case "RSP" /* RESPONSE */: {
const { rid, ret } = data;
const resolve = this.resolveByRequestID[rid];
if (!resolve) {
return;
}
delete this.resolveByRequestID[rid];
resolve(ret);
break;
}
case "RLS" /* RELEASE */: {
const { id, vid } = data;
if (vid === ((_a = this.localFunctionByID[id]) === null || _a === void 0 ? void 0 : _a.vid)) {
this.logger(`release local function: ${id}`);
this.localFunctionIDByFunction.delete(this.localFunctionByID[id].localFunction);
delete this.localFunctionByID[id];
}
else {
this.logger(`not release local function: ${id}, ${vid}`);
}
break;
}
default:
break;
}
}
}