UNPKG

@bitxenia/astrachat

Version:

Decentralized p2p real-time chat on IPFS

228 lines (221 loc) 7.66 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); // src/index.ts import { MemoryBlockstore } from "blockstore-core"; import { MemoryDatastore } from "datastore-core"; // src/utils/logger.ts var levels = ["debug", "info", "warn", "error"]; var currentLevel = "warn"; function shouldLog(level) { return levels.indexOf(level) >= levels.indexOf(currentLevel); } function logWithTimestamp(method, level, args) { if (shouldLog(level)) { const timestamp = (/* @__PURE__ */ new Date()).toISOString(); method(`[${timestamp}] [${level.toUpperCase()}]`, ...args); } } var logger = { setLevel(level) { if (levels.includes(level)) { currentLevel = level; } }, debug(...args) { logWithTimestamp(console.debug, "debug", args); }, info(...args) { logWithTimestamp(console.info, "info", args); }, warn(...args) { logWithTimestamp(console.warn, "warn", args); }, error(...args) { logWithTimestamp(console.error, "error", args); } }; // src/message.ts function createMessage(sender, senderAlias, message, parentId) { return { id: crypto.randomUUID(), parentId: parentId || "", sender, senderAlias, message, timestamp: Date.now() }; } function constructMessage(rawMessage) { const message = JSON.parse(rawMessage); if (!isChatMessage(message)) { logger.error("Parsed object isn't a ChatMessage. Chat returned: ", message); throw new Error("Parsed object isn't a ChatMessage"); } return message; } function isChatMessage(obj) { return typeof obj === "object" && obj !== null && typeof obj.id === "string" && typeof obj.sender === "string" && typeof obj.senderAlias === "string" && typeof obj.message === "string" && typeof obj.timestamp === "number"; } // src/chat.ts var Chat = class _Chat { constructor(chatSpace, chatName, messages, astraDb, onNewMessage) { __publicField(this, "chatSpace"); __publicField(this, "chatName"); __publicField(this, "astraDb"); __publicField(this, "messages"); this.chatSpace = chatSpace; this.chatName = chatName; this.astraDb = astraDb; this.messages = messages; if (onNewMessage) { this.listenToNewMessages(onNewMessage); } else { this.listenToNewMessages((_) => { }); } } static async create(chatSpace, chatName, astraDb, onNewMessage) { logger.debug("Creating Chat..."); let messages = []; const allChats = await astraDb.getAllKeys(); if (allChats.includes(chatName)) { logger.info("Existing Chat found, getting messages..."); const rawMessages = await astraDb.get(chatName); messages = rawMessages.map( (rawMessage) => constructMessage(rawMessage) ); logger.info(`Found ${messages.length} messages in existing Chat`); } return new _Chat(chatSpace, chatName, messages, astraDb, onNewMessage); } getMessages() { logger.info(`Getting messages for ${this.chatName}`); this.messages.sort((a, b) => a.timestamp - b.timestamp); return this.messages; } async sendMessage(message) { logger.info(`Sending message in ${this.chatName}: ${message}`); await this.astraDb.add(this.chatName, JSON.stringify(message)); } listenToNewMessages(callback) { logger.info(`Setting new callback for ${this.chatName}`); this.astraDb.events.on( `${this.chatSpace}::${this.chatName}`, (value) => { const newMessage = constructMessage(value); logger.info("New message detected in callback: ", newMessage.id); logger.debug("Calling set callback..."); callback(newMessage); } ); } }; // src/astrachat.ts import { createAstraDb } from "@bitxenia/astradb"; var AstrachatNode = class { constructor(chatSpace) { __publicField(this, "chatSpace"); __publicField(this, "alias"); __publicField(this, "astraDb"); this.chatSpace = chatSpace; } async init(init) { logger.debug("Creating AstraDb..."); this.astraDb = await createAstraDb({ dbName: this.chatSpace, loginKey: init.loginKey, isCollaborator: init.isCollaborator, datastore: init.datastore, blockstore: init.blockstore, publicIp: init.publicIp, tcpPort: init.tcpPort, webRTCDirectPort: init.webrtcDirectPort, dataDir: init.dataDir, bootstrapProviderPeers: init.bootstrapProviderPeers, offlineMode: init.offlineMode }); logger.debug("AstraDb created"); if (!init.alias) { logger.debug("No alias found, setting public key as alias"); } this.alias = init.alias || this.astraDb.getLoginPublicKey(); } async createChat(chatName, callback) { logger.debug(`[${this.alias}] Creating chat...`); const chat = await Chat.create( this.chatSpace, chatName, this.astraDb, callback ); const message = createMessage(this.getUserId(), this.alias, "Chat created"); await chat.sendMessage(message); logger.debug(`[${this.alias}] Chat "${chatName}" created`); } async getMessages(chatName, onNewMessage) { logger.info(`[${this.alias}] Getting messages from ${chatName}...`); const chat = await Chat.create( this.chatSpace, chatName, this.astraDb, onNewMessage ); logger.debug(`[${this.alias}] Chat found, getting messages...`); return chat.getMessages(); } async sendMessage(chatName, text, parentId) { logger.debug(`[${this.alias}] Sending message for ${chatName}...`); const chat = await Chat.create(this.chatSpace, chatName, this.astraDb); const message = createMessage(this.getUserId(), this.alias, text, parentId); logger.debug(`[${this.alias}] Sending message with ID ${message.id}`); await chat.sendMessage(message); logger.info(`[${this.alias}] Message with ID ${chatName} sent`); } async getChatList() { logger.info(`[${this.alias}] Getting chat list for ${this.chatSpace}`); return await this.astraDb.getAllKeys(); } getUserId() { logger.debug(`[${this.alias}] Getting user id...`); return this.astraDb.getLoginPublicKey(); } async getLoginKey() { logger.debug(`[${this.alias}] Getting logging key...`); return await this.astraDb.getLoginPrivateKey(); } getAlias() { return this.alias; } setChatAlias(alias) { this.alias = alias; logger.info(`[${this.alias}] New alias set: ${alias}`); } async getNodeMultiaddrs() { return await this.astraDb.getNodeMultiaddrs(); } }; // src/index.ts async function createAstrachat(init) { init.chatSpace = init.chatSpace ?? "bitxenia-chat"; init.alias = init.alias ?? ""; init.loginKey = init.loginKey ?? ""; init.isCollaborator = init.isCollaborator ?? false; init.datastore = init.datastore ?? new MemoryDatastore(); init.blockstore = init.blockstore ?? new MemoryBlockstore(); init.publicIp = init.publicIp ?? "0.0.0.0"; init.tcpPort = init.tcpPort ?? 50001; init.webrtcDirectPort = init.webrtcDirectPort ?? 50001; init.dataDir = init.dataDir ?? "./data/astrachat"; init.bootstrapProviderPeers = init.bootstrapProviderPeers ?? []; init.offlineMode = init.offlineMode ?? false; if (init.logLevel) logger.setLevel(init.logLevel); logger.info("Creating Astrachat node..."); const node = new AstrachatNode(init.chatSpace); await node.init(init); logger.info("Astrachat node created & initialized"); return node; } export { createAstrachat };