working datachannel and inital RPC test
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
import { brotli } from "jsr:@deno-library/compress";
|
import { brotli } from "jsr:@deno-library/compress";
|
||||||
|
|
||||||
|
const superLog=true;
|
||||||
|
|
||||||
const memoryCache = true;
|
const memoryCache = true;
|
||||||
const filepathResponseCache: Map<string, Response> = new Map();
|
const filepathResponseCache: Map<string, Response> = new Map();
|
||||||
@@ -254,6 +255,8 @@ function connectWebsocket(request: Request) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
superLog && console.log(message);
|
||||||
|
|
||||||
const dispatchHandler = messageDispatch.get(message?.type)
|
const dispatchHandler = messageDispatch.get(message?.type)
|
||||||
if (!dispatchHandler) {
|
if (!dispatchHandler) {
|
||||||
console.log("Got message I don't understand: ", event.data);
|
console.log("Got message I don't understand: ", event.data);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
// how? do "perfect negotiation" with bootstrap peer. All logic here moves to BP.
|
// how? do "perfect negotiation" with bootstrap peer. All logic here moves to BP.
|
||||||
|
|
||||||
import { generateID } from "IDUtils";
|
import { generateID } from "IDUtils";
|
||||||
|
import { log } from "log";
|
||||||
|
|
||||||
// Use a broadcast channel to only have one peer manager for multiple tabs,
|
// 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
|
// then we won't need to have a session ID as all queries for a peerID will be coming from the same peer manager
|
||||||
@@ -12,44 +13,226 @@ import { generateID } from "IDUtils";
|
|||||||
export class PeerManager {
|
export class PeerManager {
|
||||||
routingTable: Map<string, string>;
|
routingTable: Map<string, string>;
|
||||||
|
|
||||||
private peers: Map<string, PeerConnection>;
|
peers: Map<string, PeerConnection>;
|
||||||
private signaler: Signaler;
|
// private signaler: Signaler;
|
||||||
searchQueryFunctions: Map<string, Function> = new Map();
|
searchQueryFunctions: Map<string, Function> = new Map();
|
||||||
RPC_remote: Map<string, Function> = new Map();
|
RPC_remote: Map<string, Function> = new Map();
|
||||||
rpc: { [key: string]: Function } = {};
|
rpc: { [key: string]: Function } = {};
|
||||||
isBootstrapPeer: boolean = false;
|
isBootstrapPeer: boolean = false;
|
||||||
bootstrapPeerConnection: PeerConnection | null = null;
|
bootstrapPeerConnection: PeerConnection | null = null;
|
||||||
|
sessionID = generateID();
|
||||||
|
userID: string;
|
||||||
|
peerID: string;
|
||||||
|
|
||||||
|
websocket: WebSocket | null = null;
|
||||||
|
bootstrapPeerID: string | null = null;
|
||||||
|
connectPromise: { resolve: Function, reject: Function } | null = null;
|
||||||
|
|
||||||
|
pingPeers: RTCPeerConnection[] = [];
|
||||||
|
|
||||||
|
websocketSend(message: any) {
|
||||||
|
if (!this.websocket) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
let messageJSON = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
messageJSON = JSON.stringify(message);
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log("<-signaler:", message);
|
||||||
|
|
||||||
|
this.websocket.send(messageJSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onWebsocketMessage(event: MessageEvent) {
|
||||||
|
let messageJSON = event.data;
|
||||||
|
let message: any = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
message = JSON.parse(messageJSON);
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
log("->signaler:", message);
|
||||||
|
|
||||||
|
if (message.type === "hello2") {
|
||||||
|
|
||||||
|
if (!this.isBootstrapPeer) {
|
||||||
|
this.bootstrapPeerID = message.bootstrapPeers[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onHello2Received(this.bootstrapPeerID as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.type === "peer_message") {
|
||||||
|
|
||||||
|
let peerConnection = this.peers.get(message.from);
|
||||||
|
|
||||||
|
if (message.message.type === "rtc_description") {
|
||||||
|
|
||||||
|
// let existingConnection = this.peers.get(message.from);
|
||||||
|
|
||||||
|
// // We're already connected, so delete the existing connection and make a new one.
|
||||||
|
// if (peerConnection) {
|
||||||
|
// peerConnection.disconnect();
|
||||||
|
// this.peers.delete(message.from);
|
||||||
|
// peerConnection = undefined;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!peerConnection) {
|
||||||
|
peerConnection = this.onConnectRequest(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!peerConnection) {
|
||||||
|
log("Can't find peer for peer message:", message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
peerConnection.onWebsocketMessage(message.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
onConnectRequest(message: any) {
|
||||||
|
let remotePeerID = message.from;
|
||||||
|
let newPeer = new PeerConnection(this, remotePeerID, this.websocketSendPeerMessage.bind(this));
|
||||||
|
if (this.isBootstrapPeer) {
|
||||||
|
newPeer.setPolite(false);
|
||||||
|
}
|
||||||
|
newPeer.connect();
|
||||||
|
this.peers.set(remotePeerID, newPeer);
|
||||||
|
return newPeer;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onHello2Received(bootstrapPeerID: string) {
|
||||||
|
if (!this.isBootstrapPeer) {
|
||||||
|
this.bootstrapPeerConnection = await this.connectToPeer(bootstrapPeerID);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connectPromise?.resolve();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendHello2() {
|
||||||
|
this.websocketSend({
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
websocketSendPeerMessage(remotePeerID: string, peerMessage: { type: string; description: RTCSessionDescription; }) {
|
||||||
|
this.websocketSend({
|
||||||
|
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 onConnected(bootstrapPeerID: string) {
|
|
||||||
this.bootstrapPeerConnection = await this.connect(bootstrapPeerID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(userID: string, peerID: string, isBootstrapPeer: boolean) {
|
constructor(userID: string, peerID: string, isBootstrapPeer: boolean) {
|
||||||
this.isBootstrapPeer = isBootstrapPeer;
|
this.isBootstrapPeer = isBootstrapPeer;
|
||||||
this.peers = new Map();
|
this.peers = new Map();
|
||||||
this.routingTable = new Map();
|
this.routingTable = new Map();
|
||||||
|
this.userID = userID;
|
||||||
|
this.peerID = peerID;
|
||||||
|
|
||||||
|
|
||||||
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() {
|
||||||
// Connect to the peer that has the peer id peerID
|
let connectPromise = new Promise((resolve, reject) => {
|
||||||
let peerConnection = new PeerConnection(this, remotePeerID, this.signaler);
|
this.connectPromise = { resolve, reject };
|
||||||
await peerConnection.connect();
|
});
|
||||||
|
|
||||||
|
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("PeerManager:ws:onopen");
|
||||||
|
this.sendHello2();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.websocket.onmessage = this.onWebsocketMessage.bind(this);
|
||||||
|
|
||||||
|
return connectPromise;
|
||||||
|
|
||||||
|
// this.signaler = new Signaler(userID, peerID, isBootstrapPeer, this.onConnected.bind(this));
|
||||||
|
|
||||||
|
// Testing
|
||||||
|
// let dummyPeer = new PeerConnection(this, "dummy_peer", this.websocketSendPeerMessage.bind(this));
|
||||||
|
// this.peers.set("dummy_peer", dummyPeer);
|
||||||
|
}
|
||||||
|
|
||||||
|
async connectToPeer(remotePeerID: string) {
|
||||||
|
// Connect to the peer that has the peer id remotePeerID.
|
||||||
|
// TODO how do multiple windows / tabs from the same peer and user work?
|
||||||
|
// Need to decide if they shold all get a unique connection. A peer should only be requesting and writing
|
||||||
|
// Data once though, so it probably need to be solved on the client side as the data is shared obv
|
||||||
|
// Maybe use BroadcastChannel to proxy all calls to peermanager? That will probably really complicate things.
|
||||||
|
// What if we just user session+peerID for the connections? Then we might have two windows making requests
|
||||||
|
// For IDs etc, it would probably be best to proxy everything.
|
||||||
|
// Maybe once we put this logic in a web worker, we'll need an interface to it that works over postMessage
|
||||||
|
// anyway, and at that point, we could just use that same interface over a broadcastChannel
|
||||||
|
// let's keep it simple for now and ignore the problem :)
|
||||||
|
|
||||||
|
let peerConnection = new PeerConnection(this, remotePeerID, this.websocketSendPeerMessage.bind(this));
|
||||||
this.peers.set(remotePeerID, peerConnection);
|
this.peers.set(remotePeerID, peerConnection);
|
||||||
|
await peerConnection.connect();
|
||||||
return peerConnection;
|
return peerConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPeerDisconnected(remotePeerID: string) {
|
||||||
|
let deleted = this.peers.delete(remotePeerID);
|
||||||
|
|
||||||
|
if (!deleted) {
|
||||||
|
throw new Error(`Can't find peer that disconnected ${remotePeerID}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: What do we do if we lose connection to the bootstrap peer?
|
||||||
|
// If we have other connections, it probably doesn't matter.
|
||||||
|
// Eventually we want the bootstrap peer to be no different than any other peer anyway.
|
||||||
|
if (remotePeerID === this.bootstrapPeerID) {
|
||||||
|
this.bootstrapPeerID = null;
|
||||||
|
this.bootstrapPeerConnection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
async disconnect(remotePeerID: string) {
|
async disconnect(remotePeerID: string) {
|
||||||
let peer = this.peers.get(remotePeerID);
|
let peer = this.peers.get(remotePeerID);
|
||||||
|
|
||||||
if (!peer) {
|
if (!peer) {
|
||||||
console.log(`PeerManager.disconnect: couln't find peer ${remotePeerID}`);
|
log(`PeerManager.disconnect: couln't find peer ${remotePeerID}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +244,7 @@ export class PeerManager {
|
|||||||
let peer = this.peers.get(peerID);
|
let peer = this.peers.get(peerID);
|
||||||
|
|
||||||
if (!peer) {
|
if (!peer) {
|
||||||
console.log(`Can't find peer ${peerID}`);
|
log(`Can't find peer ${peerID}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,15 +282,11 @@ export class PeerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMessage(remotePeerID: string, message: any) {
|
onMessage(remotePeerID: string, message: any) {
|
||||||
console.log(remotePeerID, message);
|
log(remotePeerID, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function log(...args: any[]): void {
|
|
||||||
for (let arg of args) {
|
|
||||||
console.log("[LOG]", arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Message {
|
interface Message {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -118,125 +297,109 @@ interface Message {
|
|||||||
peer_message: any;
|
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.
|
// // 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.
|
// // Eventually we will replace this with connecting via other peers.
|
||||||
class Signaler {
|
// class Signaler {
|
||||||
websocket: WebSocket;
|
// websocket: WebSocket;
|
||||||
|
|
||||||
sessionID: string;
|
// sessionID: string;
|
||||||
userID: string;
|
// userID: string;
|
||||||
peerID: string;
|
// peerID: string;
|
||||||
bootstrapPeerID: string = "";
|
// bootstrapPeerID: string = "";
|
||||||
private isBootstrapPeer: boolean = false;
|
// private isBootstrapPeer: boolean = false;
|
||||||
private onConnected: Function;
|
// private onConnected: Function;
|
||||||
|
// peerRoutes: Map<string, PeerConnection> = new Map();
|
||||||
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) => {
|
// constructor(userID: string, peerID: string, isBootstrapPeer: boolean, onConnected: Function) {
|
||||||
log("signaler:ws:onopen");
|
// this.onConnected = onConnected;
|
||||||
await this.sendHello2();
|
// this.isBootstrapPeer = isBootstrapPeer;
|
||||||
};
|
// this.sessionID = generateID();
|
||||||
|
// this.userID = userID;
|
||||||
this.websocket.onmessage = this.onMessage.bind(this);
|
// this.peerID = peerID;
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
// try {
|
||||||
this.send({
|
// this.websocket = new WebSocket(
|
||||||
type: "hello2",
|
// `wss://${window.location.hostname}:${window.location.port}/ws`,
|
||||||
user_id: this.userID,
|
// );
|
||||||
// user_name: app.username,
|
// } catch (error: any) {
|
||||||
peer_id: this.peerID,
|
// throw new Error(error.message);
|
||||||
session_id: this.sessionID,
|
// }
|
||||||
// peer_name: app.peername,
|
|
||||||
is_bootstrap_peer: this.isBootstrapPeer,
|
|
||||||
// peer_description: this.rtcPeerDescription
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
connect() {
|
// this.websocket.onopen = async (event) => {
|
||||||
}
|
// log("signaler:ws:onopen");
|
||||||
|
// await this.sendHello2();
|
||||||
|
// };
|
||||||
|
|
||||||
onMessage(event: MessageEvent) {
|
// this.websocket.onmessage = this.onMessage.bind(this);
|
||||||
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) {
|
// connect() {
|
||||||
let messageJSON = "";
|
// }
|
||||||
|
|
||||||
try {
|
// onMessage(event: MessageEvent) {
|
||||||
messageJSON = JSON.stringify(message);
|
// let messageJSON = event.data;
|
||||||
} catch (e) {
|
// let message: any = null;
|
||||||
console.log(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("<-signaler:", message);
|
// try {
|
||||||
|
// message = JSON.parse(messageJSON);
|
||||||
|
// } catch (e) {
|
||||||
|
// log(e);
|
||||||
|
// throw new Error();
|
||||||
|
// }
|
||||||
|
|
||||||
this.websocket.send(messageJSON);
|
// log("->signaler:", message);
|
||||||
}
|
|
||||||
|
|
||||||
// sendPeerMessage
|
// if (message.type === "hello2") {
|
||||||
}
|
|
||||||
|
// if (!this.isBootstrapPeer) {
|
||||||
|
// this.bootstrapPeerID = message.bootstrapPeers[0];
|
||||||
|
// }
|
||||||
|
|
||||||
|
// this.onConnected(this.bootstrapPeerID);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (message.type == "peer_message") {
|
||||||
|
|
||||||
|
// if (message.message.type = "rtc_connect") {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let connection = this.peerRoutes.get(message.from_peer);
|
||||||
|
// if (!connection) {
|
||||||
|
// log("Can't find peer for peer message:", message);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// connection.onSignalerMessage(message);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// route(remotePeerID:string, peerConnection:PeerConnection) {
|
||||||
|
// this.peerRoutes.set(remotePeerID, peerConnection)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // sendPeerMessage
|
||||||
|
// }
|
||||||
|
|
||||||
class PeerConnection {
|
class PeerConnection {
|
||||||
private remotePeerID: string;
|
private remotePeerID: string;
|
||||||
private signaler: Signaler;
|
// private signaler: Signaler;
|
||||||
private peerManager: PeerManager;
|
private peerManager: PeerManager;
|
||||||
private dataChannel: RTCDataChannel | null = null;
|
private dataChannel: RTCDataChannel | null = null;
|
||||||
private messageHandlers: Map<string, Function> = new Map();
|
private messageHandlers: Map<string, Function> = new Map();
|
||||||
|
private sendPeerMessage: Function;
|
||||||
|
private makingOffer: boolean = false;
|
||||||
|
private ignoreOffer: boolean = false;
|
||||||
|
private isSettingRemoteAnswerPending: boolean = false;
|
||||||
|
private polite = true;
|
||||||
|
|
||||||
// private makingOffer:boolean = false;
|
// private makingOffer:boolean = false;
|
||||||
// private ignoreOffer:boolean = false;
|
// private ignoreOffer:boolean = false;
|
||||||
@@ -260,6 +423,7 @@ class PeerConnection {
|
|||||||
{ resolve: Function; reject: Function; functionName: string }
|
{ resolve: Function; reject: Function; functionName: string }
|
||||||
> = new Map();
|
> = new Map();
|
||||||
messageSuperlog: boolean = true;
|
messageSuperlog: boolean = true;
|
||||||
|
connectionPromise: { resolve: (value?: unknown) => void; reject: (reason?: any) => void; } | null = null;
|
||||||
|
|
||||||
async RPCHandler(message: any) {
|
async RPCHandler(message: any) {
|
||||||
}
|
}
|
||||||
@@ -267,25 +431,100 @@ class PeerConnection {
|
|||||||
constructor(
|
constructor(
|
||||||
peerManager: PeerManager,
|
peerManager: PeerManager,
|
||||||
remotePeerID: string,
|
remotePeerID: string,
|
||||||
signaler: Signaler,
|
sendPeerMessage: Function,
|
||||||
) {
|
) {
|
||||||
|
this.sendPeerMessage = sendPeerMessage;
|
||||||
this.peerManager = peerManager;
|
this.peerManager = peerManager;
|
||||||
this.remotePeerID = remotePeerID;
|
this.remotePeerID = remotePeerID;
|
||||||
this.signaler = signaler;
|
// this.signaler = signaler;
|
||||||
|
// this.signaler.route(remotePeerID, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
setPolite(polite: boolean) {
|
||||||
|
this.polite = polite;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupDataChannel() {
|
||||||
|
if (!this.dataChannel) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dataChannel.onopen = (e: any) => {
|
||||||
|
if (!this.dataChannel) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
log("data channel is open!");
|
||||||
|
this.send({ type: "hello datachannel", from: this.peerManager.peerID });
|
||||||
|
// this.dataChannel?.send(`{typeHello datachannel from: ${this.peerManager.peerID}`);
|
||||||
|
|
||||||
|
log([...this.peerManager.peers.keys()]);
|
||||||
|
|
||||||
|
if (this.peerManager.isBootstrapPeer) {
|
||||||
|
this.send({ type: 'initial_peers', from: this.peerManager.peerID, peers: [...this.peerManager.peers.keys()].filter(entry => entry !== this.remotePeerID) })
|
||||||
|
// this.dataChannel.send(JSON.stringify());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connectionPromise?.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dataChannel.onmessage = (e: MessageEvent) => {
|
||||||
|
log("data channel message: ", e.data)
|
||||||
|
this.onMessage(e.data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect() {
|
async connect() {
|
||||||
|
let connectionPromise = new Promise((resolve, reject) => this.connectionPromise = { resolve, reject });
|
||||||
this.rtcPeer = new RTCPeerConnection(PeerConnection.config);
|
this.rtcPeer = new RTCPeerConnection(PeerConnection.config);
|
||||||
|
|
||||||
|
this.rtcPeer.onconnectionstatechange = async (e: any) => {
|
||||||
|
log("rtcPeer: onconnectionstatechange:", this.rtcPeer?.connectionState)
|
||||||
|
|
||||||
|
if (!this.rtcPeer) {
|
||||||
|
throw new Error("onconnectionstatechange");
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the connection is closed, tell the peer manager that this connection has gone away
|
||||||
|
if (this.rtcPeer.connectionState === "disconnected") {
|
||||||
|
this.peerManager.onPeerDisconnected(this.remotePeerID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.rtcPeer.connectionState === "connected") {
|
||||||
|
|
||||||
|
// let stats = await this.rtcPeer.getStats();
|
||||||
|
// for (const stat in stats) {
|
||||||
|
// log(stat);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rtcPeer.ondatachannel = (e: any) => {
|
||||||
|
let dataChannel = e.channel;
|
||||||
|
|
||||||
|
this.dataChannel = dataChannel as RTCDataChannel;
|
||||||
|
|
||||||
|
this.setupDataChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.polite) {
|
||||||
this.dataChannel = this.rtcPeer.createDataChannel("ddln_main");
|
this.dataChannel = this.rtcPeer.createDataChannel("ddln_main");
|
||||||
|
this.setupDataChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (this.rtcPeer === null) {
|
if (this.rtcPeer === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this.rtcPeer.onicecandidate = ({ candidate }) => this.signaler.send(JSON.stringify({ candidate }));
|
// this.rtcPeer.onicecandidate = ({ candidate }) => this.signaler.send(JSON.stringify({ candidate }));
|
||||||
this.rtcPeer.onicecandidate = ({ candidate }) => console.log(candidate);
|
// this.rtcPeer.onicecandidate = ({ candidate }) => log(candidate);
|
||||||
|
|
||||||
|
|
||||||
|
this.rtcPeer.onicecandidate = ({ candidate }) => {
|
||||||
|
log(candidate);
|
||||||
|
this.sendPeerMessage(this.remotePeerID, { type: "rtc_candidate", candidate: candidate });
|
||||||
|
}
|
||||||
|
|
||||||
this.rtcPeer.onnegotiationneeded = async (event) => {
|
this.rtcPeer.onnegotiationneeded = async (event) => {
|
||||||
log("on negotiation needed fired");
|
log("on negotiation needed fired");
|
||||||
|
|
||||||
@@ -293,35 +532,104 @@ class PeerConnection {
|
|||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
let makingOffer = false;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
makingOffer = true;
|
this.makingOffer = true;
|
||||||
await this.rtcPeer.setLocalDescription();
|
await this.rtcPeer.setLocalDescription();
|
||||||
|
|
||||||
if (!this.rtcPeer.localDescription) {
|
if (!this.rtcPeer.localDescription) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.signaler.sendPeerMessage(this.remotePeerID, { type: "rtcDescription", description: this.rtcPeer.localDescription });
|
this.sendPeerMessage(this.remotePeerID, { type: "rtc_description", description: this.rtcPeer.localDescription });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
} finally {
|
} finally {
|
||||||
makingOffer = false;
|
this.makingOffer = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return connectionPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onWebsocketMessage(message: any) {
|
||||||
|
if (message.type == "rtc_connect") {
|
||||||
|
this.rtcPeer?.setRemoteDescription(message.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// /*
|
||||||
|
|
||||||
|
// let ignoreOffer = false;
|
||||||
|
// let isSettingRemoteAnswerPending = false;
|
||||||
|
|
||||||
|
// signaler.onmessage = async ({ data: { description, candidate } }) => {
|
||||||
|
|
||||||
|
|
||||||
|
if (!this.rtcPeer) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
let description = null;
|
||||||
|
if (message.type == "rtc_description") {
|
||||||
|
description = message.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
let candidate = null;
|
||||||
|
if (message.type == "rtc_candidate") {
|
||||||
|
candidate = message.candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (description) {
|
||||||
|
const readyForOffer =
|
||||||
|
!this.makingOffer &&
|
||||||
|
(this.rtcPeer.signalingState === "stable" || this.isSettingRemoteAnswerPending);
|
||||||
|
const offerCollision = description.type === "offer" && !readyForOffer;
|
||||||
|
|
||||||
|
this.ignoreOffer = !this.polite && offerCollision;
|
||||||
|
if (this.ignoreOffer) {
|
||||||
|
console.warn(">>>>>>>>>>>>>>>>>IGNORING OFFER");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isSettingRemoteAnswerPending = description.type == "answer";
|
||||||
|
await this.rtcPeer.setRemoteDescription(description);
|
||||||
|
this.isSettingRemoteAnswerPending = false;
|
||||||
|
if (description.type === "offer") {
|
||||||
|
await this.rtcPeer.setLocalDescription();
|
||||||
|
this.sendPeerMessage(this.remotePeerID, { type: "rtc_description", 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);
|
||||||
|
}
|
||||||
|
// };
|
||||||
|
// */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async disconnect() {
|
async disconnect() {
|
||||||
|
// this.rtcPeer?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
send(message: any) {
|
send(message: any) {
|
||||||
this.messageSuperlog && console.log("<-", message.type, message);
|
this.messageSuperlog && log("<-", message.type, message);
|
||||||
|
|
||||||
let messageJSON = JSON.stringify(message);
|
let messageJSON = JSON.stringify(message);
|
||||||
// this.dataChannel?.send();
|
this.dataChannel?.send(messageJSON);
|
||||||
|
|
||||||
this.onMessage(messageJSON);
|
// this.onMessage(messageJSON);
|
||||||
}
|
}
|
||||||
|
|
||||||
call(functionName: string, args: any) {
|
call(functionName: string, args: any) {
|
||||||
@@ -350,10 +658,10 @@ class PeerConnection {
|
|||||||
try {
|
try {
|
||||||
message = JSON.parse(messageJSON);
|
message = JSON.parse(messageJSON);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("PeerConnection.onMessage:", e);
|
log("PeerConnection.onMessage:", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messageSuperlog && console.log("->", message.type, message);
|
this.messageSuperlog && log("->", message.type, message);
|
||||||
let type = message.type;
|
let type = message.type;
|
||||||
|
|
||||||
if (type === "rpc_response") {
|
if (type === "rpc_response") {
|
||||||
|
|||||||
13
src/log.ts
13
src/log.ts
@@ -17,9 +17,16 @@ export function renderLog() {
|
|||||||
}
|
}
|
||||||
log.innerText = logLines.join("\n");
|
log.innerText = logLines.join("\n");
|
||||||
}
|
}
|
||||||
export function log(message: string) {
|
|
||||||
console.log(message);
|
export function log(...args: any[]): void {
|
||||||
logLines.push(`${new Date().toLocaleTimeString()}: ${message}`);
|
console.log(...args);
|
||||||
|
|
||||||
|
let logLine = `[${new Date().toLocaleTimeString()}]: `;
|
||||||
|
for (let arg of args) {
|
||||||
|
logLine += (typeof arg === "string" || arg instanceof String) ? arg : JSON.stringify(arg, null, 4);
|
||||||
|
}
|
||||||
|
logLines.push(logLine + "\n");
|
||||||
|
|
||||||
if (logLines.length > logLength) {
|
if (logLines.length > logLength) {
|
||||||
logLines = logLines.slice(logLines.length - logLength);
|
logLines = logLines.slice(logLines.length - logLength);
|
||||||
}
|
}
|
||||||
|
|||||||
93
src/main2.ts
93
src/main2.ts
@@ -35,6 +35,8 @@ import { openDatabase, getData, addData, addDataArray, clearData, deleteData, me
|
|||||||
import { generateID } from "IDUtils";
|
import { generateID } from "IDUtils";
|
||||||
import { PeerManager } from "PeerManager";
|
import { PeerManager } from "PeerManager";
|
||||||
|
|
||||||
|
import {log, renderLog, setLogVisibility} from "log"
|
||||||
|
|
||||||
// import {PeerConnection} from "webRTC";
|
// import {PeerConnection} from "webRTC";
|
||||||
|
|
||||||
// declare let WebTorrent: any;
|
// declare let WebTorrent: any;
|
||||||
@@ -122,29 +124,7 @@ function logID(ID: string) {
|
|||||||
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
let logLines: string[] = [];
|
|
||||||
let logLength = 30;
|
|
||||||
let logVisible = false;
|
|
||||||
function renderLog() {
|
|
||||||
if (!logVisible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let log = document.getElementById("log");
|
|
||||||
if (!log) {
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
log.innerText = logLines.join("\n");
|
|
||||||
}
|
|
||||||
function log(message: string) {
|
|
||||||
console.log(message);
|
|
||||||
logLines.push(`${new Date().toLocaleTimeString()}: ${message}`);
|
|
||||||
if (logLines.length > logLength) {
|
|
||||||
logLines = logLines.slice(logLines.length - logLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderLog();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -839,6 +819,36 @@ class App {
|
|||||||
firstRun = false;
|
firstRun = false;
|
||||||
peerManager: PeerManager | null = null;
|
peerManager: PeerManager | null = null;
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
this.peerManager = new PeerManager(this.userID, this.peerID, this.isBootstrapPeer);
|
||||||
|
log("*************** before peerManager.connect");
|
||||||
|
|
||||||
|
// We use promises here to only return from this call once we're connected to the boostrap peer
|
||||||
|
// and the datachannel is open.
|
||||||
|
// Might want to take this a step further and only return once we're connected to an initial set of peers?
|
||||||
|
// we could return progress information as we connect and have the app subscribe to that?
|
||||||
|
|
||||||
|
// Would be lovely to show a little display of peers connecting, whether you're connected directly to a friend's peer etc.
|
||||||
|
// Basically that live "dandelion" display.
|
||||||
|
|
||||||
|
this.peerManager.registerRPC('getPostIDsForUser', (userID: any) => {
|
||||||
|
return [1, 2, 3, 4, 5]
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.peerManager.connect();
|
||||||
|
log("*************** after peerManager.connect");
|
||||||
|
|
||||||
|
|
||||||
|
if (!this.isBootstrapPeer) {
|
||||||
|
let postIDs = await this.peerManager.rpc.getPostIDsForUser(this.peerManager.bootstrapPeerID, this.userID);
|
||||||
|
console.log("peerManager.rpc.getPostIDsForUser", postIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
getPreferentialUserID() {
|
getPreferentialUserID() {
|
||||||
return this.router.userID.length !== 0 ? this.router.userID : this.userID;
|
return this.router.userID.length !== 0 ? this.router.userID : this.userID;
|
||||||
}
|
}
|
||||||
@@ -1338,7 +1348,7 @@ class App {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
infoElement.style.display == 'none' ? infoElement.style.display = 'block' : infoElement.style.display = 'none';
|
infoElement.style.display == 'none' ? infoElement.style.display = 'block' : infoElement.style.display = 'none';
|
||||||
logVisible = infoElement.style.display == 'block';
|
setLogVisibility(infoElement.style.display == 'block');
|
||||||
renderLog();
|
renderLog();
|
||||||
this.lazyCreateQRCode();
|
this.lazyCreateQRCode();
|
||||||
(document.querySelector('#qrcode > img') as HTMLImageElement).classList.add('qrcode_image');
|
(document.querySelector('#qrcode > img') as HTMLImageElement).classList.add('qrcode_image');
|
||||||
@@ -1592,6 +1602,29 @@ class App {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async registerRPCs() {
|
||||||
|
if (!this.peerManager) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.peerManager.registerRPC('ping', (args: any) => {
|
||||||
|
return {id:this.peerID};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!this.isBootstrapPeer) {
|
||||||
|
let pong = await this.peerManager.rpc.ping(this.peerManager.bootstrapPeerID);
|
||||||
|
console.log(pong);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// this.peerManager.registerRPC('getPostIDsForUser', (args: any) => {
|
||||||
|
// this.sync.getPostsForUser
|
||||||
|
// });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
async testPeerManager() {
|
async testPeerManager() {
|
||||||
if (!this.peerManager) {
|
if (!this.peerManager) {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
@@ -1631,8 +1664,10 @@ class App {
|
|||||||
this.userID = this.getUserID();
|
this.userID = this.getUserID();
|
||||||
this.username = this.getUsername();
|
this.username = this.getUsername();
|
||||||
|
|
||||||
this.peerManager = new PeerManager(this.userID, this.peerID, this.isBootstrapPeer);
|
this.connect();
|
||||||
this.testPeerManager();
|
|
||||||
|
this.registerRPCs();
|
||||||
|
// this.testPeerManager();
|
||||||
|
|
||||||
// let peer: RTCPeerConnection | null = null;
|
// let peer: RTCPeerConnection | null = null;
|
||||||
// // if (window.RTCPeerConnection) {
|
// // if (window.RTCPeerConnection) {
|
||||||
@@ -2105,6 +2140,14 @@ namespace App {
|
|||||||
HOME,
|
HOME,
|
||||||
CONNECT,
|
CONNECT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// export function connect() {
|
||||||
|
// throw new Error("Function not implemented.");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export function connect() {
|
||||||
|
// throw new Error("Function not implemented.");
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
"imports": {
|
"imports": {
|
||||||
"db": "/static/db.js",
|
"db": "/static/db.js",
|
||||||
"IDUtils": "/static/IDUtils.js",
|
"IDUtils": "/static/IDUtils.js",
|
||||||
"PeerManager": "/static/PeerManager.js"
|
"PeerManager": "/static/PeerManager.js",
|
||||||
|
"log": "/static/log.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user