fchat-rising/webchat/logs.ts

121 lines
5.7 KiB
TypeScript

import {EventMessage, Message} from '../chat/common';
import core from '../chat/core';
import {Conversation, Logs as Logging, Settings} from '../chat/interfaces';
type StoredConversation = {id: number, key: string, name: string};
type StoredMessage = {
id: number, conversation: number, type: Conversation.Message.Type, sender: string, text: string, time: Date, day: number
};
async function promisifyRequest<T>(req: IDBRequest): Promise<T> {
return new Promise<T>((resolve, reject) => {
req.onsuccess = () => resolve(<T>req.result);
req.onerror = reject;
});
}
async function promisifyTransaction(req: IDBTransaction): Promise<Event> {
return new Promise<Event>((resolve, reject) => {
req.oncomplete = resolve;
req.onerror = reject;
});
}
async function iterate<S, T>(request: IDBRequest, map: (stored: S) => T): Promise<ReadonlyArray<T>> {
const array: T[] = [];
return new Promise<ReadonlyArray<T>>((resolve, reject) => {
request.onsuccess = function(): void {
const c = <IDBCursorWithValue | undefined>this.result;
if(!c) return resolve(array); //tslint:disable-line:strict-boolean-expressions
array.push(map(<S>c.value));
c.continue();
};
request.onerror = reject;
});
}
const dayMs = 86400000;
export class Logs implements Logging {
index!: {[key: string]: StoredConversation | undefined};
db!: IDBDatabase;
constructor() {
core.connection.onEvent('connecting', async() => {
const request = window.indexedDB.open('logs');
request.onupgradeneeded = () => {
const db = <IDBDatabase>request.result;
const logsStore = db.createObjectStore('logs', {keyPath: 'id', autoIncrement: true});
logsStore.createIndex('conversation', 'conversation');
logsStore.createIndex('conversation-day', ['conversation', 'day']);
db.createObjectStore('conversations', {keyPath: 'id', autoIncrement: true});
};
this.db = await promisifyRequest<IDBDatabase>(request);
const trans = this.db.transaction(['conversations']);
this.index = {};
await iterate(trans.objectStore('conversations').openCursor(), (x: StoredConversation) => this.index[x.key] = x);
});
}
async logMessage(conversation: Conversation, message: Conversation.Message): Promise<void> {
const trans = this.db.transaction(['logs', 'conversations'], 'readwrite');
let conv = this.index[conversation.key];
if(conv === undefined) {
const convId = await promisifyRequest<number>(trans.objectStore('conversations').add(
{key: conversation.key, name: conversation.name}));
this.index[conversation.key] = conv = {id: convId, key: conversation.key, name: conversation.name};
}
const sender = message.type === Conversation.Message.Type.Event ? undefined : message.sender.name;
const day = Math.floor(message.time.getTime() / dayMs - message.time.getTimezoneOffset() / 1440);
await promisifyRequest<number>(trans.objectStore('logs').put(
{conversation: conv.id, type: message.type, sender, text: message.text, date: message.time, day}));
await promisifyTransaction(trans);
}
async getBacklog(conversation: Conversation): Promise<ReadonlyArray<Conversation.Message>> {
const trans = this.db.transaction(['logs', 'conversations']);
const conv = this.index[conversation.key];
if(conv === undefined) return [];
return iterate(trans.objectStore('logs').index('conversation').openCursor(conv.id, 'prev'),
(value: StoredMessage) => value.type === Conversation.Message.Type.Event ? new EventMessage(value.text, value.time) :
new Message(value.type, core.characters.get(value.sender), value.text, value.time));
}
get conversations(): ReadonlyArray<{key: string, name: string}> {
return Object.keys(this.index).map((k) => this.index[k]!);
}
async getLogs(key: string, date: Date): Promise<ReadonlyArray<Conversation.Message>> {
const trans = this.db.transaction(['logs']);
const id = this.index[key]!.id;
const day = Math.floor(date.getTime() / dayMs - date.getTimezoneOffset() / 1440);
return iterate(trans.objectStore('logs').index('conversation-day').openCursor([id, day]),
(value: StoredMessage) => value.type === Conversation.Message.Type.Event ? new EventMessage(value.text, value.time) :
new Message(value.type, core.characters.get(value.sender), value.text, value.time));
}
async getLogDates(key: string): Promise<ReadonlyArray<Date>> {
const trans = this.db.transaction(['logs']);
const offset = new Date().getTimezoneOffset() * 1440;
const id = this.index[key]!.id;
const bound = IDBKeyRange.bound([id, 0], [id, 100000]);
return iterate(trans.objectStore('logs').index('conversation-day').openCursor(bound, 'nextunique'),
(value: StoredMessage) => new Date(value.day * dayMs + offset));
}
}
export class SettingsStore implements Settings.Store {
async get<K extends keyof Settings.Keys>(key: K): Promise<Settings.Keys[K]> {
const stored = window.localStorage.getItem(`settings.${key}`);
return Promise.resolve(stored !== null ? JSON.parse(stored) : undefined);
}
async set<K extends keyof Settings.Keys>(key: K, value: Settings.Keys[K]): Promise<void> {
window.localStorage.setItem(`settings.${key}`, JSON.stringify(value));
return Promise.resolve();
}
async getAvailableCharacters(): Promise<ReadonlyArray<string>> {
return Promise.resolve([]);
}
}