Skip to content

Commit

Permalink
refactor network module (#22)
Browse files Browse the repository at this point in the history
* refactor network module

* make it null before destroy, fix #20
  • Loading branch information
dai-shi authored Apr 12, 2020
1 parent 895b672 commit 57532a5
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 85 deletions.
79 changes: 57 additions & 22 deletions web/src/network/peerUtils.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,62 @@
import Peer from "peerjs";

export type Conn = Peer["connections"][0];
export const isValidPeerJsId = (
roomId: string,
peerJsId: unknown
): peerJsId is string =>
typeof peerJsId === "string" && peerJsId.startsWith(`${roomId}_`);

export const isConnectedConn = (conn: Conn, ignoreConnecting = false) => {
if (!conn) return false;
const peerConn = conn.peerConnection;
if (!peerConn) return false;
const connState = peerConn.connectionState;
if (connState === "connected") return true;
if (!ignoreConnecting) {
if (connState === "connecting" || connState === "new") {
return true;
}
}
// for safari
if (!connState && peerConn.signalingState === "stable") return true;
return false;
};
export const generatePeerJsId = (roomId: string, peerId: number) =>
`${roomId}_${peerId}`;

export const getPeerIdFromPeerJsId = (peerJsId: string) =>
Number(peerJsId.split("_")[1]);

export const getPeerIdFromConn = (conn: Peer.DataConnection) =>
getPeerIdFromPeerJsId(conn.peer);

export const getLivePeers = (myPeer: Peer) => {
const peers = Object.keys(myPeer.connections);
const livePeers = peers.filter((peer) =>
myPeer.connections[peer].some((conn: Conn) => isConnectedConn(conn, true))
);
return livePeers;
export const createConnectionMap = () => {
type Value = {
conn: Peer.DataConnection;
live: boolean;
};
const map = new Map<string, Value>();
const addConn = (conn: Peer.DataConnection) => {
map.set(conn.peer, { conn, live: false });
};
const markLive = (conn: Peer.DataConnection) => {
const value = map.get(conn.peer);
if (value) {
value.live = true;
}
};
const isLive = (peerJsId: string) => {
const value = map.get(peerJsId);
return value ? value.live : false;
};
const hasConn = (peerJsId: string) => map.has(peerJsId);
const delConn = (conn: Peer.DataConnection) => {
const value = map.get(conn.peer);
if (value && value.conn === conn) {
map.delete(conn.peer);
}
};
const getLivePeerJsIds = () =>
Array.from(map.keys()).filter((k) => map.get(k)?.live);
const forEachLiveConns = (callback: (conn: Peer.DataConnection) => void) => {
for (const value of map.values()) {
if (value.live) {
callback(value.conn);
}
}
};
return {
addConn,
markLive,
isLive,
hasConn,
delConn,
getLivePeerJsIds,
forEachLiveConns,
};
};
115 changes: 52 additions & 63 deletions web/src/network/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ import Peer from "peerjs";

import { rand4 } from "../utils/crypto";
import { sleep } from "../utils/sleep";
import { Conn, isConnectedConn, getLivePeers } from "./peerUtils";
import {
isValidPeerJsId,
generatePeerJsId,
getPeerIdFromPeerJsId,
getPeerIdFromConn,
createConnectionMap,
} from "./peerUtils";

const SEED_PEERS = 5; // config
const guessSeed = (id: string) => Number(id.split("_")[1]) < SEED_PEERS;
const guessSeed = (id: string) => getPeerIdFromPeerJsId(id) < SEED_PEERS;

export type NetworkStatus =
| { type: "CONNECTING_SEED_PEERS" }
Expand All @@ -27,7 +33,7 @@ const createMyPeer = (
updateNetworkStatus({ type: "INITIALIZING_PEER", index });
const isSeed = index < SEED_PEERS;
const peerId = isSeed ? index : rand4();
const id = `${roomId}_${peerId}`;
const id = generatePeerJsId(roomId, peerId);
console.log("createMyPeer", index, id);
const peer = new Peer(id);
return new Promise((resolve) => {
Expand Down Expand Up @@ -58,60 +64,45 @@ export const createRoom = (
) => {
let myPeer: Peer | null = null;
let lastBroadcastData: unknown | null = null;
const connMap = createConnectionMap();

const showConnectedStatus = (closedPeerId?: number) => {
const showConnectedStatus = () => {
if (!myPeer) return;
const peerIds = getLivePeers(myPeer)
.map((id) => Number(id.split("_")[1]))
.filter((peerId) => peerId !== closedPeerId);
const peerIds = connMap.getLivePeerJsIds().map(getPeerIdFromPeerJsId);
updateNetworkStatus({ type: "CONNECTED_PEERS", peerIds });
};

const connectPeer = (id: string) => {
if (!myPeer) return;
if (myPeer.id === id) return;
const conns = myPeer.connections[id];
const hasEffectiveConn = conns && conns.some(isConnectedConn);
if (hasEffectiveConn) return;
console.log(
"connectPeer",
id,
conns &&
conns.map(
(c: Conn) => c.peerConnection && c.peerConnection.connectionState
)
);
if (connMap.hasConn(id)) return;
console.log("connectPeer", id);
const conn = myPeer.connect(id, { serialization: "json" });
connMap.addConn(conn);
initConnection(conn);
};

const broadcastData = (data: unknown) => {
lastBroadcastData = data;
if (myPeer) {
Object.keys(myPeer.connections).forEach((key) => {
if (!myPeer) return;
const peers = getLivePeers(myPeer);
myPeer.connections[key].forEach((conn: Conn) => {
if (conn.open) {
try {
conn.send({ data, peers });
} catch (e) {
console.error("broadcastData", e);
}
}
});
});
}
if (!myPeer) return;
const peers = connMap.getLivePeerJsIds();
connMap.forEachLiveConns((conn) => {
try {
conn.send({ data, peers });
} catch (e) {
console.error("broadcastData", e);
}
});
};

const handlePayload = (conn: Conn, payload: unknown) => {
const handlePayload = (conn: Peer.DataConnection, payload: unknown) => {
try {
const peerId = Number(conn.peer.split("_")[1]);
const peerId = getPeerIdFromConn(conn);
if (payload && typeof payload === "object") {
receiveData(peerId, (payload as { data: unknown }).data);
if (Array.isArray((payload as { peers: unknown }).peers)) {
(payload as { peers: unknown[] }).peers.forEach((peer) => {
if (typeof peer === "string" && peer.startsWith(`${roomId}_`)) {
if (isValidPeerJsId(roomId, peer)) {
connectPeer(peer);
}
});
Expand All @@ -122,41 +113,43 @@ export const createRoom = (
}
};

const initConnection = (conn: Conn) => {
const initConnection = (conn: Peer.DataConnection) => {
conn.on("open", () => {
connMap.markLive(conn);
showConnectedStatus();
if (myPeer && lastBroadcastData) {
conn.send({
data: lastBroadcastData,
peers: getLivePeers(myPeer),
peers: connMap.getLivePeerJsIds(),
});
}
});
conn.on("data", (payload: unknown) => handlePayload(conn, payload));
conn.on("close", async () => {
connMap.delConn(conn);
console.log("dataConnection closed", conn);
showConnectedStatus(Number(conn.peer.split("_")[1]));
if (guessSeed(conn.peer)) reInitMyPeer(conn.peer);
});
conn.on("error", async (err: Error) => {
console.error("dataConnection error", conn, err);
showConnectedStatus(Number(conn.peer.split("_")[1]));
showConnectedStatus();
if (guessSeed(conn.peer)) reInitMyPeer(conn.peer);
});
};

const initMyPeer = async () => {
if (myPeer) return;
myPeer = await createMyPeer(0, roomId, updateNetworkStatus);
if (process.env.NODE_ENV !== "production") {
(window as any).myPeer = myPeer;
}
myPeer.on("connection", (conn) => {
console.log("new connection received", conn);
const peerId = Number(conn.peer.split("_")[1]);
const peerId = getPeerIdFromConn(conn);
updateNetworkStatus({ type: "NEW_CONNECTION", peerId });
connMap.addConn(conn);
initConnection(conn);
connMap.markLive(conn);
});
updateNetworkStatus({ type: "CONNECTING_SEED_PEERS" });
for (let i = 0; i < SEED_PEERS; i += 1) {
const id = `${roomId}_${i}`;
const id = generatePeerJsId(roomId, i);
connectPeer(id);
}
};
Expand All @@ -167,36 +160,32 @@ export const createRoom = (
if (guessSeed(myPeer.id)) return;
const waitSec = 30 + Math.floor(Math.random() * 60);
console.log(
"Disconnected seed peer: " +
disconnectedId.split("_")[1] +
", reinit in " +
waitSec +
"sec..."
`Disconnected seed peer: ${getPeerIdFromPeerJsId(
disconnectedId
)}, reinit in ${waitSec}sec...`
);
await sleep(waitSec * 1000);
if (!myPeer) return;
if (guessSeed(myPeer.id)) return;
let checkSeeds = true;
for (let i = 0; i < SEED_PEERS; i += 1) {
const id = `${roomId}_${i}`;
const conns = myPeer.connections[id] || [];
if (!conns.some(isConnectedConn)) {
checkSeeds = false;
}
}
if (checkSeeds) {
const existsAllSeeds = Array.from(Array(SEED_PEERS).keys()).every((i) => {
const id = generatePeerJsId(roomId, i);
return connMap.isLive(id);
});
if (existsAllSeeds) {
showConnectedStatus();
return;
}
myPeer.destroy();
const oldPeer = myPeer;
myPeer = null;
oldPeer.destroy();
initMyPeer();
};

const dispose = () => {
if (myPeer) {
myPeer.destroy();
// do not set null
const oldPeer = myPeer;
myPeer = null;
oldPeer.destroy();
}
};

Expand Down
1 change: 1 addition & 0 deletions web/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"dom.iterable",
"esnext"
],
"downlevelIteration": true,
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
Expand Down

0 comments on commit 57532a5

Please sign in to comment.