working websocket and bootstrap peer reconnection when connection is lost
This commit is contained in:
@@ -33,11 +33,16 @@ export class PeerManager {
|
||||
|
||||
websocket: WebSocket | null = null;
|
||||
bootstrapPeerID: string | null = null;
|
||||
connectPromise: { resolve: Function, reject: Function } | null = null;
|
||||
connectPromiseCallbacks: { resolve: Function, reject: Function } | null = null;
|
||||
connectPromise: Promise<null> | null = null;
|
||||
|
||||
pingPeers: RTCPeerConnection[] = [];
|
||||
watchdogPeriodSeconds: number = 10;
|
||||
eventListeners: Map<PeerEventTypes, Function[]> = new Map();
|
||||
reconnectPeriod: number = 10;
|
||||
messageSuperlog = false;
|
||||
watchdogInterval: number = 0;
|
||||
reconnectTimer: number | null = null;
|
||||
|
||||
// async watchdog() {
|
||||
// // Check that we're connected to at least N peers. If not, reconnect to the bootstrap server.
|
||||
@@ -60,7 +65,7 @@ export class PeerManager {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log.apply(null, log("<-signaler:", message));
|
||||
this.messageSuperlog && console.log.apply(null, log("<-signaler:", message));
|
||||
|
||||
this.websocket.send(messageJSON);
|
||||
}
|
||||
@@ -77,7 +82,7 @@ export class PeerManager {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
console.log.apply(null, log("->signaler:", message));
|
||||
this.messageSuperlog && console.log.apply(null, log("->signaler:", message));
|
||||
|
||||
if (message.type === "hello2") {
|
||||
|
||||
@@ -105,7 +110,15 @@ export class PeerManager {
|
||||
}
|
||||
|
||||
if (!peerConnection) {
|
||||
peerConnection = this.onConnectRequest(message);
|
||||
let remotePeerID = message.from;
|
||||
let newPeer = new PeerConnection(this, remotePeerID, this.websocketSendPeerMessage.bind(this));
|
||||
if (this.isBootstrapPeer) {
|
||||
newPeer.setPolite(false);
|
||||
}
|
||||
peerConnection = newPeer;
|
||||
this.peers.set(newPeer.remotePeerID, newPeer);
|
||||
|
||||
this.onConnectRequest(newPeer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,35 +132,36 @@ export class PeerManager {
|
||||
}
|
||||
|
||||
}
|
||||
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);
|
||||
async onConnectRequest(newPeer: PeerConnection) {
|
||||
// let remotePeerID = message.from;
|
||||
// let newPeer = new PeerConnection(this, remotePeerID, this.websocketSendPeerMessage.bind(this));
|
||||
// if (this.isBootstrapPeer) {
|
||||
// newPeer.setPolite(false);
|
||||
// }
|
||||
await newPeer.connect();
|
||||
this.onPeerConnected(newPeer.remotePeerID);
|
||||
return newPeer;
|
||||
}
|
||||
|
||||
async onHello2Received(bootstrapPeerID: string) {
|
||||
|
||||
if (this.isBootstrapPeer) {
|
||||
this.connectPromise?.resolve();
|
||||
this.connectPromiseCallbacks?.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bootstrapPeerID) {
|
||||
console.log.apply(null, log("Didn't get bootstrap peer, waiting 10 seconds..."));
|
||||
setTimeout(async (e: Event) => { await this.sendHello2() }, 10_000);
|
||||
// console.log.apply(null, log("Didn't get bootstrap peer, waiting 10 seconds..."));
|
||||
// let callSendHello2OnTimeout = () => { console.log(this, "jajajajaj");this.sendHello2() };
|
||||
// setTimeout(callSendHello2OnTimeout, 5_000);
|
||||
return;
|
||||
}
|
||||
|
||||
this.bootstrapPeerConnection = await this.connectToPeer(bootstrapPeerID);
|
||||
this.connectPromise?.resolve();
|
||||
this.connectPromiseCallbacks?.resolve();
|
||||
}
|
||||
|
||||
async sendHello2() {
|
||||
sendHello2() {
|
||||
this.websocketSend({
|
||||
type: "hello2",
|
||||
user_id: this.userID,
|
||||
@@ -195,45 +209,34 @@ export class PeerManager {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
connect() {
|
||||
// setInterval(this.watchdog.bind(this), this.watchdogPeriodSeconds * 1000);
|
||||
|
||||
setInterval(()=>{
|
||||
|
||||
|
||||
if (!this.isBootstrapPeer && this.peers.size === 0) {
|
||||
console.log.apply(null, log("No peers connected :("));
|
||||
if (this.websocket?.readyState === WebSocket.OPEN) {
|
||||
this.sendHello2();
|
||||
}
|
||||
}
|
||||
|
||||
let output = `local peerID:${logID(this.peerID)}` + "\n";
|
||||
for (let [peerID, peer] of this.peers) {
|
||||
output += `${logID(peerID)}: ${peer.rtcPeer?.connectionState}` + "\n";
|
||||
}
|
||||
|
||||
console.log.apply(null, log(output));
|
||||
|
||||
|
||||
|
||||
}, 5000);
|
||||
|
||||
let connectPromise = new Promise((resolve, reject) => {
|
||||
this.connectPromise = { resolve, reject };
|
||||
});
|
||||
|
||||
connectWebSocket() {
|
||||
try {
|
||||
let hostname = globalThis?.location?.hostname ?? 'ddln.app';
|
||||
|
||||
let port = globalThis?.location?.port ?? '443';
|
||||
|
||||
let wsURL = `wss://${hostname}:${port}/ws`;
|
||||
|
||||
console.log(`wsURL: ${wsURL}`);
|
||||
console.log.apply(null, log(`Attempting to connect websocket to URL: ${wsURL}`));
|
||||
|
||||
this.websocket = new WebSocket(wsURL);
|
||||
// this.websocket.onclose = (e: CloseEvent) => {
|
||||
|
||||
// let closedUnexpectedly = !e.wasClean;
|
||||
|
||||
// if (closedUnexpectedly) {
|
||||
// console.log.apply(null, log(`Websocket closed unexpectedly. Will try to reconnect in ${this.reconnectPeriod} seconds`));
|
||||
// // let alreadyReconnecting = this.reconnectTimer !== null;
|
||||
// // if (!alreadyReconnecting) {
|
||||
// this.reconnectTimer = globalThis.setTimeout(() => {
|
||||
// console.log.apply(null, log(`Reconnecting web socket`));
|
||||
|
||||
// this.reconnectTimer = null;
|
||||
// this.connectWebSocket();
|
||||
// }, this.reconnectPeriod * 1000)
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
} catch (error: any) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
@@ -244,6 +247,52 @@ export class PeerManager {
|
||||
};
|
||||
|
||||
this.websocket.onmessage = this.onWebsocketMessage.bind(this);
|
||||
}
|
||||
|
||||
connect() {
|
||||
// setInterval(this.watchdog.bind(this), this.watchdogPeriodSeconds * 1000);
|
||||
|
||||
|
||||
// Side effects :(
|
||||
if (!this.watchdogInterval) {
|
||||
this.watchdogInterval = setInterval(() => {
|
||||
|
||||
|
||||
if (!this.isBootstrapPeer && this.peers.size === 0) {
|
||||
console.log.apply(null, log(`No peers connected, will attempt to reconnect in ${this.reconnectPeriod} seconds...`));
|
||||
|
||||
// Websocket reconnect
|
||||
if (this.websocket?.readyState === WebSocket.OPEN) {
|
||||
this.sendHello2();
|
||||
}
|
||||
|
||||
if (this.websocket?.readyState === WebSocket.CLOSED) {
|
||||
this.connectWebSocket();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
let output = `local peerID:${logID(this.peerID)}` + "\n";
|
||||
for (let [peerID, peer] of this.peers) {
|
||||
output += `${logID(peerID)}: ${peer.rtcPeer?.connectionState}` + "\n";
|
||||
}
|
||||
|
||||
console.log.apply(null, log(output));
|
||||
}, this.reconnectPeriod * 1000);
|
||||
}
|
||||
|
||||
let connectPromise = this.connectPromise;
|
||||
|
||||
if (!connectPromise) {
|
||||
connectPromise = new Promise((resolve, reject) => {
|
||||
this.connectPromiseCallbacks = { resolve, reject };
|
||||
});
|
||||
|
||||
this.connectPromise = connectPromise;
|
||||
}
|
||||
|
||||
this.connectWebSocket();
|
||||
|
||||
return connectPromise;
|
||||
|
||||
@@ -257,7 +306,7 @@ export class PeerManager {
|
||||
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
|
||||
// Need to decide if they should 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
|
||||
@@ -274,10 +323,11 @@ export class PeerManager {
|
||||
}
|
||||
|
||||
onPeerConnected(peerID: PeerID) {
|
||||
this.dispatchEvent(PeerEventTypes.PEER_CONNECTED, {peerID:peerID});
|
||||
console.log.apply(null, log(`PeerManager: Successfully connected to peer ${peerID}`));
|
||||
this.dispatchEvent(PeerEventTypes.PEER_CONNECTED, { peerID: peerID });
|
||||
}
|
||||
|
||||
dispatchEvent(event:PeerEventTypes, parameters:any) {
|
||||
dispatchEvent(event: PeerEventTypes, parameters: any) {
|
||||
let listeners = this.eventListeners.get(event);
|
||||
|
||||
if (!listeners) {
|
||||
@@ -290,7 +340,7 @@ export class PeerManager {
|
||||
}
|
||||
|
||||
|
||||
addEventListener(eventName:PeerEventTypes, func:Function) {
|
||||
addEventListener(eventName: PeerEventTypes, func: Function) {
|
||||
let listeners = this.eventListeners.get(eventName);
|
||||
if (!listeners) {
|
||||
this.eventListeners.set(eventName, [func]);
|
||||
@@ -315,7 +365,7 @@ export class PeerManager {
|
||||
this.bootstrapPeerConnection = null;
|
||||
}
|
||||
|
||||
this.dispatchEvent(PeerEventTypes.PEER_DISCONNECTED, {peerID:remotePeerID});
|
||||
this.dispatchEvent(PeerEventTypes.PEER_DISCONNECTED, { peerID: remotePeerID });
|
||||
|
||||
}
|
||||
|
||||
@@ -326,7 +376,7 @@ export class PeerManager {
|
||||
console.log.apply(null, log(`PeerManager.disconnect: couldn't find peer ${remotePeerID}`));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
console.log.apply(null, log(`PeerManager.disconnect: disconnecting peer ${remotePeerID}`));
|
||||
await peer.disconnect();
|
||||
this.peers.delete(remotePeerID);
|
||||
@@ -388,7 +438,7 @@ interface Message {
|
||||
}
|
||||
|
||||
class PeerConnection {
|
||||
private remotePeerID: string;
|
||||
remotePeerID: string;
|
||||
// private signaler: Signaler;
|
||||
private peerManager: PeerManager;
|
||||
private dataChannel: RTCDataChannel | null = null;
|
||||
@@ -398,6 +448,8 @@ class PeerConnection {
|
||||
private ignoreOffer: boolean = false;
|
||||
private isSettingRemoteAnswerPending: boolean = false;
|
||||
private polite = true;
|
||||
private webRTCSuperlog = false;
|
||||
private dataChannelSuperlog = false;
|
||||
|
||||
// private makingOffer:boolean = false;
|
||||
// private ignoreOffer:boolean = false;
|
||||
@@ -407,7 +459,7 @@ class PeerConnection {
|
||||
static config = {
|
||||
iceServers: [
|
||||
{ urls: "stun:ddln.app" },
|
||||
// { urls: "turn:ddln.app", username: "a", credential: "b" },
|
||||
{ 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" },
|
||||
@@ -420,7 +472,7 @@ class PeerConnection {
|
||||
string,
|
||||
{ resolve: Function; reject: Function; functionName: string }
|
||||
> = new Map();
|
||||
messageSuperlog: boolean = true;
|
||||
messageSuperlog: boolean = false;
|
||||
connectionPromise: { resolve: (value?: unknown) => void; reject: (reason?: any) => void; } | null = null;
|
||||
|
||||
async RPCHandler(message: any) {
|
||||
@@ -451,42 +503,45 @@ class PeerConnection {
|
||||
if (!this.dataChannel) {
|
||||
throw new Error();
|
||||
}
|
||||
console.log.apply(null, log("data channel is open to: ", this.remotePeerID, " from: ", this.peerManager.peerID));
|
||||
this.send({ type: "hello datachannel", from: this.peerManager.peerID, to: this.remotePeerID});
|
||||
this.dataChannelSuperlog && console.log.apply(null, log("data channel is open to: ", this.remotePeerID, " from: ", this.peerManager.peerID));
|
||||
this.send({ type: "hello datachannel", from: this.peerManager.peerID, to: this.remotePeerID });
|
||||
// this.dataChannel?.send(`{typeHello datachannel from: ${this.peerManager.peerID}`);
|
||||
|
||||
console.log.apply(null, 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.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.connectionPromise?.resolve(this.remotePeerID);
|
||||
|
||||
//globalThis.setTimeout(()=>this.connectionPromise?.resolve(this.remotePeerID), 5000);
|
||||
|
||||
}
|
||||
|
||||
this.dataChannel.onmessage = (e: MessageEvent) => {
|
||||
console.log.apply(null, log("->datachannel: ", e.data))
|
||||
this.dataChannelSuperlog && console.log.apply(null, log("->datachannel: ", e.data));
|
||||
this.onMessage(e.data);
|
||||
}
|
||||
|
||||
this.dataChannel.onclose = (e: Event) => {
|
||||
console.log.apply(null, log(`datachannel from peer ${this.remotePeerID} closed, disconnecting peer.`));
|
||||
this.dataChannelSuperlog && console.log.apply(null, log(`datachannel from peer ${this.remotePeerID} closed, disconnecting peer.`));
|
||||
this.peerManager.disconnectFromPeer(this.remotePeerID);
|
||||
}
|
||||
}
|
||||
|
||||
async connect() {
|
||||
let connectionPromise = new Promise((resolve, reject) => this.connectionPromise = { resolve, reject });
|
||||
let connectionPromise = new Promise((resolve, reject) => { this.connectionPromise = { resolve, reject } });
|
||||
this.rtcPeer = new RTCPeerConnection(PeerConnection.config);
|
||||
|
||||
this.rtcPeer.onconnectionstatechange = async (e: any) => {
|
||||
console.log.apply(null, log(`rtcPeer: onconnectionstatechange: ${this.rtcPeer?.connectionState}: ${this.remotePeerID}`));
|
||||
this.webRTCSuperlog && console.log.apply(null, log(`rtcPeer: onconnectionstatechange: ${this.rtcPeer?.connectionState}: ${this.remotePeerID}`));
|
||||
|
||||
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 === "failed") {
|
||||
this.peerManager.onPeerDisconnected(this.remotePeerID);
|
||||
@@ -507,7 +562,7 @@ class PeerConnection {
|
||||
let localCandidate = stats.get(candidatePair.localCandidateId);
|
||||
let remoteCandidate = stats.get(candidatePair.remoteCandidateId);
|
||||
|
||||
console.log.apply(null, log("Connected candidates\n", localCandidate, remoteCandidate));
|
||||
this.webRTCSuperlog && console.log.apply(null, log("Connected candidates\n", localCandidate, remoteCandidate));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -536,12 +591,12 @@ class PeerConnection {
|
||||
|
||||
|
||||
this.rtcPeer.onicecandidate = ({ candidate }) => {
|
||||
console.log.apply(null, log(candidate));
|
||||
this.webRTCSuperlog && console.log.apply(null, log(candidate));
|
||||
this.sendPeerMessage(this.remotePeerID, { type: "rtc_candidate", candidate: candidate });
|
||||
}
|
||||
|
||||
this.rtcPeer.onnegotiationneeded = async (event) => {
|
||||
console.log.apply(null, log("on negotiation needed fired"));
|
||||
this.webRTCSuperlog && console.log.apply(null, log("on negotiation needed fired"));
|
||||
|
||||
if (!this.rtcPeer) {
|
||||
throw new Error();
|
||||
@@ -709,4 +764,4 @@ class PeerConnection {
|
||||
|
||||
// this.peerManger.onMessage(this.remotePeerID, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user