Last active
March 4, 2024 09:15
-
-
Save clinuxrulz/77f341832c6025bf10f0b183ee85e072 to your computer and use it in GitHub Desktop.
Browser Plugin Utility (bidirectional asynchronous function calls)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export class Plugin { | |
private src: String; | |
private callbacks: any; | |
private iframe?: HTMLIFrameElement; | |
private messageHandler: (e: MessageEvent) => void; | |
private recvMsgHandler: (msg: string) => void; | |
private nextCallId: number = 0; | |
private resolveMap: any; | |
onLoad: () => void = () => {}; | |
private allocCallId(): string { | |
return "" + (this.nextCallId++); | |
} | |
constructor(src: String, callbacks: any) { | |
this.src = src; | |
this.callbacks = callbacks; | |
this.messageHandler = (e: MessageEvent) => { | |
if (e.source !== this.iframe.contentWindow) { | |
return; | |
} | |
this.recvMsgHandler(e.data); | |
}; | |
this.recvMsgHandler = (msg: string) => { | |
var data = JSON.parse(msg); | |
if (data.type == "call") { | |
callbacks[data.fnName](data.params).then(result => { | |
this.iframe.contentWindow.postMessage( | |
JSON.stringify({ | |
type: "response", | |
callId: data.callId, | |
result | |
}), | |
"*" | |
); | |
}); | |
} else if (data.type == "response") { | |
this.resolveMap[data.callId](data.result); | |
delete this.resolveMap[data.callId]; | |
} | |
}; | |
this.resolveMap = {}; | |
} | |
install(): void { | |
let bootstrap = | |
"var exports = {};" + | |
"var callWithResult = (() => {" + | |
"var resolveMap = {};" + | |
"var allocCallId = (() => {" + | |
"var callId = 0;" + | |
"return () => { return \"\" + (callId++); };" + | |
"})();" + | |
"function callWithResult2(fnName, params) {" + | |
"return new Promise(resolve => {" + | |
"var callId = allocCallId();" + | |
"resolveMap[callId] = resolve;" + | |
"parent.postMessage(" + | |
"JSON.stringify({" + | |
"type: \"call\"," + | |
"callId: callId," + | |
"fnName: fnName," + | |
"params: params" + | |
"})," + | |
"\"*\"" + | |
");" + | |
"});" + | |
"}" + | |
"window.addEventListener(\"message\", e => {" + | |
"var data = JSON.parse(e.data);" + | |
"if (data.type == \"response\") {" + | |
"var key = data.callId;" + | |
"resolveMap[key](data.result);" + | |
"delete resolveMap[key];" + | |
"} else if (data.type == \"call\") {" + | |
"exports[data.fnName](data.params).then(result => {" + | |
"parent.postMessage(" + | |
"JSON.stringify({" + | |
"type: \"response\"," + | |
"callId: data.callId," + | |
"result: result" + | |
"})," + | |
"\"*\"" + | |
");" + | |
"});" + | |
"}" + | |
"});" + | |
"return callWithResult2;" + | |
"})();"; | |
for (var fnName in this.callbacks) { | |
if (this.callbacks.hasOwnProperty(fnName)) { | |
bootstrap += "function " + fnName + "(params) { return callWithResult(\"" + fnName + "\", params); };"; | |
} | |
} | |
this.iframe = document.createElement("iframe"); | |
this.iframe.addEventListener("load", e => this.onLoad()); | |
this.iframe.style.width = "0"; | |
this.iframe.style.height = "0"; | |
this.iframe.srcdoc = "<html><head></head><body><script>" + bootstrap + this.src + "</script></body></html>"; | |
document.body.append(this.iframe); | |
window.addEventListener("message", this.messageHandler); | |
} | |
uninstall(): void { | |
document.body.removeChild(this.iframe); | |
window.removeEventListener("message", this.messageHandler); | |
} | |
call(fnName: String, params: any): Promise<any> { | |
var callId = this.allocCallId(); | |
return new Promise(resolve => { | |
this.resolveMap[callId] = resolve; | |
this.iframe.contentWindow.postMessage( | |
JSON.stringify({ | |
type: "call", | |
callId, | |
fnName, | |
params, | |
}), | |
"*" | |
); | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
var testPlugin = new Plugin( | |
"testFn(42).then(result => console.log(\"test plugin called testFn with result:\", result));" + | |
"exports.testFn2 = params => new Promise(resolve => resolve(params));", | |
{ | |
testFn: (params) => { | |
return new Promise(resolve => resolve(params)); | |
} | |
} | |
); | |
testPlugin.onLoad = () => { | |
testPlugin.call("testFn2", 123).then(result => console.log("parent window called testFn2 from plugin with result:", result)); | |
}; | |
onMount(() => { | |
testPlugin.install(); | |
}); | |
onCleanup(() => { | |
testPlugin.uninstall(); | |
}); | |
... |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment