UNPKG

@solid-data-modules/chats-ldo

Version:

A library to mangage chats in Solid Pods built with LDO

280 lines 11.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Chat = void 0; const scheduleNewDayTrigger_js_1 = require("./util/scheduleNewDayTrigger.js"); const longChat_shapeTypes_js_1 = require("./.ldo/longChat.shapeTypes.js"); const resultHelpers_js_1 = require("./util/resultHelpers.js"); const uuid_1 = require("uuid"); class Chat { constructor(containerUri, dataset) { this.mostRecentMessageDate = new Date(0).toISOString(); this.containerResource = dataset.getResource(containerUri); this.chatResource = this.containerResource.child("index.ttl"); this.dataset = dataset; this.clearNewDayTrigger = (0, scheduleNewDayTrigger_js_1.scheduleNewDayTrigger)(this.onNewDay.bind(this)); } /** * =========================================================================== * HELPERS * =========================================================================== */ async onNewDay() { // Unsubscribe from the previous day's resource await this.endNotificationSubscription(); // Create a new document for today this.todaysMessageResource = await this.getOrCreateTodaysMessageResource(); // Subscribe to it if a subscription exists await this.startNotificationSubscription(); } /** * Gets the resource for today's messages. If it doesn't exist, create it. */ async getOrCreateTodaysMessageResource() { // Get Todays date const today = new Date(); const year = today.getUTCFullYear(); const month = today.getUTCMonth() + 1; const day = today.getUTCDate(); // Year const yearResource = (0, resultHelpers_js_1.getResource)(await this.containerResource.createChildIfAbsent(`${year}/`)); // Month const monthResource = (0, resultHelpers_js_1.getResource)(await yearResource.createChildIfAbsent(`${month}/`)); // Day const dayResource = (0, resultHelpers_js_1.getResource)(await monthResource.createChildIfAbsent(`${day}/`)); // Index const indexResource = (0, resultHelpers_js_1.getResource)(await dayResource.createChildIfAbsent(`index.ttl`)); return indexResource; } /** * Ensures today's message resource is identified */ async ensureTodaysMessageResource() { if (this.todaysMessageResource) return this.todaysMessageResource; this.todaysMessageResource = await this.getOrCreateTodaysMessageResource(); // Set the most recent message time const [mostRecentMessage] = this.getMessagesForMessageResource(this.todaysMessageResource); if (mostRecentMessage) this.mostRecentMessageDate = mostRecentMessage.created2; return this.todaysMessageResource; } /** * Given a message resource, find the message resource that is chronologically * previous to this one. Return undefined if there is none. * @param currentResource */ async getPreviousMessageResource(currentResource) { const getPreviousContainer = (list, target) => { const sorted = [...list] .filter((item) => item.type === "SolidContainer") .sort((a, b) => a.uri.localeCompare(b.uri)); const sliced = sorted.slice(0, sorted.findIndex(i => i.uri === target.uri)); return sliced.pop(); }; const getMostRecentContainer = (list) => [...list] .filter((item) => item.type === "SolidContainer") .sort((a, b) => a.uri.localeCompare(b.uri)) .pop(); const getIndexIfExists = async (container) => { const indexResource = container.child("index.ttl"); (0, resultHelpers_js_1.throwIfErrOrAbsent)(await indexResource.readIfUnfetched()); return indexResource; }; const drillDownMostRecent = async (container, levels) => { if (levels === 0) return getIndexIfExists(container); (0, resultHelpers_js_1.throwIfErrOrAbsent)(await container.readIfUnfetched()); const mostRecent = getMostRecentContainer(container.children()); if (!mostRecent) return undefined; return drillDownMostRecent(mostRecent, levels - 1); }; let container = await currentResource.getParentContainer(); // Day const levelNames = ["Month", "Year", "Eternity"]; // Logical structure for (let depth = 0; depth < levelNames.length; depth++) { const parent = await container.getParentContainer(); (0, resultHelpers_js_1.throwIfErrOrAbsent)(await parent.readIfUnfetched()); const previous = getPreviousContainer(parent.children(), container); if (previous) { (0, resultHelpers_js_1.throwIfErrOrAbsent)(await previous.readIfUnfetched()); return drillDownMostRecent(previous, depth); // Drill into [day, month, year] } container = parent; } return undefined; } /** * Begins notification subscription with today's message resource */ async startNotificationSubscription() { await this.ensureTodaysMessageResource(); if (this.subscriptionCallback) { console.log(this.todaysMessageResource.isPresent()); this.internalCallback = () => { console.log("Callback Called:", this.todaysMessageResource); const messages = this.getMessagesForMessageResource(this.todaysMessageResource); const newMessages = messages.filter((message) => { return message.created2 > this.mostRecentMessageDate; }); if (newMessages.length > 0) { this.mostRecentMessageDate = newMessages[0].created2; this.subscriptionCallback?.(newMessages); } }; this.subscriptionId = await this.todaysMessageResource .subscribeToNotifications({ onNotification: this.internalCallback.bind(this), }); } } /** * Ends notification subscription with today's message resource */ async endNotificationSubscription() { await this.ensureTodaysMessageResource(); if (this.subscriptionId) { await this.todaysMessageResource .unsubscribeFromNotifications(this.subscriptionId); this.subscriptionId = undefined; } } /** * Returns the messages in a resource sorted in order of most recent to least * recent * @param resource */ getMessagesForMessageResource(resource) { const chatMessageList = this.dataset .usingType(longChat_shapeTypes_js_1.ChatMessageShapeShapeType) .matchObject(`${this.chatResource.uri}#this`, "http://www.w3.org/2005/01/wf/flow#message", `${resource.uri}`); return chatMessageList.toArray() .sort((a, b) => b.created2.localeCompare(a.created2)) ?? []; } /** * =========================================================================== * PUBLIC METHODS * =========================================================================== */ /** * Creates a chat. Throws an error if it already esists * @param chatInfo */ async createChat(chatInfo) { const createResult = (0, resultHelpers_js_1.throwIfErr)(await this.chatResource.createIfAbsent()); if (createResult.type !== "createSuccess") throw new Error("Could not create chat. Chat already exists"); await this.setChatInfo(chatInfo); } /** * Gets a chat's info * @returns */ async getChatInfo() { const result = (0, resultHelpers_js_1.throwIfErrOrAbsent)(await this.chatResource.readIfUnfetched()); return this.dataset.usingType(longChat_shapeTypes_js_1.ChatShapeShapeType) .fromSubject(`${this.chatResource.uri}#this`); } async setChatInfo(chatInfo) { await this.chatResource.readIfUnfetched(); const chatInfoTransaction = this.dataset .startTransaction(); const cChatInfo = chatInfoTransaction .usingType(longChat_shapeTypes_js_1.ChatShapeShapeType) .write(this.chatResource.uri) .fromSubject(`${this.chatResource.uri}#this`); Object.assign(cChatInfo, chatInfo); (0, resultHelpers_js_1.throwIfErr)(await chatInfoTransaction.commitToRemote()); } /** * Deletes this chat */ async deleteChat() { await this.endNotificationSubscription(); (0, resultHelpers_js_1.throwIfErr)(await this.containerResource.delete()); } /** * Deletes the given message * @param messageId */ async removeMessage(messageId) { const messageTransaction = this.dataset .startTransaction(); const cMessage = messageTransaction .usingType(longChat_shapeTypes_js_1.ChatMessageShapeShapeType) .fromSubject(messageId); delete cMessage["@id"]; (0, resultHelpers_js_1.throwIfErr)(await messageTransaction.commitToRemote()); } /** * Sends a message to the chat * @param content * @param makerWebId */ async sendMessage(content, makerWebId) { await this.ensureTodaysMessageResource(); const messageTransaction = this.dataset .startTransaction(); const cChatMessageList = messageTransaction .usingType(longChat_shapeTypes_js_1.ChatMessageListShapeShapeType) .write(this.todaysMessageResource.uri) .fromSubject(`${this.chatResource.uri}#this`); const messageDate = new Date().toISOString(); cChatMessageList.message?.add({ "@id": `${this.todaysMessageResource.uri}#${(0, uuid_1.v4)()}`, content, maker: { "@id": makerWebId }, created2: messageDate, }); const result = (0, resultHelpers_js_1.throwIfErr)(await messageTransaction.commitToRemote()); this.mostRecentMessageDate = messageDate; } /** * Returns an iterator to fetch paginated messages. * @returns */ getMessageIterator() { const self = this; async function* messageGenerator() { await self.ensureTodaysMessageResource(); let currentResource = self.todaysMessageResource; while (currentResource) { const messages = self.getMessagesForMessageResource(currentResource); yield messages; currentResource = await self.getPreviousMessageResource(currentResource); } } return messageGenerator(); } /** * Subscribe to new incoming messages for this chat * @param callback * @returns */ async subscribeToMessages(callback) { // If Subscription Callback already exists, simply update it and continue // the subscription if (this.subscriptionCallback) { this.subscriptionCallback = callback; return; } this.subscriptionCallback = callback; // Begin subscription await this.startNotificationSubscription(); } /** * Unsubscribe from notifications */ async unsubscribeFromMessages() { this.subscriptionCallback = undefined; await this.endNotificationSubscription(); } /** * Call this before destroying the class to clean up listeners. */ async destroy() { this.clearNewDayTrigger?.(); await this.endNotificationSubscription(); } } exports.Chat = Chat; //# sourceMappingURL=Chat.js.map