UNPKG

actionhero

Version:

The reusable, scalable, and quick node.js API server for stateless and stateful applications

239 lines (238 loc) 9.78 kB
"use strict"; 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 = {}));