@bitxenia/astrachat
Version:
Decentralized p2p real-time chat on IPFS
228 lines (221 loc) • 7.66 kB
JavaScript
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
};