Skip to content

Commit

Permalink
Merge pull request liriliri#39 from coolzjy/master
Browse files Browse the repository at this point in the history
feat: support rtc peer connection
  • Loading branch information
surunzi authored Oct 12, 2023
2 parents 7849216 + ca25d1b commit 0c499d4
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ public
devtools/.cipd
devtools/devtools-frontend
devtools/.gclient_entries
scripts/lib/__pycache__
script/lib/__pycache__
package-lock.json
1 change: 1 addition & 0 deletions patches/devtools-frontend/.patches
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ network.patch
application.patch
sources.patch
embedded.patch
feat_peer_connection.patch
184 changes: 184 additions & 0 deletions patches/devtools-frontend/feat_peer_connection.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Zhang Visper <[email protected]>
Date: Thu, 25 May 2023 13:42:27 +0800
Subject: feat: peer connection


diff --git a/front_end/core/sdk/Connections.ts b/front_end/core/sdk/Connections.ts
index 6a575078a98de2df5deacd02469b7c038f16696c..faeb15991702678fd929b335bcacc814f57e0c4c 100644
--- a/front_end/core/sdk/Connections.ts
+++ b/front_end/core/sdk/Connections.ts
@@ -75,6 +75,161 @@ export class MainConnection implements ProtocolClient.InspectorBackend.Connectio
}
}

+export class PeerConnection implements ProtocolClient.InspectorBackend.Connection {
+ #socket: WebSocket|null;
+ onMessage: ((arg0: (Object|string)) => void)|null;
+ #onDisconnect: ((arg0: string) => void)|null;
+ #onWebSocketDisconnect: (() => void)|null;
+ #messages: string[];
+ #connection: RTCPeerConnection|null;
+ #channel: RTCDataChannel|null;
+ #channelConnected: boolean;
+ constructor(url: string, onWebSocketDisconnect: () => void) {
+ this.#socket = new WebSocket(url);
+ this.#socket.onerror = this.onSocketError.bind(this);
+ this.#socket.onopen = this.onSocketOpen.bind(this);
+ this.#socket.onmessage = (messageEvent: MessageEvent<string>): void => {
+ const { type, data } = JSON.parse(messageEvent.data) as { type: string; data: any };
+ if (type === 'answer') {
+ this.#connection?.setRemoteDescription(data);
+ } else if (type === 'candidate') {
+ this.#connection?.addIceCandidate(data);
+ }
+ };
+ this.#socket.onclose = this.onSocketClose.bind(this);
+
+ this.onMessage = null;
+ this.#onDisconnect = null;
+ this.#onWebSocketDisconnect = onWebSocketDisconnect;
+ this.#messages = [];
+
+ const connection = this.#connection = new RTCPeerConnection({
+ iceServers: [{
+ urls: ['stun:stun.qq.com:3478']
+ }]
+ })
+
+ this.#channel = connection.createDataChannel('dev-tool');
+ this.#channel.onerror = this.onChannelError.bind(this);
+ this.#channel.onopen = this.onChannelOpen.bind(this);
+ this.#channel.onmessage = (messageEvent: MessageEvent<string>): void => {
+ if (this.onMessage) {
+ this.onMessage.call(null, messageEvent.data);
+ }
+ }
+ this.#channel.onclose = this.onChannelClose.bind(this);
+ this.#channelConnected = false;
+ connection.onicecandidate = (event) => {
+ if (event.candidate) {
+ this.#socket?.send(JSON.stringify({
+ type: 'candidate',
+ data: event.candidate,
+ }))
+ }
+ }
+ }
+
+ setOnMessage(onMessage: (arg0: (Object|string)) => void): void {
+ this.onMessage = onMessage;
+ }
+
+ setOnDisconnect(onDisconnect: (arg0: string) => void): void {
+ this.#onDisconnect = onDisconnect;
+ }
+
+ private onSocketError(): void {
+ if (this.#onWebSocketDisconnect) {
+ this.#onWebSocketDisconnect.call(null);
+ }
+ if (this.#onDisconnect) {
+ // This is called if error occurred while connecting.
+ this.#onDisconnect.call(null, 'connection failed');
+ }
+ this.closeSocket();
+ }
+
+ private onChannelError(): void {
+ this.closeChannel();
+ }
+
+ private async onSocketOpen(): Promise<void> {
+ if (this.#socket && this.#connection) {
+ this.#socket.onerror = console.error;
+ const offer = await this.#connection.createOffer();
+ await this.#connection.setLocalDescription(offer);
+ this.#socket.send(JSON.stringify({
+ type: 'offer',
+ data: offer
+ }));
+ }
+ }
+
+ private onChannelOpen(): void {
+ this.#channelConnected = true;
+ if (this.#channel) {
+ this.#channel.onerror = console.error;
+ for (const message of this.#messages) {
+ this.#channel.send(message);
+ }
+ }
+ this.#messages = [];
+ }
+
+ private onSocketClose(): void {
+ if (this.#onWebSocketDisconnect) {
+ this.#onWebSocketDisconnect.call(null);
+ }
+ if (this.#onDisconnect) {
+ this.#onDisconnect.call(null, 'websocket closed');
+ }
+ this.closeSocket();
+ }
+
+ private onChannelClose(): void {
+ this.closeChannel();
+ }
+
+ private closeSocket(callback?: (() => void)): void {
+ if (this.#socket) {
+ this.#socket.onerror = null;
+ this.#socket.onopen = null;
+ this.#socket.onclose = callback || null;
+ this.#socket.onmessage = null;
+ this.#socket.close();
+ this.#socket = null;
+ }
+ this.#onWebSocketDisconnect = null;
+ }
+
+ private closeChannel(callback?: (() => void)): void {
+ if(this.#channel) {
+ this.#channel.onerror = null;
+ this.#channel.onopen = null;
+ this.#channel.onclose = callback || null
+ this.#channel.onmessage = null;
+ this.#channel.close();
+ }
+ }
+
+ sendRawMessage(message: string): void {
+ if (this.#channelConnected && this.#channel) {
+ this.#channel.send(message);
+ } else {
+ this.#messages.push(message);
+ }
+ }
+
+ async disconnect(): Promise<void> {
+ await Promise.all([
+ new Promise<void>(this.closeSocket.bind(this)),
+ new Promise<void>(this.closeChannel.bind(this))
+ ])
+ if (this.#onDisconnect) {
+ this.#onDisconnect.call(null, 'force disconnect');
+ }
+ }
+}
+
export class EmbeddedConnection implements ProtocolClient.InspectorBackend.Connection {
onMessage: ((arg0: Object) => void) | null;
private targetOrigin: string = '';
@@ -306,6 +461,11 @@ function createMainConnection(websocketConnectionLost: () => void): ProtocolClie
const wsParam = Root.Runtime.Runtime.queryParam('ws');
const wssParam = Root.Runtime.Runtime.queryParam('wss');
const embeddedParam = Root.Runtime.Runtime.queryParam('embedded');
+ const rtc = Root.Runtime.Runtime.queryParam('rtc');
+ if (rtc === 'true') {
+ const ws = wsParam ? `ws://${wsParam}` : `wss://${wssParam}`;
+ return new PeerConnection(ws, websocketConnectionLost)
+ }
if (embeddedParam) {
return new EmbeddedConnection(embeddedParam)
}
1 change: 1 addition & 0 deletions server/lib/ChannelManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module.exports = class ChannelManager extends Emitter {
channel,
ip,
userAgent,
rtc: ws.rtc,
};

channel.on('close', () => this.removeTarget(id, title));
Expand Down
1 change: 1 addition & 0 deletions server/lib/WebSocketServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ module.exports = class WebSocketServer {
ws.title = q.title;
ws.favicon = q.favicon;
ws.userAgent = q.userAgent;
ws.rtc = q.rtc === 'true'
} else {
ws.target = q.target;
}
Expand Down
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ switch (os) {
break;
}

function inspect(id: string) {
function inspect(id: string, rtc: boolean) {
const { domain, basePath } = window;
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
const url =
location.protocol +
`//${domain}${basePath}front_end/chii_app.html?${protocol}=${domain}${basePath}client/${randomId(6)}?target=${id}`;
`//${domain}${basePath}front_end/chii_app.html?${protocol}=${encodeURIComponent(`${domain}${basePath}client/${randomId(6)}?target=${id}`)}&rtc=${rtc}`;
window.open(url, '_blank');
}

Expand Down Expand Up @@ -134,7 +134,7 @@ function render(targets: any[]) {
cursor: 'pointer',
},
onclick() {
inspect(target.id);
inspect(target.id, target.rtc);
},
},
'inspect'
Expand Down
9 changes: 7 additions & 2 deletions src/target.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { embedded } from './target/config';
import { embedded, rtc } from './target/config';
import connectRtc from './target/connectRtc';
import connectServer from './target/connectServer';
import connectIframe from './target/connectIframe';

if (!embedded) {
connectServer();
if (rtc) {
connectRtc()
} else {
connectServer();
}
} else {
connectIframe();
}
5 changes: 5 additions & 0 deletions src/target/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@ if (!startWith(serverUrl, 'http')) {
}

let embedded = false;
let rtc = false;
let cdn = '';

const element = getTargetScriptEl();
if (element) {
if (element.getAttribute('embedded') === 'true') {
embedded = true;
}
if (element.getAttribute('rtc') === 'true') {
rtc = true;
}
cdn = element.getAttribute('cdn') || '';
}

Expand All @@ -54,6 +58,7 @@ export {
// https://chii.liriliri.io/base/
serverUrl,
embedded,
rtc,
cdn,
id,
};
75 changes: 75 additions & 0 deletions src/target/connectRtc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import Socket from 'licia/Socket';
import query from 'licia/query';
import chobitsu from 'chobitsu';
import { serverUrl, id } from './config';
import { getFavicon } from './util';

export default async function () {
const proxy = `${serverUrl}proxy`;
chobitsu.domain('Page').setProxy({
proxy,
});
chobitsu.domain('Debugger').setProxy({
proxy,
});
chobitsu.domain('CSS').setProxy({
proxy,
});

const connection = new RTCPeerConnection({
iceServers: [
{
urls: ['stun:stun.qq.com:3478'],
},
],
});

connection.addEventListener('datachannel', event => {
const { channel } = event;

channel.onmessage = event => {
chobitsu.sendRawMessage(event.data);
};

chobitsu.setOnMessage((message: string) => {
channel.send(message);
});
});

const ws = new Socket(
`${serverUrl.replace(/^http/, 'ws')}target/${id}?${query.stringify({
url: location.href,
title: (window as any).ChiiTitle || document.title,
favicon: getFavicon(),
rtc: true,
})}`
);

connection.onicecandidate = event => {
if (event.candidate) {
ws.send(
JSON.stringify({
type: 'candidate',
data: event.candidate,
})
);
}
};

ws.on('message', async (event: MessageEvent<string>) => {
const { type, data } = JSON.parse(event.data) as { type: string; data: any };
if (type === 'offer') {
connection.setRemoteDescription(new RTCSessionDescription(data));
const answer = await connection.createAnswer();
connection.setLocalDescription(answer);
ws.send(
JSON.stringify({
type: 'answer',
data: answer,
})
);
} else if (type === 'candidate') {
connection.addIceCandidate(data);
}
});
}
3 changes: 3 additions & 0 deletions test/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ function injectTarget() {
if (location.href.indexOf('embedded=true') > -1) {
script.setAttribute('embedded', 'true');
}
if (location.href.indexOf('rtc=true') > -1) {
script.setAttribute('rtc', 'true')
}
if (location.href.indexOf('cdn=true') > -1) {
script.setAttribute('cdn', 'https://cdn.jsdelivr.net/npm/chii/public');
}
Expand Down

0 comments on commit 0c499d4

Please sign in to comment.