wip v2
This commit is contained in:
13
src/IDUtils.ts
Normal file
13
src/IDUtils.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
function uuidv4() {
|
||||
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c: any) =>
|
||||
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
||||
);
|
||||
}
|
||||
|
||||
export function generateID() {
|
||||
if (self.crypto.hasOwnProperty("randomUUID")) {
|
||||
return self.crypto.randomUUID();
|
||||
}
|
||||
|
||||
return uuidv4();
|
||||
}
|
||||
544
src/PeerManager.ts
Normal file
544
src/PeerManager.ts
Normal file
@@ -0,0 +1,544 @@
|
||||
// connect to WS server, send info, connecto to bootstrap peer
|
||||
// once connected to bootstrap peer,
|
||||
|
||||
// Goal, connect to bootstrap peer, ask bootstrap peer for peers that have posts from users that we care about. get peers, connect to those peers, sync.
|
||||
// how? do "perfect negotiation" with bootstrap peer. All logic here moves to BP.
|
||||
|
||||
import { generateID } from "IDUtils";
|
||||
|
||||
// Use a broadcast channel to only have one peer manager for multiple tabs,
|
||||
// then we won't need to have a session ID as all queries for a peerID will be coming from the same peer manager
|
||||
|
||||
export class PeerManager {
|
||||
routingTable: Map<string, string>;
|
||||
|
||||
private peers: Map<string, PeerConnection>;
|
||||
private signaler: Signaler;
|
||||
searchQueryFunctions: Map<string, Function> = new Map();
|
||||
RPC_remote: Map<string, Function> = new Map();
|
||||
rpc: { [key: string]: Function } = {};
|
||||
isBootstrapPeer: boolean = false;
|
||||
bootstrapPeerConnection: PeerConnection | null = null;
|
||||
|
||||
async onConnected(bootstrapPeerID: string) {
|
||||
this.bootstrapPeerConnection = await this.connect(bootstrapPeerID);
|
||||
}
|
||||
|
||||
constructor(userID: string, peerID: string, isBootstrapPeer: boolean) {
|
||||
this.isBootstrapPeer = isBootstrapPeer;
|
||||
this.peers = new Map();
|
||||
this.routingTable = new Map();
|
||||
|
||||
|
||||
this.signaler = new Signaler(userID, peerID, isBootstrapPeer, this.onConnected.bind(this));
|
||||
|
||||
// Testing
|
||||
let dummyPeer = new PeerConnection(this, "dummy_peer", this.signaler);
|
||||
this.peers.set("dummy_peer", dummyPeer);
|
||||
}
|
||||
|
||||
async connect(remotePeerID: string) {
|
||||
// Connect to the peer that has the peer id peerID
|
||||
let peerConnection = new PeerConnection(this, remotePeerID, this.signaler);
|
||||
await peerConnection.connect();
|
||||
this.peers.set(remotePeerID, peerConnection);
|
||||
return peerConnection;
|
||||
}
|
||||
|
||||
async disconnect(remotePeerID: string) {
|
||||
let peer = this.peers.get(remotePeerID);
|
||||
|
||||
if (!peer) {
|
||||
console.log(`PeerManager.disconnect: couln't find peer ${remotePeerID}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await peer.disconnect();
|
||||
this.peers.delete(remotePeerID);
|
||||
}
|
||||
|
||||
async call(peerID: string, functionName: string, args: any) {
|
||||
let peer = this.peers.get(peerID);
|
||||
|
||||
if (!peer) {
|
||||
console.log(`Can't find peer ${peerID}`);
|
||||
return;
|
||||
}
|
||||
|
||||
return await peer.call(functionName, args);
|
||||
}
|
||||
|
||||
callFromRemote(functionName: string, args: any) {
|
||||
let func = this.RPC_remote.get(functionName);
|
||||
|
||||
if (!func) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return func(args);
|
||||
}
|
||||
|
||||
registerRPC(functionName: string, func: Function) {
|
||||
this.rpc[functionName] = (peerID: string, args: any) => {
|
||||
return this.call(peerID, functionName, args);
|
||||
};
|
||||
this.RPC_remote.set(functionName, func);
|
||||
}
|
||||
|
||||
registerSearchQuery(searchType: string, queryFunction: Function) {
|
||||
this.searchQueryFunctions.set(searchType, queryFunction);
|
||||
}
|
||||
|
||||
async search(type: string, message: any) {
|
||||
let promises = [];
|
||||
for (let peer of this.peers.values()) {
|
||||
promises.push(peer.call(type, message));
|
||||
}
|
||||
|
||||
return await Promise.allSettled(promises);
|
||||
}
|
||||
|
||||
onMessage(remotePeerID: string, message: any) {
|
||||
console.log(remotePeerID, message);
|
||||
}
|
||||
}
|
||||
|
||||
function log(...args: any[]): void {
|
||||
for (let arg of args) {
|
||||
console.log("[LOG]", arg);
|
||||
}
|
||||
}
|
||||
|
||||
interface Message {
|
||||
type: string;
|
||||
from_peer: string;
|
||||
to_peer: string;
|
||||
from_username: string;
|
||||
from_peername: string;
|
||||
peer_message: any;
|
||||
}
|
||||
|
||||
// Initially this wil be the bootstrap peer, We'll keep a connection to it and it will keep a list of all connected peers.
|
||||
// Eventually we will replace this with connecting via other peers.
|
||||
class Signaler {
|
||||
websocket: WebSocket;
|
||||
|
||||
sessionID: string;
|
||||
userID: string;
|
||||
peerID: string;
|
||||
bootstrapPeerID: string = "";
|
||||
private isBootstrapPeer: boolean = false;
|
||||
private onConnected: Function;
|
||||
|
||||
constructor(userID: string, peerID: string, isBootstrapPeer: boolean, onConnected: Function) {
|
||||
this.onConnected = onConnected;
|
||||
this.isBootstrapPeer = isBootstrapPeer;
|
||||
this.sessionID = generateID();
|
||||
this.userID = userID;
|
||||
this.peerID = peerID;
|
||||
|
||||
|
||||
try {
|
||||
this.websocket = new WebSocket(
|
||||
`wss://${window.location.hostname}:${window.location.port}/ws`,
|
||||
);
|
||||
} catch (error: any) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
|
||||
this.websocket.onopen = async (event) => {
|
||||
log("signaler:ws:onopen");
|
||||
await this.sendHello2();
|
||||
};
|
||||
|
||||
this.websocket.onmessage = this.onMessage.bind(this);
|
||||
}
|
||||
|
||||
sendPeerMessage(remotePeerID: string, peerMessage: { type: string; description: RTCSessionDescription; }) {
|
||||
this.send({
|
||||
type: "peer_message",
|
||||
from: this.peerID,
|
||||
to: remotePeerID,
|
||||
from_username: "blah user",
|
||||
from_peername: "blah peer",
|
||||
message: peerMessage
|
||||
})
|
||||
|
||||
// let responseMessage = { type: "peer_message",
|
||||
// from: app.peerID,
|
||||
// to: data.from,
|
||||
// from_username: app.username,
|
||||
// from_peername: app.peername,
|
||||
// message: { type: "get_posts_for_user", post_ids: postIds, user_id: message.user_id } }
|
||||
|
||||
}
|
||||
|
||||
|
||||
async sendHello2() {
|
||||
this.send({
|
||||
type: "hello2",
|
||||
user_id: this.userID,
|
||||
// user_name: app.username,
|
||||
peer_id: this.peerID,
|
||||
session_id: this.sessionID,
|
||||
// peer_name: app.peername,
|
||||
is_bootstrap_peer: this.isBootstrapPeer,
|
||||
// peer_description: this.rtcPeerDescription
|
||||
});
|
||||
}
|
||||
|
||||
connect() {
|
||||
}
|
||||
|
||||
onMessage(event: MessageEvent) {
|
||||
let messageJSON = event.data;
|
||||
let message: any = null;
|
||||
|
||||
try {
|
||||
message = JSON.parse(messageJSON);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
console.log("->signaler:", message);
|
||||
|
||||
if (message.type === "hello2") {
|
||||
|
||||
if (!this.isBootstrapPeer) {
|
||||
this.bootstrapPeerID = message.bootstrapPeers[0];
|
||||
}
|
||||
|
||||
this.onConnected(this.bootstrapPeerID);
|
||||
}
|
||||
}
|
||||
|
||||
send(message: any) {
|
||||
let messageJSON = "";
|
||||
|
||||
try {
|
||||
messageJSON = JSON.stringify(message);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("<-signaler:", message);
|
||||
|
||||
this.websocket.send(messageJSON);
|
||||
}
|
||||
|
||||
// sendPeerMessage
|
||||
}
|
||||
|
||||
class PeerConnection {
|
||||
private remotePeerID: string;
|
||||
private signaler: Signaler;
|
||||
private peerManager: PeerManager;
|
||||
private dataChannel: RTCDataChannel | null = null;
|
||||
private messageHandlers: Map<string, Function> = new Map();
|
||||
|
||||
// private makingOffer:boolean = false;
|
||||
// private ignoreOffer:boolean = false;
|
||||
|
||||
rtcPeer: RTCPeerConnection | null = null;
|
||||
|
||||
static config = {
|
||||
iceServers: [
|
||||
{ urls: "stun:ddln.app" },
|
||||
// { urls: "turn:ddln.app", username: "a", credential: "b" },
|
||||
{ urls: "stun:stun.l.google.com" }, // keeping this for now as my STUN server is not return ipv6
|
||||
// { urls: "stun:stun1.l.google.com" },
|
||||
// { urls: "stun:stun2.l.google.com" },
|
||||
// { urls: "stun:stun3.l.google.com" },
|
||||
// { urls: "stun:stun4.l.google.com" },
|
||||
],
|
||||
};
|
||||
|
||||
pendingRPCs: Map<
|
||||
string,
|
||||
{ resolve: Function; reject: Function; functionName: string }
|
||||
> = new Map();
|
||||
messageSuperlog: boolean = true;
|
||||
|
||||
async RPCHandler(message: any) {
|
||||
}
|
||||
|
||||
constructor(
|
||||
peerManager: PeerManager,
|
||||
remotePeerID: string,
|
||||
signaler: Signaler,
|
||||
) {
|
||||
this.peerManager = peerManager;
|
||||
this.remotePeerID = remotePeerID;
|
||||
this.signaler = signaler;
|
||||
}
|
||||
|
||||
async connect() {
|
||||
this.rtcPeer = new RTCPeerConnection(PeerConnection.config);
|
||||
this.dataChannel = this.rtcPeer.createDataChannel("ddln_main");
|
||||
|
||||
if (this.rtcPeer === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this.rtcPeer.onicecandidate = ({ candidate }) => this.signaler.send(JSON.stringify({ candidate }));
|
||||
this.rtcPeer.onicecandidate = ({ candidate }) => console.log(candidate);
|
||||
|
||||
|
||||
this.rtcPeer.onnegotiationneeded = async (event) => {
|
||||
log("on negotiation needed fired");
|
||||
|
||||
if (!this.rtcPeer) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
let makingOffer = false;
|
||||
|
||||
try {
|
||||
makingOffer = true;
|
||||
await this.rtcPeer.setLocalDescription();
|
||||
|
||||
if (!this.rtcPeer.localDescription) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.signaler.sendPeerMessage(this.remotePeerID, { type: "rtcDescription", description: this.rtcPeer.localDescription });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
makingOffer = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
}
|
||||
|
||||
send(message: any) {
|
||||
this.messageSuperlog && console.log("<-", message.type, message);
|
||||
|
||||
let messageJSON = JSON.stringify(message);
|
||||
// this.dataChannel?.send();
|
||||
|
||||
this.onMessage(messageJSON);
|
||||
}
|
||||
|
||||
call(functionName: string, args: any) {
|
||||
let transactionID = generateID(); // make this faster as we will only ever have a small number of in-flight queries on a peer
|
||||
// Think about a timeout here to auto reject it after a while.
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
this.pendingRPCs.set(transactionID, { resolve, reject, functionName });
|
||||
// setTimeout(() => reject("bad"), 1000);
|
||||
});
|
||||
|
||||
let message = {
|
||||
type: "rpc_call",
|
||||
transaction_id: transactionID,
|
||||
function_name: functionName,
|
||||
args: args,
|
||||
};
|
||||
|
||||
this.send(message);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
onMessage(messageJSON: any) {
|
||||
|
||||
let message: any = {};
|
||||
try {
|
||||
message = JSON.parse(messageJSON);
|
||||
} catch (e) {
|
||||
console.log("PeerConnection.onMessage:", e);
|
||||
}
|
||||
|
||||
this.messageSuperlog && console.log("->", message.type, message);
|
||||
let type = message.type;
|
||||
|
||||
if (type === "rpc_response") {
|
||||
let pendingRPC = this.pendingRPCs.get(message.transaction_id);
|
||||
|
||||
if (!pendingRPC) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
pendingRPC.resolve(message.response);
|
||||
}
|
||||
|
||||
if (type === "rpc_call") {
|
||||
|
||||
let response = this.peerManager.callFromRemote(message.function_name, message.args);
|
||||
|
||||
let responseMessage = { type: 'rpc_response', transaction_id: message.transaction_id, response: response };
|
||||
|
||||
this.send(responseMessage);
|
||||
|
||||
}
|
||||
|
||||
// this.peerManger.onMessage(this.remotePeerID, message);
|
||||
}
|
||||
}
|
||||
|
||||
// export class PeerConnection2 {
|
||||
|
||||
// id: string;
|
||||
|
||||
// private makingOffer:boolean = false;
|
||||
// private ignoreOffer:boolean = false;
|
||||
|
||||
// rtcPeer: RTCPeerConnection;
|
||||
|
||||
// signaler:WebSocket;
|
||||
|
||||
// static config = {
|
||||
// iceServers: [
|
||||
// { urls: "stun:stun.l.google.com" },
|
||||
// { urls: "stun:stun1.l.google.com" },
|
||||
// { urls: "stun:stun2.l.google.com" },
|
||||
// { urls: "stun:stun3.l.google.com" },
|
||||
// { urls: "stun:stun4.l.google.com" },
|
||||
// ],
|
||||
// };
|
||||
|
||||
// constructor(remotePeerID: string, signaler:WebSocket) {
|
||||
// this.id = remotePeerID;
|
||||
|
||||
// this.rtcPeer = new RTCPeerConnection(PeerConnection2.config);
|
||||
|
||||
// this.signaler = signaler;;
|
||||
|
||||
// this.rtcPeer.onnegotiationneeded = async () => {
|
||||
// try {
|
||||
// this.makingOffer = true;
|
||||
// await this.rtcPeer.setLocalDescription();
|
||||
// signaler.send(JSON.stringify({ description: this.rtcPeer.localDescription }));
|
||||
// } catch (err) {
|
||||
// console.error(err);
|
||||
// } finally {
|
||||
// this.makingOffer = false;
|
||||
// }
|
||||
// };
|
||||
|
||||
// this.rtcPeer.onicecandidate = ({ candidate }) => signaler.send(JSON.stringify({ candidate }));
|
||||
|
||||
// this.ignoreOffer = false;
|
||||
// }
|
||||
|
||||
// onSignallerMessage = async ({ data: { description, candidate } }: MessageEvent) => {
|
||||
// try {
|
||||
// if (description) {
|
||||
// // const offerCollision =
|
||||
// // description.type === "offer" &&
|
||||
// // (this.makingOffer || this.rtcPeer.signalingState !== "stable");
|
||||
|
||||
// // this.ignoreOffer = !polite && offerCollision;
|
||||
// if (this.ignoreOffer) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// await this.rtcPeer.setRemoteDescription(description);
|
||||
// if (description.type === "offer") {
|
||||
// await this.rtcPeer.setLocalDescription();
|
||||
// this.signaler.send(JSON.stringify({ description: this.rtcPeer.localDescription }));
|
||||
// }
|
||||
// } else if (candidate) {
|
||||
// try {
|
||||
// await this.rtcPeer.addIceCandidate(candidate);
|
||||
// } catch (err) {
|
||||
// if (!this.ignoreOffer) {
|
||||
// throw err;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } catch (err) {
|
||||
// console.error(err);
|
||||
// }
|
||||
// };
|
||||
|
||||
// }
|
||||
|
||||
// // const config = {
|
||||
// // iceServers: [{ urls: "stun:stun.mystunserver.tld" }],
|
||||
// // };
|
||||
|
||||
// // let polite = true;
|
||||
|
||||
// // const signaler = new SignalingChannel();
|
||||
// // const signaler: any = {}
|
||||
// // const rtcPeer = new RTCPeerConnection(config);
|
||||
|
||||
// // // const constraints = { audio: true, video: true };
|
||||
// // const selfVideo = document.querySelector("video.selfview");
|
||||
// // const remoteVideo = document.querySelector("video.remoteview");
|
||||
|
||||
// // async function start() {
|
||||
// // try {
|
||||
// // const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||
|
||||
// // for (const track of stream.getTracks()) {
|
||||
// // pc.addTrack(track, stream);
|
||||
// // }
|
||||
// // selfVideo.srcObject = stream;
|
||||
// // } catch (err) {
|
||||
// // console.error(err);
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// // rtcPeer.ontrack = ({ track, streams }) => {
|
||||
// // track.onunmute = () => {
|
||||
// // if (remoteVideo.srcObject) {
|
||||
// // return;
|
||||
// // }
|
||||
// // remoteVideo.srcObject = streams[0];
|
||||
// // };
|
||||
// // };
|
||||
|
||||
// // makingOffer = false;
|
||||
|
||||
// // rtcPeer.onnegotiationneeded = async () => {
|
||||
// // try {
|
||||
// // // makingOffer = true;
|
||||
// // await rtcPeer.setLocalDescription();
|
||||
// // signaler.send({ description: rtcPeer.localDescription });
|
||||
// // } catch (err) {
|
||||
// // console.error(err);
|
||||
// // } finally {
|
||||
// // makingOffer = false;
|
||||
// // }
|
||||
// // };
|
||||
|
||||
// // rtcPeer.onicecandidate = ({ candidate }) => signaler.send({ candidate });
|
||||
|
||||
// // let ignoreOffer = false;
|
||||
|
||||
// // signaler.onmessage = async ({ data: { description, candidate } }: MessageEvent) => {
|
||||
// // try {
|
||||
// // if (description) {
|
||||
// // const offerCollision =
|
||||
// // description.type === "offer" &&
|
||||
// // // (makingOffer || rtcPeer.signalingState !== "stable");
|
||||
|
||||
// // ignoreOffer = !polite && offerCollision;
|
||||
// // if (ignoreOffer) {
|
||||
// // return;
|
||||
// // }
|
||||
|
||||
// // await rtcPeer.setRemoteDescription(description);
|
||||
// // if (description.type === "offer") {
|
||||
// // await rtcPeer.setLocalDescription();
|
||||
// // signaler.send({ description: rtcPeer.localDescription });
|
||||
// // }
|
||||
// // } else if (candidate) {
|
||||
// // try {
|
||||
// // await rtcPeer.addIceCandidate(candidate);
|
||||
// // } catch (err) {
|
||||
// // if (!ignoreOffer) {
|
||||
// // throw err;
|
||||
// // }
|
||||
// // }
|
||||
// // }
|
||||
// // } catch (err) {
|
||||
// // console.error(err);
|
||||
// // }
|
||||
// // };
|
||||
28
src/log.ts
Normal file
28
src/log.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
let logLines: string[] = [];
|
||||
let logLength = 30;
|
||||
let logVisible = false;
|
||||
|
||||
export function setLogVisibility(visible:boolean) {
|
||||
logVisible = visible;
|
||||
}
|
||||
|
||||
export function renderLog() {
|
||||
if (!logVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
let log = document.getElementById("log");
|
||||
if (!log) {
|
||||
throw new Error();
|
||||
}
|
||||
log.innerText = logLines.join("\n");
|
||||
}
|
||||
export function log(message: string) {
|
||||
console.log(message);
|
||||
logLines.push(`${new Date().toLocaleTimeString()}: ${message}`);
|
||||
if (logLines.length > logLength) {
|
||||
logLines = logLines.slice(logLines.length - logLength);
|
||||
}
|
||||
|
||||
renderLog();
|
||||
}
|
||||
130
src/main.ts
130
src/main.ts
@@ -33,6 +33,7 @@ Restruucture the app around the data. App/WS split is messy. Clean it up.
|
||||
// import * as ForceGraph3D from "3d-force-graph";
|
||||
import { openDatabase, getData, addData, addDataArray, clearData, deleteData, mergeDataArray, getAllData, checkPostIds, getAllIds, getPostsByIds } from "db";
|
||||
|
||||
// import {PeerConnection} from "webRTC";
|
||||
|
||||
// declare let WebTorrent: any;
|
||||
|
||||
@@ -279,6 +280,88 @@ interface PeerMessage {
|
||||
message: any;
|
||||
}
|
||||
|
||||
// class Signaler {
|
||||
// websocket: WebSocket | null = null;
|
||||
|
||||
// websocketPingInterval: number = 0;
|
||||
|
||||
|
||||
|
||||
// connect() {
|
||||
// if (this.websocket?.readyState === WebSocket.OPEN) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// window.clearInterval(this.websocketPingInterval);
|
||||
// if (this.websocket) { this.websocket.close() };
|
||||
|
||||
// try {
|
||||
// this.websocket = new WebSocket(`wss://${window.location.hostname}:${window.location.port}/ws`);
|
||||
// } catch (error: any) {
|
||||
// console.log(error.message);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// this.websocket.onopen = async (event) => {
|
||||
// log("ws:connected");
|
||||
// await this.sendHello();
|
||||
|
||||
// // If we're running as a headless peer, send a hello message every N seconds to refresh the posts we have.
|
||||
// let helloRefreshIntervalPeriod = 120;
|
||||
// if (app.isHeadless) {
|
||||
// console.log("wsConnection: Setting hello refresh interval to ", helloRefreshIntervalPeriod)
|
||||
// this.helloRefreshInterval = window.setInterval(() => {
|
||||
// console.log("wsConnection: Hello refresh.")
|
||||
|
||||
// if (!navigator.onLine) {
|
||||
// return;
|
||||
// }
|
||||
// this.sendHello();
|
||||
// }, helloRefreshIntervalPeriod * 1000);
|
||||
// }
|
||||
|
||||
// this.websocketPingInterval = window.setInterval(() => {
|
||||
// if (!navigator.onLine) {
|
||||
// return;
|
||||
// }
|
||||
// this.send({ type: "ping", peer_id: this.peerID, peer_name: app.peername, user_id: app.userID, user_name: app.username });
|
||||
// }, 10_000)
|
||||
// };
|
||||
|
||||
// this.websocket.onclose = (event) => {
|
||||
// log("ws:disconnected");
|
||||
// // this.retry *= 2;
|
||||
// log(`Retrying in ${this.retry} seconds`);
|
||||
// window.setTimeout(() => { this.connect(); }, this.retry * 1000);
|
||||
// };
|
||||
|
||||
// this.websocket.onmessage = (event) => {
|
||||
// // log('ws:<-' + event.data.slice(0, 240));
|
||||
// let data = JSON.parse(event.data);
|
||||
|
||||
// let { type } = data;
|
||||
|
||||
// let handler = this.messageHandlers.get(type);
|
||||
// if (!handler) {
|
||||
// console.warn(`Got a message we can't handle:`, type);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// handler(data);
|
||||
|
||||
// };
|
||||
|
||||
// this.websocket.onerror = (event) => {
|
||||
// log('ws:error: ' + event);
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
// disconnect() {
|
||||
// this.websocket?.close();
|
||||
// }
|
||||
// }
|
||||
|
||||
class wsConnection {
|
||||
websocket: WebSocket | null = null;
|
||||
userID = "";
|
||||
@@ -304,12 +387,19 @@ class wsConnection {
|
||||
this.messageHandlers.set('pong', this.pongHandler);
|
||||
this.messageHandlers.set('peer_message', this.peerMessageHandler.bind(this));
|
||||
|
||||
|
||||
//
|
||||
this.peerMessageHandlers.set('get_post_ids_for_user', this.getPostIdsForUserHandler.bind(this));
|
||||
this.peerMessageHandlers.set('get_post_ids_for_user_response', this.getPostIdsForUserResponseHandler.bind(this));
|
||||
|
||||
this.peerMessageHandlers.set('get_posts_for_user', this.getPostsForUserHandler.bind(this));
|
||||
this.peerMessageHandlers.set('get_posts_for_user_response', this.getPostsForUserReponseHandler.bind(this));
|
||||
|
||||
this.peerMessageHandlers.set('send_webrtc_offer', this.sendWebRTCOfferHandler.bind(this));
|
||||
this.peerMessageHandlers.set('send_webrtc_offer_response', this.getPostIdsForUserResponseHandler.bind(this));
|
||||
|
||||
|
||||
|
||||
window.addEventListener('beforeunload', () => this.disconnect());
|
||||
|
||||
this.connect();
|
||||
@@ -340,6 +430,10 @@ class wsConnection {
|
||||
pongHandler(data: any) {
|
||||
}
|
||||
|
||||
async sendWebRTCOfferHandler(data:any) {
|
||||
|
||||
}
|
||||
|
||||
async getPostIdsForUserResponseHandler(data: any) {
|
||||
// log(`getPostsForUserResponse: ${data}`)
|
||||
|
||||
@@ -1020,6 +1114,8 @@ class App {
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
||||
// What happens with multiple tabs/connections from the same peer ID? I don't think this works, we also need a session ID for each connection
|
||||
getPeerID() {
|
||||
let id = localStorage.getItem("peer_id");
|
||||
|
||||
@@ -1540,7 +1636,36 @@ class App {
|
||||
}
|
||||
|
||||
async main() {
|
||||
let urlParams = (new URL(window.location.href)).searchParams;
|
||||
if (urlParams.has('log')) {
|
||||
this.showInfo();
|
||||
}
|
||||
|
||||
let peer:RTCPeerConnection|null = null;
|
||||
// if (window.RTCPeerConnection) {
|
||||
peer = new RTCPeerConnection({
|
||||
iceServers: [
|
||||
{ urls: "stun:ddln.app"},
|
||||
{ urls: "turn:ddln.app", username:"a", credential:"b"},
|
||||
{ urls: "stun:stun.l.google.com" }, // keeping this for now as my STUN server is not return ipv6
|
||||
// { urls: "stun:stun1.l.google.com" },
|
||||
// { urls: "stun:stun2.l.google.com" },
|
||||
// { urls: "stun:stun3.l.google.com" },
|
||||
// { urls: "stun:stun4.l.google.com" },
|
||||
]
|
||||
});
|
||||
|
||||
peer.createDataChannel('boop');
|
||||
|
||||
peer.onicecandidate = ({ candidate }) => {log(`WRTC:${candidate?.address} ${candidate?.protocol} ${candidate?.type} ${(candidate as any)?.url}`)};
|
||||
peer.onnegotiationneeded = (event) => {console.log("on negotiation needed: ", event)}
|
||||
|
||||
peer.createOffer().then((description)=>{
|
||||
peer.setLocalDescription(description)
|
||||
log("RTC: " + description.sdp + description.type);
|
||||
});
|
||||
|
||||
// }
|
||||
|
||||
// await this.exportPostsForUser('b38b623c-c3fa-4351-9cab-50233c99fa4e');
|
||||
|
||||
@@ -1579,11 +1704,6 @@ class App {
|
||||
document.getElementById('connectURL')!.innerHTML = `<a href="${this.connectURL}">connect</a>`;
|
||||
|
||||
|
||||
let urlParams = (new URL(window.location.href)).searchParams;
|
||||
if (urlParams.has('log')) {
|
||||
this.showInfo();
|
||||
}
|
||||
|
||||
if (urlParams.has('headless')) {
|
||||
this.isHeadless = true;
|
||||
}
|
||||
|
||||
2114
src/main2.ts
Normal file
2114
src/main2.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,10 +5,12 @@ const cacheName = "dandelion_cache_v1";
|
||||
const contentToCache = [
|
||||
'/static/index.html',
|
||||
'/static/main.css',
|
||||
'/static/main.js',
|
||||
'/static/main2.js',
|
||||
'/static/lib/marked.min.js',
|
||||
'/static/lib/qrcode.min.js',
|
||||
'/static/db.js',
|
||||
'/static/PeerManager.js',
|
||||
'/static/IDUtils.js',
|
||||
'/static/favicon.ico'
|
||||
];
|
||||
|
||||
|
||||
110
src/webRTC.ts
110
src/webRTC.ts
@@ -1,110 +0,0 @@
|
||||
class PeerManager {
|
||||
connect(peerID: string) {
|
||||
// Connect to the peer that has the peer id peerID
|
||||
}
|
||||
|
||||
disconnect(peerID: string) {
|
||||
}
|
||||
}
|
||||
|
||||
class PeerConnection {
|
||||
static config = {
|
||||
iceServers: [
|
||||
{ urls: "stun:stun.l.google.com" },
|
||||
{ urls: "stun:stun1.l.google.com" },
|
||||
{ urls: "stun:stun2.l.google.com" },
|
||||
{ urls: "stun:stun3.l.google.com" },
|
||||
{ urls: "stun:stun4.l.google.com" },
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
const config = {
|
||||
iceServers: [{ urls: "stun:stun.mystunserver.tld" }],
|
||||
};
|
||||
|
||||
let polite = true;
|
||||
|
||||
// const signaler = new SignalingChannel();
|
||||
const signaler: any = {}
|
||||
const pc = new RTCPeerConnection(config);
|
||||
|
||||
|
||||
const constraints = { audio: true, video: true };
|
||||
const selfVideo = document.querySelector("video.selfview");
|
||||
const remoteVideo = document.querySelector("video.remoteview");
|
||||
|
||||
async function start() {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||
|
||||
for (const track of stream.getTracks()) {
|
||||
pc.addTrack(track, stream);
|
||||
}
|
||||
// selfVideo.srcObject = stream;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pc.ontrack = ({ track, streams }) => {
|
||||
track.onunmute = () => {
|
||||
// if (remoteVideo.srcObject) {
|
||||
// return;
|
||||
// }
|
||||
// remoteVideo.srcObject = streams[0];
|
||||
};
|
||||
};
|
||||
|
||||
let makingOffer = false;
|
||||
|
||||
pc.onnegotiationneeded = async () => {
|
||||
try {
|
||||
makingOffer = true;
|
||||
await pc.setLocalDescription();
|
||||
signaler.send({ description: pc.localDescription });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
makingOffer = false;
|
||||
}
|
||||
};
|
||||
|
||||
pc.onicecandidate = ({ candidate }) => signaler.send({ candidate });
|
||||
|
||||
let ignoreOffer = false;
|
||||
|
||||
signaler.onmessage = async ({ data: { description, candidate } }: MessageEvent) => {
|
||||
try {
|
||||
if (description) {
|
||||
const offerCollision =
|
||||
description.type === "offer" &&
|
||||
(makingOffer || pc.signalingState !== "stable");
|
||||
|
||||
ignoreOffer = !polite && offerCollision;
|
||||
if (ignoreOffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
await pc.setRemoteDescription(description);
|
||||
if (description.type === "offer") {
|
||||
await pc.setLocalDescription();
|
||||
signaler.send({ description: pc.localDescription });
|
||||
}
|
||||
} else if (candidate) {
|
||||
try {
|
||||
await pc.addIceCandidate(candidate);
|
||||
} catch (err) {
|
||||
if (!ignoreOffer) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user