Skip to content

Instantly share code, notes, and snippets.

@giuseppeg
Last active June 29, 2022 18:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save giuseppeg/ff628aaeaea3a97ff4cd9eff59d96860 to your computer and use it in GitHub Desktop.
Save giuseppeg/ff628aaeaea3a97ff4cd9eff59d96860 to your computer and use it in GitHub Desktop.
// 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