actionhero
Version:
The reusable, scalable, and quick node.js API server for stateless and stateful applications
239 lines (238 loc) • 9.78 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.chatRoom = void 0;
const index_1 = require("./../index");
var chatRoom;
(function (chatRoom) {
/**
* Middleware definition for processing chat events. Can be of the
*
* ```js
* var chatMiddleware = {
* name: 'chat middleware',
* priority: 1000,
* join: (connection, room) => {
* // announce all connections entering a room
* api.chatRoom.broadcast(null, room, 'I have joined the room: ' + connection.id, callback)
* },
* leave:(connection, room, callback) => {
* // announce all connections leaving a room
* api.chatRoom.broadcast(null, room, 'I have left the room: ' + connection.id, callback)
* },
* // Will be executed once per client connection before delivering the message.
* say: (connection, room, messagePayload) => {
* // do stuff
* log(messagePayload)
* },
* // Will be executed only once, when the message is sent to the server.
* onSayReceive: (connection, room, messagePayload) => {
* // do stuff
* log(messagePayload)
* }
* }
* api.chatRoom.addMiddleware(chatMiddleware)
* ```
*/
function client() {
if (index_1.api.redis.clients && index_1.api.redis.clients.client) {
return index_1.api.redis.clients.client;
}
else {
throw new Error("redis not connected, chatRoom cannot be used");
}
}
chatRoom.client = client;
/**
* Add a middleware component to connection handling.
*/
async function addMiddleware(data) {
if (!data.name) {
throw new Error("middleware.name is required");
}
if (!data.priority) {
data.priority = index_1.config.general.defaultMiddlewarePriority;
}
data.priority = Number(data.priority);
index_1.api.chatRoom.middleware[data.name] = data;
index_1.api.chatRoom.globalMiddleware.push(data.name);
index_1.api.chatRoom.globalMiddleware.sort((a, b) => {
if (index_1.api.chatRoom.middleware[a].priority >
index_1.api.chatRoom.middleware[b].priority) {
return 1;
}
else {
return -1;
}
});
}
chatRoom.addMiddleware = addMiddleware;
/**
* List all chat rooms created
*/
async function list() {
return client().smembers(index_1.api.chatRoom.keys.rooms);
}
chatRoom.list = list;
/**
* Add a new chat room. Throws an error if the room already exists.
*/
async function add(room) {
const found = await chatRoom.exists(room);
if (found === false) {
return client().sadd(index_1.api.chatRoom.keys.rooms, room);
}
else {
throw new Error(await index_1.config.errors.connectionRoomExists(room));
}
}
chatRoom.add = add;
/**
* Remove an existing chat room. All connections in the room will be removed. Throws an error if the room does not exist.
*/
async function destroy(room) {
const found = await chatRoom.exists(room);
if (found === true) {
await index_1.api.chatRoom.broadcast(null, room, await index_1.config.errors.connectionRoomHasBeenDeleted(room));
const membersHash = await client().hgetall(index_1.api.chatRoom.keys.members + room);
for (const id in membersHash) {
await chatRoom.removeMember(id, room, false);
}
await client().srem(index_1.api.chatRoom.keys.rooms, room);
await client().del(index_1.api.chatRoom.keys.members + room);
}
else {
throw new Error(await index_1.config.errors.connectionRoomNotExist(room));
}
}
chatRoom.destroy = destroy;
/**
* Check if a room exists.
*/
async function exists(room) {
const isMember = await client().sismember(index_1.api.chatRoom.keys.rooms, room);
let found = false;
if (isMember === 1 || isMember.toString() === "true")
found = true;
return found;
}
chatRoom.exists = exists;
/**
* Configures what properties of connections in a room to return via `api.chatRoom.roomStatus`
*/
async function sanitizeMemberDetails(memberData) {
return {
id: memberData.id,
joinedAt: memberData.joinedAt,
};
}
chatRoom.sanitizeMemberDetails = sanitizeMemberDetails;
/**
* Learn about the connections in the room.
* Returns a hash of the form { room: room, members: cleanedMembers, membersCount: count }. Members is an array of connections in the room sanitized via `api.chatRoom.sanitizeMemberDetails`
*/
async function roomStatus(room) {
if (room) {
const found = await chatRoom.exists(room);
if (found === true) {
const key = index_1.api.chatRoom.keys.members + room;
const members = (await index_1.api.redis.clients.client.hgetall(key));
const cleanedMembers = {};
let count = 0;
for (const id in members) {
const data = JSON.parse(members[id]);
cleanedMembers[id] = await chatRoom.sanitizeMemberDetails(data);
count++;
}
return {
room: room,
members: cleanedMembers,
membersCount: count,
};
}
else {
throw new Error(await index_1.config.errors.connectionRoomNotExist(room));
}
}
else {
throw new Error(await index_1.config.errors.connectionRoomRequired());
}
}
chatRoom.roomStatus = roomStatus;
/**
* An overwrite-able method which configures what properties of connections in a room are initially stored about a connection when added via `api.chatRoom.addMember`
*/
async function generateMemberDetails(connection) {
return {
id: connection.id,
joinedAt: new Date().getTime(),
host: index_1.id,
};
}
chatRoom.generateMemberDetails = generateMemberDetails;
/**
* Add a connection (via id) to a room. Throws errors if the room does not exist, or the connection is already in the room. Middleware errors also throw.
*/
async function addMember(connectionId, room) {
const connection = index_1.api.connections.connections[connectionId];
if (!connection) {
return index_1.redis.doCluster("api.chatRoom.addMember", [connectionId, room], connectionId, true);
}
if (connection.rooms.includes(room)) {
throw new Error(await index_1.config.errors.connectionAlreadyInRoom(connection, room));
}
if (!connection.rooms.includes(room)) {
const found = await chatRoom.exists(room);
if (!found) {
throw new Error(await index_1.config.errors.connectionRoomNotExist(room));
}
await index_1.api.chatRoom.runMiddleware(connection, room, "join");
if (!connection.rooms.includes(room)) {
connection.rooms.push(room);
}
const memberDetails = await chatRoom.generateMemberDetails(connection);
await client().hset(index_1.api.chatRoom.keys.members + room, connection.id, JSON.stringify(memberDetails));
}
return true;
}
chatRoom.addMember = addMember;
/**
* Remote a connection (via id) from a room. Throws errors if the room does not exist, or the connection is not in the room. Middleware errors also throw.
* toWaitRemote: Should this method wait until the remote Actionhero server (the one the connection is connected too) responds?
*/
async function removeMember(connectionId, room, toWaitRemote = true) {
const connection = index_1.api.connections.connections[connectionId];
if (!connection) {
return index_1.redis.doCluster("api.chatRoom.removeMember", [connectionId, room], connectionId, toWaitRemote);
}
if (!connection.rooms.includes(room)) {
throw new Error(await index_1.config.errors.connectionNotInRoom(connection, room));
}
if (connection.rooms.includes(room)) {
const found = await chatRoom.exists(room);
if (!found) {
throw new Error(await index_1.config.errors.connectionRoomNotExist(room));
}
await index_1.api.chatRoom.runMiddleware(connection, room, "leave");
if (connection.rooms.includes(room)) {
const index = connection.rooms.indexOf(room);
connection.rooms.splice(index, 1);
}
await client().hdel(index_1.api.chatRoom.keys.members + room, connection.id);
}
return true;
}
chatRoom.removeMember = removeMember;
/**
* Send a message to all clients connected to this room
* - connection:
* - {} send to every connections
* - should either be a real client you are emulating (found in api.connections)
* - a mock
* - room is the string name of an already-existing room
* - message can be anything: string, json, object, etc
*/
async function broadcast(connection, room, message) {
return index_1.api.chatRoom.broadcast(connection, room, message);
}
chatRoom.broadcast = broadcast;
})(chatRoom || (exports.chatRoom = chatRoom = {}));