mirror of
https://github.com/MercuryWorkshop/adrift.git
synced 2025-05-13 06:10:01 -04:00
Merge branch 'master' of https://github.com/MercuryWorkshop/adrift
This commit is contained in:
commit
d307d9cd94
9 changed files with 7046 additions and 185 deletions
25
client/AdriftClient.ts
Normal file
25
client/AdriftClient.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
|
||||
import Connection from "./Connection";
|
||||
import { BareClient as BareClientCustom, registerRemoteListener, setBareClientImplementation, Client, GetRequestHeadersCallback, MetaCallback, ReadyStateCallback, WebSocketImpl, BareHeaders, BareResponse } from "bare-client-custom";
|
||||
|
||||
|
||||
// export class Adrift {
|
||||
// bareclient:AdriftBareClient,
|
||||
// constructor(connection:Connection){
|
||||
//
|
||||
// }
|
||||
// }
|
||||
//
|
||||
export class AdriftBareClient extends Client {
|
||||
constructor(private connection: Connection) {
|
||||
super();
|
||||
}
|
||||
async request(method: string, requestHeaders: BareHeaders, body: BodyInit | null, remote: URL, cache: string | undefined, duplex: string | undefined, signal: AbortSignal | undefined): Promise<BareResponse> {
|
||||
let rawResponse = await this.connection.httprequest({ a: "test data" });
|
||||
|
||||
return new Response(JSON.stringify(rawResponse)) as BareResponse;
|
||||
}
|
||||
async connect(remote: URL, protocols: string[], getRequestHeaders: GetRequestHeadersCallback, onMeta: MetaCallback, onReadyState: ReadyStateCallback, webSocketImpl: WebSocketImpl): WebSocket {
|
||||
|
||||
}
|
||||
}
|
135
client/App.tsx
135
client/App.tsx
|
@ -1,82 +1,93 @@
|
|||
import { openWindow, deleteWindow } from "corium";
|
||||
import { h, render, Component, Fragment } from 'preact';
|
||||
import { RTCConnection } from "./rtc";
|
||||
import { Component, Fragment, h, render } from "preact";
|
||||
import { RTCTransport } from "./RTCTransport";
|
||||
|
||||
|
||||
// import { setOffer, setCallback } from "./firebase";
|
||||
const _ = [h, render, Component, Fragment];
|
||||
import "./firebase";
|
||||
import { BareClient as BareClientCustom, registerRemoteListener, setBareClientImplementation, Client, GetRequestHeadersCallback, MetaCallback, ReadyStateCallback, WebSocketImpl, BareHeaders, BareResponse } from "bare-client-custom";
|
||||
|
||||
import { createBareClient } from "@tomphttp/bare-client";
|
||||
import { AdriftBareClient } from "./AdriftClient";
|
||||
import Connection from "./Connection";
|
||||
|
||||
let rtc = new RTCTransport(
|
||||
console.log,
|
||||
() => {
|
||||
// rtc.dataChannel.send("test message");
|
||||
|
||||
|
||||
class AdriftClient extends Client {
|
||||
|
||||
async request(method: string, requestHeaders: BareHeaders, body: BodyInit | null, remote: URL, cache: string | undefined, duplex: string | undefined, signal: AbortSignal | undefined): Promise<BareResponse> {
|
||||
return new Response("test") as BareResponse;
|
||||
}
|
||||
async connect(remote: URL, protocols: string[], getRequestHeaders: GetRequestHeadersCallback, onMeta: MetaCallback, onReadyState: ReadyStateCallback, webSocketImpl: WebSocketImpl): WebSocket {
|
||||
|
||||
}
|
||||
}
|
||||
// let client = new AdriftBareClient;
|
||||
// setBareClientImplementation(client);
|
||||
//
|
||||
|
||||
},
|
||||
console.log,
|
||||
console.log,
|
||||
console.log
|
||||
);
|
||||
let connection = new Connection(rtc);
|
||||
window["co"] = connection;
|
||||
// connection.httprequest({ a: 1, b: 2 });
|
||||
|
||||
let bare = new AdriftBareClient(connection);
|
||||
setBareClientImplementation(bare);
|
||||
registerRemoteListener();
|
||||
|
||||
|
||||
export default class App extends Component {
|
||||
rtc = new RTCConnection({
|
||||
onmessage: console.log,
|
||||
onopen: () => {
|
||||
this.rtc.dataChannel.send("test message");
|
||||
|
||||
let client = new AdriftClient;
|
||||
setBareClientImplementation(client);
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
state = {
|
||||
rtc = rtc;
|
||||
|
||||
state = {};
|
||||
|
||||
onInput = (e) => {
|
||||
const { value } = e.target;
|
||||
this.setState((prev) => ({ ...prev, answer: value }));
|
||||
};
|
||||
|
||||
onInput = e => {
|
||||
const { value } = e.target;
|
||||
this.setState(prev => ({ ...prev, answer: value }));
|
||||
}
|
||||
|
||||
render(props, state) {
|
||||
|
||||
|
||||
|
||||
// setCallback(this.rtc.answer.bind(this.rtc));
|
||||
return <>
|
||||
<div>
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
<div></div>
|
||||
<button
|
||||
onClick={async () => {
|
||||
let offer = await this.rtc.createOffer();
|
||||
console.log("offer created", offer);
|
||||
console.log(JSON.stringify(offer));
|
||||
|
||||
<button onClick={async () => {
|
||||
let offer = await this.rtc.createOffer();
|
||||
console.log("offer created", offer);
|
||||
const r = await fetch("http://localhost:3000/connect", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(offer),
|
||||
});
|
||||
if (r.status != 200) {
|
||||
throw new Error("connect: " + r.status + " " + r.statusText);
|
||||
}
|
||||
const { answer, candidates } = await r.json();
|
||||
await this.rtc.answer(answer, candidates);
|
||||
alert("connected");
|
||||
|
||||
// setOffer(JSON.stringify(offer));
|
||||
|
||||
// this.setState(prev => ({ ...prev, offer: JSON.stringify(offer) }));
|
||||
|
||||
|
||||
}}>create offer</button>
|
||||
<p>
|
||||
offer: <code>{state.offer}</code>
|
||||
</p>
|
||||
|
||||
paste answer: <input type="text" value={state.answer} onInput={this.onInput} />
|
||||
|
||||
<button onClick={async () => {
|
||||
let { answer, candidates } = JSON.parse(state.answer);
|
||||
await this.rtc.answer(answer, candidates);
|
||||
alert("connected");
|
||||
}}>
|
||||
connect
|
||||
</button>
|
||||
|
||||
</>;
|
||||
// setOffer(JSON.stringify(offer));
|
||||
|
||||
// this.setState(prev => ({ ...prev, offer: JSON.stringify(offer) }));
|
||||
}}
|
||||
>
|
||||
create offer
|
||||
</button>
|
||||
<p>
|
||||
offer: <code>{state.offer}</code>
|
||||
</p>
|
||||
paste answer:{" "}
|
||||
<input type="text" value={state.answer} onInput={this.onInput} />
|
||||
<button
|
||||
onClick={async () => {
|
||||
let { answer, candidates } = JSON.parse(state.answer);
|
||||
await this.rtc.answer(answer, candidates);
|
||||
alert("connected");
|
||||
}}
|
||||
>
|
||||
connect
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
96
client/Connection.ts
Normal file
96
client/Connection.ts
Normal file
|
@ -0,0 +1,96 @@
|
|||
import Transport from "./Transport";
|
||||
|
||||
|
||||
type ObjectValues<T> = T[keyof T];
|
||||
const C2SRequestTypes = {
|
||||
HTTPRequest: 0,
|
||||
WSOpen: 1,
|
||||
WSClose: 2,
|
||||
WSSendText: 3,
|
||||
WSSendBinary: 4,
|
||||
} as const;
|
||||
type C2SRequestType = ObjectValues<typeof C2SRequestTypes>
|
||||
|
||||
const S2CRequestTypes = {
|
||||
HTTPResponse: 0,
|
||||
WSOpen: 1,
|
||||
WSDataText: 2,
|
||||
WSDataBinary: 3,
|
||||
} as const;
|
||||
type S2CRequestType = ObjectValues<typeof S2CRequestTypes>
|
||||
|
||||
|
||||
|
||||
|
||||
export default class Connection {
|
||||
|
||||
|
||||
callbacks: Record<number, Function> = {};
|
||||
|
||||
counter: number = 0;
|
||||
|
||||
constructor(public transport: Transport) {
|
||||
|
||||
transport.ondata = this.ondata.bind(this);
|
||||
}
|
||||
|
||||
ondata(data: ArrayBuffer) {
|
||||
let cursor = 0;
|
||||
const view = new DataView(data);
|
||||
|
||||
let requestID = view.getUint16(cursor);
|
||||
cursor += 2;
|
||||
let requestType = view.getUint8(cursor) as S2CRequestType;
|
||||
cursor += 1;
|
||||
|
||||
console.log(requestID, requestType);
|
||||
|
||||
switch (requestType) {
|
||||
case S2CRequestTypes.HTTPResponse: {
|
||||
let decoder = new TextDecoder();
|
||||
let text = decoder.decode(data.slice(cursor));
|
||||
console.log(text);
|
||||
let json = JSON.parse(text);
|
||||
|
||||
console.log(requestID);
|
||||
|
||||
this.callbacks[requestID](json);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async send(data: ArrayBuffer | Blob, type: C2SRequestType): Promise<number> {
|
||||
|
||||
let requestID = this.counter++;
|
||||
|
||||
let header = new ArrayBuffer(2 + 1);
|
||||
let view = new DataView(header);
|
||||
|
||||
let cursor = 0;
|
||||
|
||||
view.setUint16(cursor, requestID);
|
||||
cursor += 2;
|
||||
view.setUint8(cursor, type);
|
||||
cursor += 1;
|
||||
|
||||
let buf = await new Blob([header, data]).arrayBuffer();
|
||||
|
||||
this.transport.send(buf);
|
||||
console.log(buf);
|
||||
|
||||
return requestID;
|
||||
|
||||
}
|
||||
|
||||
httprequest(data: object): Promise<object> {
|
||||
let json = JSON.stringify(data);
|
||||
|
||||
return new Promise(async (resolve) => {
|
||||
|
||||
let id = this.counter;
|
||||
this.callbacks[id] = resolve;
|
||||
await this.send(new Blob([json]), C2SRequestTypes.HTTPRequest);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
import Transport from "./Transport";
|
||||
import Connection from "./Connection";
|
||||
|
||||
const rtcConf = {
|
||||
iceServers: [
|
||||
{
|
||||
|
@ -23,56 +26,41 @@ interface RTCOptions {
|
|||
onmessage?
|
||||
}
|
||||
|
||||
export class RTCConnection {
|
||||
export class RTCTransport extends Transport {
|
||||
peer: RTCPeerConnection;
|
||||
|
||||
dataChannel: RTCDataChannel;
|
||||
|
||||
constructor(options: RTCOptions) {
|
||||
let {
|
||||
|
||||
onconnectionstatechange,
|
||||
onsignalingstatechange,
|
||||
oniceconnectionstatechange,
|
||||
onicegatheringstatechange,
|
||||
onopen,
|
||||
onclose,
|
||||
onmessage
|
||||
} = options;
|
||||
|
||||
constructor(onopen, onclose,
|
||||
public onconnectionstatechange: () => void,
|
||||
public onsignalingstatechange: () => void,
|
||||
public onicegatheringstatechange: () => void,
|
||||
) {
|
||||
super(onopen, onclose);
|
||||
this.peer = new RTCPeerConnection(rtcConf);
|
||||
this.peer.onconnectionstatechange = onconnectionstatechange;
|
||||
// (event) => {
|
||||
// console.log('Connection state:', this.peer.connectionState);
|
||||
// };
|
||||
this.peer.onsignalingstatechange = onsignalingstatechange;
|
||||
// (event) => {
|
||||
// console.log('Signaling state:', this.peer.signalingState);
|
||||
// };
|
||||
this.peer.oniceconnectionstatechange = oniceconnectionstatechange;
|
||||
// (event) => {
|
||||
// console.log('ICE connection state:', this.peer.iceConnectionState);
|
||||
// if (this.peer.iceConnectionState == "disconnected" || this.peer.iceConnectionState == "failed") {
|
||||
// console.log("disconnected");
|
||||
// // ondisconnect();
|
||||
// }
|
||||
// };
|
||||
this.peer.onicegatheringstatechange = onicegatheringstatechange;
|
||||
// (event) => {
|
||||
// console.log('ICE gathering state:', this.peer.iceGatheringState);
|
||||
// };
|
||||
|
||||
this.peer.onsignalingstatechange = onsignalingstatechange;
|
||||
|
||||
this.peer.oniceconnectionstatechange =
|
||||
(event) => {
|
||||
console.log('ICE connection state:', this.peer.iceConnectionState);
|
||||
if (this.peer.iceConnectionState == "disconnected" || this.peer.iceConnectionState == "failed") {
|
||||
console.log("disconnected");
|
||||
onclose();
|
||||
}
|
||||
};
|
||||
this.peer.onicegatheringstatechange = onicegatheringstatechange;
|
||||
this.dataChannel = this.peer.createDataChannel('host-server');
|
||||
this.dataChannel.onopen = onopen;
|
||||
// () => {
|
||||
// console.log("READY!!!");
|
||||
//
|
||||
// dataChannel.send("test data");
|
||||
// };
|
||||
|
||||
this.dataChannel.onclose = onclose;
|
||||
this.dataChannel.onmessage = onmessage;
|
||||
|
||||
this.dataChannel.onmessage = (event) => {
|
||||
this.ondata(event.data)
|
||||
};
|
||||
}
|
||||
|
||||
send(data: ArrayBuffer) {
|
||||
this.dataChannel.send(data);
|
||||
}
|
||||
|
||||
|
10
client/Transport.ts
Normal file
10
client/Transport.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export default abstract class Transport {
|
||||
public ondata: (data: ArrayBuffer) => void = () => { }
|
||||
constructor(
|
||||
public onopen: () => void,
|
||||
public onclose: () => void
|
||||
) { }
|
||||
|
||||
abstract send(data: ArrayBuffer): void;
|
||||
abstract send(data: ArrayBuffer): void;
|
||||
}
|
644
package-lock.json
generated
644
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -16,8 +16,15 @@
|
|||
"corium": "file:corium",
|
||||
"dotenv": "^16.3.1",
|
||||
"esbuild": "0.19.0",
|
||||
"express": "^4.18.2",
|
||||
"express-ws": "^5.0.2",
|
||||
"firebase": "^10.1.0",
|
||||
"preact": "^10.16.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"wrtc": "^0.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/express-ws": "^3.0.1"
|
||||
}
|
||||
}
|
||||
|
|
6183
pnpm-lock.yaml
generated
Normal file
6183
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,16 +1,8 @@
|
|||
import dotenv from "dotenv";
|
||||
import express from "express";
|
||||
import expressWs from "express-ws";
|
||||
import * as wrtc from "wrtc";
|
||||
|
||||
|
||||
import { app } from "../firebase-config";
|
||||
console.log(app);
|
||||
import { getDatabase, ref, onValue, set } from "firebase/database";
|
||||
|
||||
const db = getDatabase();
|
||||
let reff = ref(db, "/peers/demo");
|
||||
|
||||
|
||||
|
||||
const configuration = {
|
||||
iceServers: [
|
||||
{
|
||||
|
@ -20,7 +12,11 @@ const configuration = {
|
|||
};
|
||||
dotenv.config();
|
||||
|
||||
async function connect(offer, candidates) {
|
||||
async function connect(
|
||||
offer,
|
||||
candidates,
|
||||
onAnswer: (answer: Record<string, any>) => void
|
||||
) {
|
||||
const localCandidates: any[] = [];
|
||||
let dataChannel;
|
||||
const peer = new wrtc.RTCPeerConnection(configuration);
|
||||
|
@ -38,18 +34,19 @@ async function connect(offer, candidates) {
|
|||
};
|
||||
};
|
||||
peer.onconnectionstatechange = () => {
|
||||
console.log('Connection state:', peer.connectionState);
|
||||
console.log("Connection state:", peer.connectionState);
|
||||
};
|
||||
peer.onsignalingstatechange = () => {
|
||||
console.log('Signaling state:', peer.signalingState);
|
||||
console.log("Signaling state:", peer.signalingState);
|
||||
};
|
||||
peer.oniceconnectionstatechange = () => {
|
||||
console.log('ICE connection state:', peer.iceConnectionState);
|
||||
console.log("ICE connection state:", peer.iceConnectionState);
|
||||
};
|
||||
peer.onicegatheringstatechange = () => {
|
||||
console.log('ICE gathering state:', peer.iceGatheringState);
|
||||
console.log("ICE gathering state:", peer.iceGatheringState);
|
||||
};
|
||||
peer.onicecandidate = (event: any) => {
|
||||
console.log("onicecandidate");
|
||||
if (event.candidate) {
|
||||
localCandidates.push(event.candidate);
|
||||
return;
|
||||
|
@ -58,26 +55,46 @@ async function connect(offer, candidates) {
|
|||
answer: peer.localDescription,
|
||||
candidates: localCandidates,
|
||||
};
|
||||
let string = JSON.stringify(payload)
|
||||
|
||||
set(reff, string);
|
||||
onAnswer(payload);
|
||||
};
|
||||
await peer.setRemoteDescription(offer);
|
||||
let answer = await peer.createAnswer();
|
||||
await peer.setLocalDescription(answer);
|
||||
for (let candidate of candidates) {
|
||||
if (!candidate.candidate) continue;
|
||||
console.log({ candidate });
|
||||
await peer.addIceCandidate(candidate);
|
||||
}
|
||||
}
|
||||
onValue(reff, (snapshot) => {
|
||||
const rawdata = snapshot.val();
|
||||
|
||||
let data = JSON.parse(rawdata);
|
||||
console.log(data);
|
||||
const app = express() as unknown as expressWs.Application;
|
||||
expressWs(app);
|
||||
|
||||
app.use(express.json());
|
||||
app.use((req, res, next) => {
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
next();
|
||||
});
|
||||
|
||||
app.post("/connect", (req, res) => {
|
||||
const data = req.body;
|
||||
if (data && data.offer && data.localCandidates) {
|
||||
const { offer, localCandidates } = data;
|
||||
connect(offer, localCandidates);
|
||||
let didAnswer = false;
|
||||
connect(offer, localCandidates, (answer) => {
|
||||
if (!didAnswer) {
|
||||
didAnswer = true;
|
||||
res.json(answer);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.ws("/dev-ws", (ws, req) => {
|
||||
console.log("ws connect");
|
||||
ws.on("message", (msg) => {
|
||||
console.log({ msg });
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(3000, () => console.log("listening"));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue