use tracker-list on client and improve interstitials

This commit is contained in:
CoolElectronics 2023-08-15 20:54:47 -04:00
parent 494ef6a4a9
commit 9ec17943c2
No known key found for this signature in database
GPG key ID: F63593D168636C50
9 changed files with 307 additions and 260 deletions

View file

@ -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
View 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>

View file

@ -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
View file

@ -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:

View file

@ -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();

View file

@ -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"));

View file

@ -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);
}

View file

@ -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));
})
});
}

View file

@ -12,7 +12,7 @@
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedLocals": false,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,