Skip to content

Instantly share code, notes, and snippets.

@oeway
Last active May 16, 2021 14:06
Show Gist options
  • Save oeway/b3588952e26fab4f7ea1e8b84264b98c to your computer and use it in GitHub Desktop.
Save oeway/b3588952e26fab4f7ea1e8b84264b98c to your computer and use it in GitHub Desktop.
Connecting to another ImJoy plugin in the same browser via broadcast channel and webRTC (only works with Chrome and FireFox)
/**
* Contains the routines loaded by the plugin iframe under web-browser
* in case when worker failed to initialize
*
* Initializes the web environment version of the platform-dependent
* connection object for the plugin site
*/
import { connectRPC } from "./pluginCore.js";
import { RPC, API_VERSION } from "./rpc.js";
import { MessageEmitter, randId, normalizeConfig } from "./utils.js";
export { setupRPC, waitForInitialization } from "./main.js";
export { version as VERSION } from "../package.json";
export { RPC, API_VERSION };
export class Connection extends MessageEmitter {
constructor(config) {
super(config && config.debug);
this.config = config || {};
this.config.channel = this.config.channel || "imjoy_webrtc_channel";
this.peer_id = this.config.channel;
this.broadcastChannel = new BroadcastChannel(this.config.channel);
this.broadcastChannel.onmessage = e => {
const data = e.data;
if (data.peer_id === this.peer_id) {
this._fire(data.type, data);
} else if (this.config.debug) {
console.log(
`connection peer id mismatch ${data.peer_id} !== ${this.peer_id}`
);
}
};
if (this.config.isPlugin) {
this.connect();
}
}
connect() {
if (this.config.isPlugin) {
this._fire("connected");
this.once("initialize", () => {
if (!this.rpc) {
this.rpc = connectRPC(this, this.config);
} else {
this.rpc.once("remoteReady", () => {
// this.rpc.sendInterface();
const api = this.rpc.getRemote() || {};
window.dispatchEvent(
new CustomEvent("imjoy_remote_api_ready", { detail: api })
);
});
}
this.emit({
type: "initialized",
config: this.config,
origin: "*",
peer_id: this.peer_id
});
});
this.emit({
type: "imjoyRPCReady",
config: this.config,
peer_id: this.peer_id
});
} else {
this.emit({
type: "initialize",
config: this.config,
peer_id: this.peer_id
});
}
}
reset() {
this._event_handlers = {};
this._once_handlers = {};
}
execute() {
throw new Error("Execution is not allowed for socketio connection");
}
disconnect() {
this._fire("beforeDisconnect");
this.socket.disconnect();
this.init();
this._fire("disconnected");
}
emit(data) {
data.peer_id = this.peer_id;
this.broadcastChannel.postMessage(data);
}
}
export function connectToPeer(config) {
config = config || {};
config.name = config.name || randId();
config = normalizeConfig(config);
return new Promise((resolve, reject) => {
const handleEvent = e => {
const api = e.detail;
if (config.expose_api_globally) {
window.api = api;
}
// imjoy plugin api
resolve(api);
window.removeEventListener("imjoy_remote_api_ready", handleEvent);
};
window.addEventListener("imjoy_remote_api_ready", handleEvent);
config = config || {};
config.dedicated_thread = false;
config.lang = "javascript";
config.api_version = API_VERSION;
config.isPlugin = true;
new Connection(config);
});
}
loadImJoyBasicApp({
base_url: "/",
debug: true,
process_url_query: true,
show_window_title: true,
show_progress_bar: true,
show_empty_window: true,
hide_about_imjoy: false,
menu_style: { position: "absolute", right: 0, top: 0, zIndex: 999 },
window_style: { width: "100%", height: "calc(100% - 30px)" },
main_container: null,
menu_container: "menu-container",
window_manager_container: "window-container",
imjoy_api: {},
}).then(async app => {
const api = app.imjoy.api;
const conn = new imjoyRPCWebRTC.Connection({channel: '123'});
const p = await app.imjoy.pm.connectPlugin(conn);
console.log('==============>', p)
p.api.setup('hello')
});
<script
type="text/javascript"
onload="imjoyRPCWebRTC.connectToPeer({channel: '123'}).then((api)=>{api.export({'setup': (msg)=>{document.body.insertAdjacentHTML( 'beforeend', msg );}})})"
src="http://localhost:9090/imjoy-rpc-webrtc.js"
></script>
<style>
body {
overscroll-behavior: contain;
}
</style>
/**
* Contains the routines loaded by the plugin iframe under web-browser
* in case when worker failed to initialize
*
* Initializes the web environment version of the platform-dependent
* connection object for the plugin site
*/
import { connectRPC } from "./pluginCore.js";
import { RPC, API_VERSION } from "./rpc.js";
import { MessageEmitter, randId, normalizeConfig } from "./utils.js";
import adapter from 'webrtc-adapter';
import { serialize, deserialize } from 'bson';
export { setupRPC, waitForInitialization } from "./main.js";
export { version as VERSION } from "../package.json";
export { RPC, API_VERSION };
export class Connection extends MessageEmitter {
constructor(config) {
super(config && config.debug);
this.config = config || {};
this.config.channel = this.config.channel || 'imjoy_webrtc_channel';
this.peer_id = this.config.channel;
this.dc = null
this.pc = null
this.broadcastChannel = new BroadcastChannel(this.config.channel);
this.broadcastChannel.onmessage = e => {
const msg = JSON.parse(e.data);
if(msg.sdp){
if(!this.pc || this.pc.iceConnectionState === 'closed' || this.pc.iceConnectionState === 'connected'){
this.initPeerConnection()
}
const pc = this.pc;
pc.setRemoteDescription(new RTCSessionDescription(msg.sdp))
.then(() => pc.signalingState == "stable" || pc.createAnswer()
.then(answer => pc.setLocalDescription(answer))
.then(() => this.broadcast({
sdp: pc.localDescription
})))
.catch(console.error)
}
else if(msg.candidate){
const pc = this.pc;
pc.addIceCandidate(new RTCIceCandidate(msg.candidate)).catch(console.error);
}
};
this.initPeerConnection()
if(this.config.isPlugin){
this.connect()
}
}
initDataChannel(dc) {
dc.onopen = e => {
if(this.config.isPlugin && dc.readyState ==='open'){
this._fire("connected");
this.once("initialize", () => {
if (!this.rpc) {
this.rpc = connectRPC(this, this.config);
} else {
this.rpc.once("remoteReady", () => {
// this.rpc.sendInterface();
const api = this.rpc.getRemote() || {};
window.dispatchEvent(
new CustomEvent("imjoy_remote_api_ready", { detail: api })
);
});
}
this.emit({
type: "initialized",
config: this.config,
origin: '*',
peer_id: this.peer_id
});
});
dc.send(serialize({
type: "imjoyRPCReady",
config: this.config,
peer_id: this.peer_id
}));
}
else{
dc.send(serialize({
type: "initialize",
config: this.config,
peer_id: this.peer_id
}))
}
};
dc.onclose = e => {this._fire("disconnected");};
dc.onmessage = e => {
const data = deserialize(e.data);
if (data.peer_id === this.peer_id) {
this._fire(data.type, data);
} else if (this.config.debug) {
console.log(
`connection peer id mismatch ${data.peer_id} !== ${this.peer_id}`
);
}
}
};
initPeerConnection(){
if(this.pc) this.pc.close();
const pc = new RTCPeerConnection()
pc.ondatachannel = e => this.initDataChannel(this.dc = e.channel);
pc.oniceconnectionstatechange = e => {
if(pc.iceConnectionState === 'connected'){
if(this.config.isPlugin){
this.dc = this.pc.createDataChannel("plugin-channel")
this.initDataChannel(this.dc);
}
}
console.log('peer connection: ', pc.iceConnectionState);
}
pc.onicecandidate = e => this.broadcast({
candidate: e.candidate
});
pc.onnegotiationneeded = e => pc.createOffer()
.then(offer => pc.setLocalDescription(offer))
.then(() => this.broadcast({
sdp: pc.localDescription
}))
.catch(console.error);
this.pc = pc;
}
broadcast(obj){
this.broadcastChannel.postMessage(JSON.stringify(obj));
}
connect() {
this.dc = this.pc.createDataChannel("plugin-channel")
this.initDataChannel(this.dc);
}
reset() {
this._event_handlers = {};
this._once_handlers = {};
}
execute() {
throw new Error("Execution is not allowed for socketio connection");
}
disconnect() {
this._fire("beforeDisconnect");
this.socket.disconnect();
this.init();
this._fire("disconnected");
}
emit(data) {
data.peer_id = this.peer_id;
this.dc.send(serialize(data));
}
}
export function connectToPeer(config) {
config = config || {};
config.name = config.name || randId();
config = normalizeConfig(config);
return new Promise((resolve, reject) => {
const handleEvent = e => {
const api = e.detail;
if (config.expose_api_globally) {
window.api = api;
}
// imjoy plugin api
resolve(api);
window.removeEventListener("imjoy_remote_api_ready", handleEvent);
};
window.addEventListener("imjoy_remote_api_ready", handleEvent);
config = config || {};
config.dedicated_thread = false;
config.lang = "javascript";
config.api_version = API_VERSION;
config.isPlugin = true;
new Connection(config);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment