Last active
June 29, 2022 18:40
-
-
Save giuseppeg/ff628aaeaea3a97ff4cd9eff59d96860 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Adapted from https://github.com/go717franciswang/peerjs-chatroom | |
const CLIENT_MSG = { | |
CHAT: 1, | |
GET_MEMBERS: 2, | |
}; | |
const SERVER_MSG = { | |
CHAT: 1, | |
MEMBERS: 2, | |
NEW_MEMBER: 3, | |
LOST_MEMBER: 4, | |
}; | |
export function chatroom(config = {}) { | |
let Peer; | |
let conn = null; | |
let ownpeerid = null; | |
let isConnected = false; | |
let clients = {}; | |
let refreshMembersInterval = null; | |
async function connect() { | |
if (isConnected) { | |
throw new Error("already connected. disconnect first"); | |
} | |
isConnected = true; | |
const [loading, onSuccess, onError] = createPromise(); | |
Peer = (await import("peerjs")).Peer; | |
// Try to create a peer as a server. | |
// roomId is unique and only one can have that id | |
// i.e. only one can be the server. | |
let server = new Peer(config.roomId, config.peerjs); | |
server.on("error", (e) => { | |
// server exists -> connect as a client | |
if (e.toString().match(/ID.*is taken/)) { | |
server = null; | |
createClient().then(() => { | |
config.onEvent("info", "Client created successfully"); | |
onSuccess(); | |
}, onError); | |
} else { | |
onError(e); | |
} | |
}); | |
server.on("open", () => { | |
config.onEvent("info", "Server created successfully"); | |
server.on("connection", (conn) => { | |
conn.on("error", (e) => { | |
config.onEvent("error", "error while connecting to client " + e); | |
}); | |
conn.on("data", (data) => { | |
switch (data.type) { | |
case CLIENT_MSG.CHAT: { | |
broadcast({ | |
...data, | |
type: SERVER_MSG.CHAT, | |
}); | |
break; | |
} | |
case CLIENT_MSG.GET_MEMBERS: { | |
const members = []; | |
for (let id in clients) { | |
if (id != conn.peer) { | |
members.push({ peerid: id, nick: clients[id].nick }); | |
} | |
} | |
// New member | |
const isNewMember = clients[conn.peer] == null; | |
if (isNewMember) { | |
clients[conn.peer] = { conn, nick: data.nick }; | |
} | |
conn.send({ | |
type: SERVER_MSG.MEMBERS, | |
members, | |
}); | |
// New member | |
if (isNewMember) { | |
broadcast({ | |
type: SERVER_MSG.NEW_MEMBER, | |
peerid: conn.peer, | |
nick: data.nick, | |
}); | |
} | |
break; | |
} | |
} | |
}); | |
conn.on("close", () => { | |
removeClient(clients[conn.peer]); | |
}); | |
}); | |
createClient().then(onSuccess, onError); | |
}); | |
return loading.then(() => { | |
return { | |
peerid: ownpeerid, | |
send: (data) => { | |
if (ownpeerid) { | |
sendMessage({ ...data, nick: config.nick, peerid: ownpeerid }); | |
} | |
}, | |
disconnect, | |
}; | |
}); | |
} | |
function createClient() { | |
const client = new Peer(); | |
const [loading, onSuccess, onError] = createPromise(); | |
client.on("error", (e) => { | |
onError(e); | |
}); | |
client.on("open", () => { | |
const ownId = client.id; | |
conn = client.connect(config.roomId); | |
let initializing = true; | |
conn.on("open", () => { | |
conn.on("error", (e) => { | |
config.onEvent("error", "connection error " + e); | |
}); | |
conn.on("data", (data) => { | |
switch (data.type) { | |
case SERVER_MSG.CHAT: { | |
if (data.peerid !== ownpeerid) { | |
config.onEvent("message", data); | |
} | |
break; | |
} | |
case SERVER_MSG.NEW_MEMBER: { | |
if (data.peerid !== ownpeerid) { | |
config.onEvent("new_member", { | |
peerid: data.peerid, | |
nick: data.nick, | |
}); | |
} | |
break; | |
} | |
case SERVER_MSG.LOST_MEMBER: { | |
config.onEvent("lost_member", { | |
peerid: data.peerid, | |
nick: data.nick, | |
}); | |
break; | |
} | |
case SERVER_MSG.MEMBERS: { | |
if (initializing) { | |
initializing = false; | |
if (data.members.some((m) => m.nick == config.nick)) { | |
config.nick += (Math.random() * 1000).toFixed(0); | |
// conn.close(); | |
// conn = null; | |
// return onError("nick already in use"); | |
} | |
conn.on("close", () => { | |
disconnect(); | |
config.onEvent("status", "connecting"); | |
connect(); | |
}); | |
// Poll members. | |
if (refreshMembersInterval) | |
clearInterval(refreshMembersInterval); | |
refreshMembersInterval = setInterval( | |
() => refreshMembers(conn), | |
(config.refreshMembersInterval || 30) * 1000 | |
); | |
ownpeerid = ownId; | |
onSuccess(); | |
config.onEvent("status", "connected"); | |
} | |
config.onEvent("members", data.members); | |
break; | |
} | |
} | |
}); | |
// Upon connection ask the server for the list of members. | |
// In SERVER_MSG.MEMBERS we check that the current nick is not taken | |
// before finishing the client initialization. | |
conn.send({ | |
type: CLIENT_MSG.GET_MEMBERS, | |
peerid: conn.peer, | |
nick: config.nick, | |
}); | |
// Once p2p connection is established, client no longer needs to be connected to peerjs server. | |
client.disconnect(); | |
}); | |
}); | |
return loading; | |
} | |
function disconnect() { | |
if (refreshMembersInterval) clearInterval(refreshMembersInterval); | |
for (let id in clients) { | |
removeClient(clients[id]); | |
} | |
conn = null; | |
clients = {}; | |
ownpeerid = null; | |
isConnected = false; | |
} | |
function removeClient(client) { | |
const { conn, nick } = client; | |
conn.close(); | |
delete clients[conn.peer]; | |
broadcast({ | |
type: SERVER_MSG.LOST_MEMBER, | |
peerid: conn.peer, | |
nick, | |
}); | |
} | |
function broadcast(data) { | |
for (let id in clients) { | |
clients[id].conn.send(data); | |
} | |
} | |
function refreshMembers(conn) { | |
conn.send({ | |
type: CLIENT_MSG.GET_MEMBERS, | |
peerid: conn.peer, | |
nick: config.nick, | |
}); | |
} | |
function sendMessage(data) { | |
if (!conn) { | |
return; | |
} | |
conn.send({ | |
...data, | |
type: CLIENT_MSG.CHAT, | |
}); | |
} | |
return connect; | |
} | |
function createPromise() { | |
let resolve, reject; | |
const promise = new Promise((res, rej) => { | |
resolve = res; | |
reject = rej; | |
}); | |
return [promise, resolve, reject]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment