mirror of
https://github.com/MercuryWorkshop/adrift.git
synced 2025-05-12 22:00:02 -04:00
use tracker-list on client and improve interstitials
This commit is contained in:
parent
494ef6a4a9
commit
9ec17943c2
9 changed files with 307 additions and 260 deletions
|
@ -14,19 +14,19 @@
|
|||
import {
|
||||
Button,
|
||||
Card,
|
||||
SegmentedButtonContainer,
|
||||
SegmentedButtonItem,
|
||||
CircularProgressIndeterminate,
|
||||
StyleFromScheme,
|
||||
TextField,
|
||||
} from "m3-svelte";
|
||||
// note: even though we import firebase, due to the tree shaking, it will only run if we use "auth" so if ADRIFT_DEV is set it won't import
|
||||
// import { auth } from "firebase-config";
|
||||
import { signInWithEmailAndPassword } from "firebase/auth";
|
||||
|
||||
import { getAuth, signInWithEmailAndPassword } from "firebase/auth";
|
||||
import { getDatabase, onValue, ref, set } from "firebase/database";
|
||||
import type { Transport } from "protocol";
|
||||
|
||||
import { Win, openWindow } from "../../corium";
|
||||
import Proxy from "./Proxy.svelte";
|
||||
import { initializeApp } from "firebase/app";
|
||||
|
||||
import TrackerList from "tracker-list";
|
||||
let transport: Transport;
|
||||
|
||||
let rtctransport: RTCTransport | undefined;
|
||||
|
@ -34,26 +34,7 @@
|
|||
let email = "test@test.com";
|
||||
let password = "123456";
|
||||
|
||||
let ready = false;
|
||||
|
||||
let selectedProxy = "ultraviolet";
|
||||
|
||||
let url: string = "http://google.com";
|
||||
let proxyIframe: HTMLIFrameElement;
|
||||
|
||||
let rtcState = "";
|
||||
|
||||
if (import.meta.env.VITE_ADRIFT_DEV) {
|
||||
console.log(
|
||||
"%cADRIFT RUNNING IN DEVELOPMENT MODE",
|
||||
"background: blue; color: white; font-size: x-large"
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
"%cADRIFT RUNNING IN PRODUCTION MODE",
|
||||
"background: blue; color: white; font-size: x-large"
|
||||
);
|
||||
}
|
||||
let connectionState = "";
|
||||
|
||||
if (!import.meta.env.VITE_ADRIFT_SINGLEFILE) {
|
||||
console.log("registering bare-client-custom");
|
||||
|
@ -67,7 +48,7 @@
|
|||
let bare = new AdriftBareClient(connection);
|
||||
console.log(setBareClientImplementation);
|
||||
setBareClientImplementation(bare);
|
||||
ready = true;
|
||||
state = ReadyState.Connected;
|
||||
}
|
||||
|
||||
function onTransportClose() {
|
||||
|
@ -79,31 +60,41 @@
|
|||
onTransportOpen,
|
||||
onTransportClose,
|
||||
() => {
|
||||
rtcState = `Connection ${transport.peer.connectionState}`;
|
||||
connectionState = `Connection ${transport.peer.connectionState}`;
|
||||
},
|
||||
() => {
|
||||
rtcState = `Signaling ${transport.peer.connectionState}`;
|
||||
connectionState = `Signaling ${transport.peer.connectionState}`;
|
||||
},
|
||||
() => {
|
||||
rtcState = `Gathering ${transport.peer.connectionState}`;
|
||||
connectionState = `Gathering ${transport.peer.connectionState}`;
|
||||
}
|
||||
);
|
||||
return transport;
|
||||
}
|
||||
|
||||
async function connectFirebase() {
|
||||
async function initFirebase() {
|
||||
let tracker = TrackerList["us-central-1"];
|
||||
initializeApp(tracker.firebase);
|
||||
}
|
||||
|
||||
async function connectAccount() {
|
||||
await initFirebase();
|
||||
rtctransport = transport = createRTCTransport();
|
||||
|
||||
let auth = getAuth();
|
||||
let creds = await signInWithEmailAndPassword(auth, email, password);
|
||||
state = ReadyState.Connecting;
|
||||
|
||||
const db = getDatabase();
|
||||
let peer = ref(db, `/peers/${creds.user.uid}`);
|
||||
|
||||
let offer = await rtctransport.createOffer();
|
||||
|
||||
connectionState = "Finding your node...";
|
||||
|
||||
set(peer, JSON.stringify(offer));
|
||||
|
||||
onValue(peer, (snapshot) => {
|
||||
onValue(peer, async (snapshot) => {
|
||||
const str = snapshot.val();
|
||||
if (str) {
|
||||
console.log(str);
|
||||
|
@ -112,6 +103,10 @@
|
|||
if (data && data.answer && data.candidates) {
|
||||
set(peer, null);
|
||||
const { answer, candidates } = data;
|
||||
connectionState = "Linking to node...";
|
||||
await new Promise((r) => {
|
||||
setTimeout(r, 500);
|
||||
});
|
||||
rtctransport?.answer(answer, candidates);
|
||||
}
|
||||
}
|
||||
|
@ -119,10 +114,20 @@
|
|||
}
|
||||
|
||||
async function connectSwarm() {
|
||||
await initFirebase();
|
||||
|
||||
state = ReadyState.Connecting;
|
||||
|
||||
rtctransport = transport = createRTCTransport();
|
||||
|
||||
let offer = await rtctransport.createOffer();
|
||||
connectionState = "Routing you to an available node...";
|
||||
|
||||
let answer = await SignalFirebase.signalSwarm(JSON.stringify(offer));
|
||||
connectionState = "Linking to node...";
|
||||
await new Promise((r) => {
|
||||
setTimeout(r, 500);
|
||||
});
|
||||
|
||||
rtctransport.answer(answer.answer, answer.candidates);
|
||||
}
|
||||
|
@ -151,36 +156,6 @@
|
|||
);
|
||||
}
|
||||
|
||||
function visitURL(url: string) {
|
||||
if (!import.meta.env.VITE_ADRIFT_SINGLEFILE) {
|
||||
let path =
|
||||
selectedProxy == "dynamic"
|
||||
? `/service/route?url=${url}`
|
||||
: `${__uv$config.prefix}${__uv$config.encodeUrl(url)}`;
|
||||
|
||||
proxyIframe.src = path;
|
||||
} else {
|
||||
let bare = new BareClient();
|
||||
openWindow(
|
||||
new Request(url),
|
||||
"_self",
|
||||
proxyIframe.contentWindow! as unknown as Win,
|
||||
bare as any,
|
||||
"replace"
|
||||
);
|
||||
}
|
||||
}
|
||||
function frameLoad() {
|
||||
if (!import.meta.env.VITE_ADRIFT_SINGLEFILE) {
|
||||
const location = proxyIframe.contentDocument?.location.href;
|
||||
if (location && location != "about:blank") {
|
||||
url = __uv$config.decodeUrl(
|
||||
proxyIframe.contentDocument?.location.href.replace(/.*\//g, "")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).bare = new BareClient();
|
||||
(window as any).myWsTest = () => {
|
||||
// const url = "wss://ws.postman-echo.com/raw";
|
||||
|
@ -194,42 +169,22 @@
|
|||
ws.addEventListener("close", (e) => console.log("close listener", e));
|
||||
ws.onmessage = (e) => console.log("message", e);
|
||||
};
|
||||
|
||||
enum ReadyState {
|
||||
Idle,
|
||||
Connecting,
|
||||
Connected,
|
||||
}
|
||||
let state = ReadyState.Idle;
|
||||
</script>
|
||||
|
||||
{#if ready}
|
||||
<div class="container h-full w-full">
|
||||
<div class="flex">
|
||||
<div class="container">
|
||||
<input bind:value={url} type="text" />
|
||||
<button on:click={() => visitURL(url)}>Go!</button>
|
||||
</div>
|
||||
{#if !import.meta.env.VITE_ADRIFT_SINGLEFILE}
|
||||
<div>
|
||||
<SegmentedButtonContainer>
|
||||
<input
|
||||
type="radio"
|
||||
name="selectedProxy"
|
||||
bind:group={selectedProxy}
|
||||
value="ultraviolet"
|
||||
id="ultraviolet"
|
||||
/>
|
||||
<SegmentedButtonItem input="ultraviolet"
|
||||
>Ultraviolet</SegmentedButtonItem
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="selectedProxy"
|
||||
bind:group={selectedProxy}
|
||||
value="dynamic"
|
||||
id="dynamic"
|
||||
/>
|
||||
<SegmentedButtonItem input="dynamic">Dynamic</SegmentedButtonItem>
|
||||
</SegmentedButtonContainer>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<iframe class="h-full w-full" bind:this={proxyIframe} on:load={frameLoad} />
|
||||
</div>
|
||||
{#if state == ReadyState.Connected}
|
||||
<Proxy />
|
||||
{:else if state == ReadyState.Connecting}
|
||||
<CircularProgressIndeterminate />
|
||||
<h2>
|
||||
{connectionState}
|
||||
</h2>
|
||||
{:else if !import.meta.env.VITE_ADRIFT_DEV}
|
||||
<div id="loginpage">
|
||||
<div class="bigcard">
|
||||
|
@ -247,15 +202,15 @@
|
|||
extraOptions={{ type: "password" }}
|
||||
/>
|
||||
|
||||
<Button type="outlined" on:click={connectFirebase}
|
||||
<Button type="outlined" on:click={connectAccount}
|
||||
>Connect with firebase</Button
|
||||
>
|
||||
|
||||
<Button type="filled" on:click={connectSwarm}
|
||||
>Connect with the swarm (firebase, webrtc, insecure)
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<h2>
|
||||
{rtcState}
|
||||
</h2>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex items-center justify-center h-full">
|
||||
|
@ -269,15 +224,7 @@
|
|||
<Button type="filled" on:click={connectDevWS}
|
||||
>Connect with localhost websocket transport</Button
|
||||
>
|
||||
|
||||
<Button type="filled" on:click={connectSwarm}
|
||||
>Connect with the swarm (webrtc, insecure)
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<h2>
|
||||
{rtcState}
|
||||
</h2>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
76
frontend/src/Proxy.svelte
Normal file
76
frontend/src/Proxy.svelte
Normal file
|
@ -0,0 +1,76 @@
|
|||
<script lang="ts">
|
||||
import { BareClient } from "bare-client-custom";
|
||||
import { SegmentedButtonContainer, SegmentedButtonItem } from "m3-svelte";
|
||||
import { Win, openWindow } from "../../corium";
|
||||
|
||||
let selectedProxy = "ultraviolet";
|
||||
|
||||
let url: string = "http://google.com";
|
||||
|
||||
let proxyIframe: HTMLIFrameElement;
|
||||
|
||||
function frameLoad() {
|
||||
if (!import.meta.env.VITE_ADRIFT_SINGLEFILE) {
|
||||
const location = proxyIframe.contentDocument?.location.href;
|
||||
if (location && location != "about:blank") {
|
||||
url = __uv$config.decodeUrl(
|
||||
proxyIframe.contentDocument?.location.href.replace(/.*\//g, "")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function visitURL(url: string) {
|
||||
if (!import.meta.env.VITE_ADRIFT_SINGLEFILE) {
|
||||
let path =
|
||||
selectedProxy == "dynamic"
|
||||
? `/service/route?url=${url}`
|
||||
: `${__uv$config.prefix}${__uv$config.encodeUrl(url)}`;
|
||||
|
||||
proxyIframe.src = path;
|
||||
} else {
|
||||
let bare = new BareClient();
|
||||
openWindow(
|
||||
new Request(url),
|
||||
"_self",
|
||||
proxyIframe.contentWindow! as unknown as Win,
|
||||
bare as any,
|
||||
"replace"
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container h-full w-full">
|
||||
<div class="flex">
|
||||
<div class="container">
|
||||
<input bind:value={url} type="text" />
|
||||
<button on:click={() => visitURL(url)}>Go!</button>
|
||||
</div>
|
||||
{#if !import.meta.env.VITE_ADRIFT_SINGLEFILE}
|
||||
<div>
|
||||
<SegmentedButtonContainer>
|
||||
<input
|
||||
type="radio"
|
||||
name="selectedProxy"
|
||||
bind:group={selectedProxy}
|
||||
value="ultraviolet"
|
||||
id="ultraviolet"
|
||||
/>
|
||||
<SegmentedButtonItem input="ultraviolet"
|
||||
>Ultraviolet</SegmentedButtonItem
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="selectedProxy"
|
||||
bind:group={selectedProxy}
|
||||
value="dynamic"
|
||||
id="dynamic"
|
||||
/>
|
||||
<SegmentedButtonItem input="dynamic">Dynamic</SegmentedButtonItem>
|
||||
</SegmentedButtonContainer>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<iframe class="h-full w-full" bind:this={proxyIframe} on:load={frameLoad} />
|
||||
</div>
|
|
@ -1,5 +1,16 @@
|
|||
import App from "./App.svelte";
|
||||
import "./index.css";
|
||||
if (import.meta.env.VITE_ADRIFT_DEV) {
|
||||
console.log(
|
||||
"%cADRIFT RUNNING IN DEVELOPMENT MODE",
|
||||
"background: blue; color: white; font-size: x-large"
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
"%cADRIFT RUNNING IN PRODUCTION MODE",
|
||||
"background: blue; color: white; font-size: x-large"
|
||||
);
|
||||
}
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById("app")!,
|
||||
|
|
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
|
@ -286,12 +286,6 @@ importers:
|
|||
specifier: ^5.0.1
|
||||
version: 5.0.1(webpack@5.75.0)
|
||||
|
||||
firebase-config:
|
||||
dependencies:
|
||||
firebase:
|
||||
specifier: ^10.1.0
|
||||
version: 10.1.0(react-native@0.72.3)
|
||||
|
||||
frontend:
|
||||
dependencies:
|
||||
'@rollup/browser':
|
||||
|
@ -321,9 +315,6 @@ importers:
|
|||
firebase:
|
||||
specifier: ^10.1.0
|
||||
version: 10.1.0(react-native@0.72.3)
|
||||
firebase-config:
|
||||
specifier: workspace:*
|
||||
version: link:../firebase-config
|
||||
m3-svelte:
|
||||
specifier: ^2.0.3
|
||||
version: 2.0.3
|
||||
|
@ -345,6 +336,9 @@ importers:
|
|||
tailwindcss:
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3
|
||||
tracker-list:
|
||||
specifier: workspace:*
|
||||
version: link:../tracker-list
|
||||
typescript:
|
||||
specifier: ^5.1.6
|
||||
version: 5.1.6
|
||||
|
@ -377,9 +371,6 @@ importers:
|
|||
firebase:
|
||||
specifier: ^10.1.0
|
||||
version: 10.1.0(react-native@0.72.3)
|
||||
firebase-config:
|
||||
specifier: workspace:*
|
||||
version: link:../firebase-config
|
||||
ipaddr.js:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0
|
||||
|
@ -389,6 +380,9 @@ importers:
|
|||
protocol:
|
||||
specifier: workspace:*
|
||||
version: link:../protocol
|
||||
tracker-list:
|
||||
specifier: workspace:*
|
||||
version: link:../tracker-list
|
||||
ts-node:
|
||||
specifier: ^10.9.1
|
||||
version: 10.9.1(@types/node@20.4.10)(typescript@5.1.6)
|
||||
|
@ -435,9 +429,9 @@ importers:
|
|||
firebase-admin:
|
||||
specifier: ^11.10.1
|
||||
version: 11.10.1
|
||||
firebase-config:
|
||||
tracker-list:
|
||||
specifier: workspace:*
|
||||
version: link:../firebase-config
|
||||
version: link:../tracker-list
|
||||
ts-node:
|
||||
specifier: ^10.9.1
|
||||
version: 10.9.1(@types/node@20.4.10)(typescript@5.1.6)
|
||||
|
@ -464,6 +458,8 @@ importers:
|
|||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
|
||||
tracker-list: {}
|
||||
|
||||
packages:
|
||||
|
||||
/@aashutoshrathi/word-wrap@1.2.6:
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
import { getAuth, signInWithEmailAndPassword } from "firebase/auth";
|
||||
|
||||
|
||||
import { getDatabase, onValue, ref, set } from "firebase/database";
|
||||
import { answerRtc } from "./rtc";
|
||||
|
||||
async function connectFirebase() {
|
||||
let creds = await signInWithEmailAndPassword(getAuth(), "test@test.com", "123456");
|
||||
|
||||
const db = getDatabase();
|
||||
let peer = ref(db, `/peers/${creds.user.uid}`);
|
||||
|
||||
set(peer, "");
|
||||
|
||||
onValue(peer, (snapshot) => {
|
||||
const str = snapshot.val();
|
||||
|
||||
if (str) {
|
||||
let data = JSON.parse(str);
|
||||
if (data && data.offer && data.localCandidates) {
|
||||
answerRtc(data, (answer) => {
|
||||
console.log("answering");
|
||||
set(peer, JSON.stringify(answer));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
connectFirebase();
|
|
@ -2,72 +2,13 @@ import dotenv from "dotenv";
|
|||
import express from "express";
|
||||
import expressWs from "express-ws";
|
||||
|
||||
import { signInWithEmailAndPassword } from "firebase/auth";
|
||||
import wrtc from "wrtc";
|
||||
|
||||
|
||||
import { getDatabase, onValue, ref, set } from "firebase/database";
|
||||
import { AdriftServer } from "./server";
|
||||
import { AdriftServer, connectTracker } from "./server";
|
||||
|
||||
import WebSocket from "isomorphic-ws";
|
||||
const configuration = {
|
||||
iceServers: [
|
||||
{
|
||||
urls: "stun:stun.l.google.com:19302",
|
||||
},
|
||||
],
|
||||
};
|
||||
import { answerRtc, bufferToArrayBuffer, connect } from "./rtc";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
async function connect(
|
||||
offer: RTCSessionDescriptionInit,
|
||||
candidates: RTCIceCandidateInit[],
|
||||
onAnswer: (answer: Record<string, any>) => void
|
||||
): Promise<RTCDataChannel> {
|
||||
const localCandidates: any[] = [];
|
||||
let dataChannel;
|
||||
const peer: RTCPeerConnection = new wrtc.RTCPeerConnection(configuration);
|
||||
let promise = new Promise((resolve) => {
|
||||
peer.ondatachannel = (event) => {
|
||||
dataChannel = event.channel;
|
||||
resolve(dataChannel);
|
||||
};
|
||||
});
|
||||
peer.onconnectionstatechange = () => {
|
||||
console.log("Connection state:", peer.connectionState);
|
||||
};
|
||||
peer.onsignalingstatechange = () => {
|
||||
console.log("Signaling state:", peer.signalingState);
|
||||
};
|
||||
peer.oniceconnectionstatechange = () => {
|
||||
console.log("ICE connection state:", peer.iceConnectionState);
|
||||
};
|
||||
peer.onicegatheringstatechange = () => {
|
||||
console.log("ICE gathering state:", peer.iceGatheringState);
|
||||
};
|
||||
peer.onicecandidate = (event: any) => {
|
||||
console.log("onicecandidate");
|
||||
if (event.candidate) {
|
||||
localCandidates.push(event.candidate);
|
||||
return;
|
||||
}
|
||||
let payload = {
|
||||
answer: peer.localDescription,
|
||||
candidates: localCandidates,
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
return promise as any;
|
||||
}
|
||||
|
||||
const app = express() as unknown as expressWs.Application;
|
||||
expressWs(app);
|
||||
|
@ -82,48 +23,8 @@ app.use((_req, res, next) => {
|
|||
next();
|
||||
});
|
||||
|
||||
function bufferToArrayBuffer(buf: Buffer): ArrayBuffer {
|
||||
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
||||
}
|
||||
|
||||
async function answerRtc(data: any, onrespond: (answer: any) => void) {
|
||||
if (data && data.offer && data.localCandidates) {
|
||||
const { offer, localCandidates } = data;
|
||||
let didAnswer = false;
|
||||
|
||||
let dataChannel = await connect(offer, localCandidates, (answer) => {
|
||||
if (!didAnswer) {
|
||||
didAnswer = true;
|
||||
onrespond(answer);
|
||||
// res.json(answer);
|
||||
}
|
||||
});
|
||||
dataChannel.binaryType = "arraybuffer";
|
||||
|
||||
let server: AdriftServer;
|
||||
|
||||
dataChannel.onopen = () => {
|
||||
console.log("opened");
|
||||
server = new AdriftServer((msg) => dataChannel.send(msg));
|
||||
};
|
||||
dataChannel.onclose = () => {
|
||||
console.log("closed");
|
||||
server.onClose();
|
||||
};
|
||||
dataChannel.onmessage = (event) => {
|
||||
console.log("messaged");
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
server.onMsg(event.data);
|
||||
return;
|
||||
}
|
||||
if (event.data instanceof Buffer) {
|
||||
server.onMsg(bufferToArrayBuffer(event.data));
|
||||
return;
|
||||
}
|
||||
throw new Error("Unexpected datachannel message type");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
app.post("/connect", (req, res) => {
|
||||
const data = req.body;
|
||||
|
@ -149,42 +50,12 @@ app.ws("/dev-ws", (ws, _req) => {
|
|||
});
|
||||
});
|
||||
|
||||
async function connectFirebase() {
|
||||
let creds = await signInWithEmailAndPassword(auth, "test@test.com", "123456");
|
||||
try {
|
||||
|
||||
const db = getDatabase();
|
||||
let peer = ref(db, `/peers/${creds.user.uid}`);
|
||||
let tracker = new WebSocket("ws://localhost:17776/join");
|
||||
connectTracker(tracker);
|
||||
} catch (_) {
|
||||
|
||||
set(peer, "");
|
||||
|
||||
onValue(peer, (snapshot) => {
|
||||
const str = snapshot.val();
|
||||
|
||||
if (str) {
|
||||
let data = JSON.parse(str);
|
||||
if (data && data.offer && data.localCandidates) {
|
||||
answerRtc(data, (answer) => {
|
||||
console.log("answering");
|
||||
set(peer, JSON.stringify(answer));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
connectFirebase();
|
||||
|
||||
let tracker = new WebSocket("ws://localhost:17776/join");
|
||||
tracker.on("message", (str: string) => {
|
||||
if (!str) return;
|
||||
let data = JSON.parse(str);
|
||||
if (!(data && data.offer && data.localCandidates)) return;
|
||||
console.log("got offer");
|
||||
|
||||
answerRtc(data, (answer) => {
|
||||
console.log("have an answer");
|
||||
tracker.send(JSON.stringify(answer));
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
app.listen(3000, () => console.log("listening"));
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
const configuration = {
|
||||
iceServers: [
|
||||
{
|
||||
urls: "stun:stun.l.google.com:19302",
|
||||
},
|
||||
],
|
||||
};
|
||||
import wrtc from "wrtc";
|
||||
import { AdriftServer } from "./server";
|
||||
|
||||
export async function connect(
|
||||
offer: RTCSessionDescriptionInit,
|
||||
candidates: RTCIceCandidateInit[],
|
||||
onAnswer: (answer: Record<string, any>) => void
|
||||
): Promise<RTCDataChannel> {
|
||||
const localCandidates: any[] = [];
|
||||
let dataChannel;
|
||||
const peer: RTCPeerConnection = new wrtc.RTCPeerConnection(configuration);
|
||||
let promise = new Promise((resolve) => {
|
||||
peer.ondatachannel = (event) => {
|
||||
dataChannel = event.channel;
|
||||
resolve(dataChannel);
|
||||
};
|
||||
});
|
||||
peer.onconnectionstatechange = () => {
|
||||
console.log("Connection state:", peer.connectionState);
|
||||
};
|
||||
peer.onsignalingstatechange = () => {
|
||||
console.log("Signaling state:", peer.signalingState);
|
||||
};
|
||||
peer.oniceconnectionstatechange = () => {
|
||||
console.log("ICE connection state:", peer.iceConnectionState);
|
||||
};
|
||||
peer.onicegatheringstatechange = () => {
|
||||
console.log("ICE gathering state:", peer.iceGatheringState);
|
||||
};
|
||||
peer.onicecandidate = (event: any) => {
|
||||
console.log("onicecandidate");
|
||||
if (event.candidate) {
|
||||
localCandidates.push(event.candidate);
|
||||
return;
|
||||
}
|
||||
let payload = {
|
||||
answer: peer.localDescription,
|
||||
candidates: localCandidates,
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
return promise as any;
|
||||
}
|
||||
export async function answerRtc(data: any, onrespond: (answer: any) => void) {
|
||||
if (data && data.offer && data.localCandidates) {
|
||||
const { offer, localCandidates } = data;
|
||||
let didAnswer = false;
|
||||
|
||||
let dataChannel = await connect(offer, localCandidates, (answer) => {
|
||||
if (!didAnswer) {
|
||||
didAnswer = true;
|
||||
onrespond(answer);
|
||||
// res.json(answer);
|
||||
}
|
||||
});
|
||||
dataChannel.binaryType = "arraybuffer";
|
||||
|
||||
let server: AdriftServer;
|
||||
|
||||
dataChannel.onopen = () => {
|
||||
console.log("opened");
|
||||
server = new AdriftServer((msg) => dataChannel.send(msg));
|
||||
};
|
||||
dataChannel.onclose = () => {
|
||||
console.log("closed");
|
||||
server.onClose();
|
||||
};
|
||||
dataChannel.onmessage = (event) => {
|
||||
console.log("messaged");
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
server.onMsg(event.data);
|
||||
return;
|
||||
}
|
||||
if (event.data instanceof Buffer) {
|
||||
server.onMsg(bufferToArrayBuffer(event.data));
|
||||
return;
|
||||
}
|
||||
throw new Error("Unexpected datachannel message type");
|
||||
};
|
||||
}
|
||||
}
|
||||
export function bufferToArrayBuffer(buf: Buffer): ArrayBuffer {
|
||||
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
||||
}
|
|
@ -14,6 +14,8 @@ import {
|
|||
} from "protocol";
|
||||
import { Readable, Writable } from "stream";
|
||||
import { BareError, bareInitialFetch, fetchResponse, options } from "./http";
|
||||
import { answerRtc } from "./rtc";
|
||||
|
||||
|
||||
function bareErrorToResponse(e: BareError): {
|
||||
payload: HTTPResponsePayload;
|
||||
|
@ -369,3 +371,17 @@ export class AdriftServer {
|
|||
this.events.emit("close");
|
||||
}
|
||||
}
|
||||
|
||||
export function connectTracker(tracker: WebSocket) {
|
||||
tracker.on("message", (str: string) => {
|
||||
if (!str) return;
|
||||
let data = JSON.parse(str);
|
||||
if (!(data && data.offer && data.localCandidates)) return;
|
||||
console.log("got offer");
|
||||
|
||||
answerRtc(data, (answer) => {
|
||||
console.log("have an answer");
|
||||
tracker.send(JSON.stringify(answer));
|
||||
})
|
||||
});
|
||||
}
|
|
@ -12,7 +12,7 @@
|
|||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue