import { Login, ProfileEntity, defaultProfileEntity, ConfigEntity, defaultConfigEntity } from "./entities"; import type { ArticleDetail, ArticleItem, ChannelItem, CardItem, CardProfile, CardDetail, ChannelSummary, ChannelDetail } from "./items"; import type { Logging } from "./logging"; export interface Store { init(): Promise; setLogin(login: Login): Promise; clearLogin(): Promise; getSeal(guid: string): Promise<{ publicKey: string; privateKey: string } | null>; setSeal(guid: string, seal: { publicKey: string; privateKey: string }): Promise; clearSeal(guid: string): Promise; getProfileRevision(guid: string): Promise; setProfileRevision(guid: string, revision: number): Promise; getProfileData(guid: string): Promise; setProfileData(guid: string, data: ProfileEntity): Promise; getSettingsRevision(guid: string): Promise; setSettingsRevision(guid: string, revision: number): Promise; getSettingsData(guid: string): Promise; setSettingsData(guid: string, data: ConfigEntity): Promise; getContactRevision(guid: string): Promise; setContactRevision(guid: string, revision: number): Promise; getContacts(guid: string): Promise<{ cardId: string; item: CardItem }[]>; addContactCard(guid: string, cardId: string, item: CardItem): Promise; removeContactCard(guid: string, cardId: string): Promise; setContactCardRevision(guid: string, cardId: string, revision: number): Promise; setContactCardProfile(guid: string, cardId: string, profile: CardProfile): Promise; setContactCardDetail(guid: string, cardId: string, detail: CardDetail): Promise; setContactCardBlocked(guid: string, cardId: string, blocked: boolean): Promise; setContactCardOffsync(guid: string, cardId: string, offsync: boolean): Promise; setContactCardProfileRevision(guid: string, cardId: string, revision: number): Promise; setContactCardArticlesRevision(guid: string, cardId: string, revision: number): Promise; setContactCardChannelsRevision(guid: string, cardId: string, revision: number): Promise; getContactCardArticles(guid: string): Promise<{ cardId: string; articleId: string; item: ArticleItem }[]>; addContactCardArticle(guid: string, cardId: string, articleId: string, item: ArticleItem): Promise; removeContactCardArticle(guid: string, cardId: string, articleId: string): Promise; setContactCardArticleDetail(guid: string, cardId: string, articleId: string, detail: ChannelDetail, unsealedData: string | null): Promise; setContactCardArticleUnsealed(guid: string, cardId: string, articleId: string, unsealedData: string | null): Promise; getContactCardChannels(guid: string): Promise<{ cardId: string; channelId: string; item: ChannelItem }[]>; addContactCardChannel(guid: string, cardId: string, channelId: string, item: ChannelItem): Promise; removeContactCardChannel(guid: string, cardId: string, channelId: string): Promise; setContactCardChannelBlocked(guid: string, cardId: string, channelId: string, blocked: boolean): Promise; setContactCardChannelDetail(guid: string, cardId: string, channelId: string, detail: ChannelDetail, unsealedData: string): Promise; setContactCardChannelSummary(guid: string, cardId: string, channelId: string, summary: ChannelSummary, unsealedData: string): Promise; setContactCardChannelUnsealedDetail(guid: string, cardId: string, channelId: string, data: string | null): Promise; setContactCardChannelUnsealedSummary(guid: string, cardId: string, channelId: string, data: string | null): Promise; setContactCardChannelTopicSyncRevision(guid: string, cardId: string, channelId: string, revision: number): Promise; setContactCardChannelTopicRemoteRevision(guid: string, cardId: string, channelId: string, revision: number): Promise; } export interface SqlStore { set(stmt: string, params?: (string | number | null)[]): Promise; get(stmt: string, params?: (string | number | null)[]): Promise; } export interface WebStore { getValue(key: string): Promise; setValue(key: string, value: string): Promise; clearValue(key: string): Promise; clearAll(): Promise; } export class OfflineStore implements Store { private sql: SqlStore; private log: Logging; constructor(log: Logging, sql: SqlStore) { this.sql = sql; this.log = log; } private async getAppValue(guid: string, id: string, unset: any): Promise { try { const rows = await this.sql.get(`SELECT * FROM app WHERE key='${guid}::${id}';`); if (rows.length == 1 && rows[0].value != null) { return JSON.parse(rows[0].value); } } catch (err) { console.log(err); } return unset; } private async setAppValue(guid: string, id: string, value: any): Promise { await this.sql.set("INSERT OR REPLACE INTO app (key, value) values (?, ?)", [`${guid}::${id}`, JSON.stringify(value)]); } private async clearAppValue(guid: string, id: string): Promise { await this.sql.set("INSERT OR REPLACE INTO app (key, value) values (?, null)", [`${guid}::${id}`]); } private async initLogin(guid: string): Promise { await this.sql.set( `CREATE TABLE IF NOT EXISTS channel_${guid} (channel_id text, revision integer, detail_revision integer, topic_revision integer, topic_marker integer, blocked integer, sync_revision integer, detail text, unsealed_detail text, summary text, unsealed_summary text, offsync integer, read_revision integer, unique(channel_id))`, ); await this.sql.set( `CREATE TABLE IF NOT EXISTS channel_topic_${guid} (channel_id text, topic_id text, revision integer, created integer, detail_revision integer, blocked integer, detail text, unsealed_detail text, unique(channel_id, topic_id))`, ); await this.sql.set( `CREATE TABLE IF NOT EXISTS card_${guid} (card_id text, revision integer, detail_revision integer, profile_revision integer, detail text, profile text, notified_view integer, notified_article integer, notified_profile integer, notified_channel integer, offsync integer, blocked integer, unique(card_id))`, ); await this.sql.set( `CREATE TABLE IF NOT EXISTS card_channel_${guid} (card_id text, channel_id text, revision integer, detail_revision integer, topic_revision integer, topic_marker integer, sync_revision integer, detail text, unsealed_detail text, summary text, unsealed_summary text, offsync integer, blocked integer, read_revision integer, unique(card_id, channel_id))`, ); await this.sql.set( `CREATE TABLE IF NOT EXISTS card_channel_topic_${guid} (card_id text, channel_id text, topic_id text, revision integer, created integer, detail_revision integer, blocked integer, detail text, unsealed_detail text, unique(card_id, channel_id, topic_id))`, ); } public async init(): Promise { await this.sql.set("CREATE TABLE IF NOT EXISTS app (key text, value text, unique(key));"); return (await this.getAppValue("", "login", null)) as Login | null; } public async setLogin(login: Login): Promise { await this.initLogin(login.guid); await this.setAppValue("", "login", login); } public async clearLogin(): Promise { await this.clearAppValue("", "login"); } public async getSeal(guid: string): Promise<{ publicKey: string; privateKey: string } | null> { return (await this.getAppValue(guid, "seal", null)) as { publicKey: string; privateKey: string; } | null; } public async setSeal(guid: string, seal: { publicKey: string; privateKey: string }): Promise { await this.setAppValue(guid, "seal", seal); } public async clearSeal(guid: string): Promise { await this.clearAppValue(guid, "seal"); } public async getProfileRevision(guid: string): Promise { return (await this.getAppValue(guid, "profile_revision", 0)) as number; } public async setProfileRevision(guid: string, revision: number): Promise { await this.setAppValue(guid, "profile_revision", revision); } public async getProfileData(guid: string): Promise { return (await this.getAppValue(guid, "profile_data", defaultProfileEntity)) as ProfileEntity; } public async setProfileData(guid: string, data: ProfileEntity): Promise { await this.setAppValue(guid, "profile_data", data); } public async getSettingsRevision(guid: string): Promise { return (await this.getAppValue(guid, "account_revision", 0)) as number; } public async setSettingsRevision(guid: string, revision: number): Promise { await this.setAppValue(guid, "account_revision", revision); } public async getSettingsData(guid: string): Promise { return (await this.getAppValue(guid, "account_data", defaultConfigEntity)) as ConfigEntity; } public async setSettingsData(guid: string, data: ConfigEntity): Promise { await this.setAppValue(guid, "account_data", data); } public async getContactRevision(guid: string): Promise { return 0; } public async setContactRevision(guid: string, revision: number): Promise {} public async getContacts(guid: string): Promise<{ cardId: string; item: CardItem }[]> { return []; } public async addContactCard(guid: string, cardId: string, item: CardItem): Promise {} public async removeContactCard(guid: string, cardId: string): Promise {} public async setContactCardRevision(guid: string, cardId: string, revision: number): Promise {} public async setContactCardProfile(guid: string, cardId: string, profile: CardProfile): Promise {} public async setContactCardDetail(guid: string, cardId: string, detail: CardDetail): Promise {} public async setContactCardBlocked(guid: string, cardId: string, blocked: boolean): Promise {} public async setContactCardOffsync(guid: string, cardId: string, offsync: boolean): Promise {} public async setContactCardProfileRevision(guid: string, cardId: string, revision: number): Promise {} public async setContactCardArticlesRevision(guid: string, cardId: string, revision: number): Promise {} public async setContactCardChannelsRevision(guid: string, cardId: string, revision: number): Promise {} public async getContactCardArticles(guid: string): Promise<{ cardId: string; articleId: string; item: ArticleItem }[]> { return []; } public async addContactCardArticle(guid: string, cardId: string, articleId: string, item: ArticleItem): Promise {} public async removeContactCardArticle(guid: string, cardId: string, articleId: string): Promise {} public async setContactCardArticleDetail(guid: string, cardId: string, articleId: string, detail: ChannelDetail, unsealedData: string | null): Promise {} public async setContactCardArticleUnsealed(guid: string, cardId: string, articleId: string, unsealedData: string | null): Promise {} public async getContactCardChannels(guid: string): Promise<{ cardId: string; channelId: string; item: ChannelItem }[]> { return []; } public async addContactCardChannel(guid: string, cardId: string, channelId: string, item: ChannelItem): Promise {} public async removeContactCardChannel(guid: string, cardId: string, channelId: string): Promise {} public async setContactCardChannelBlocked(guid: string, cardId: string, channelId: string, blocked: boolean): Promise {} public async setContactCardChannelDetail(guid: string, cardId: string, channelId: string, detail: ChannelDetail, unsealedData: string): Promise {} public async setContactCardChannelSummary(guid: string, cardId: string, channelId: string, summary: ChannelSummary, unsealedData: string): Promise {} public async setContactCardChannelUnsealedDetail(guid: string, cardId: string, channelId: string, data: string | null): Promise {} public async setContactCardChannelUnsealedSummary(guid: string, cardId: string, channelId: string, data: string | null): Promise {} public async setContactCardChannelTopicSyncRevision(guid: string, cardId: string, channelId: string, revision: number): Promise {} public async setContactCardChannelTopicRemoteRevision(guid: string, cardId: string, channelId: string, revision: number): Promise {} } export class OnlineStore implements Store { private web: WebStore; private log: Logging; constructor(log: Logging, web: WebStore) { this.web = web; this.log = log; } private async getAppValue(guid: string, id: string, unset: any): Promise { const value = await this.web.getValue(`${guid}::${id}`); if (value != null) { return JSON.parse(value); } return unset; } private async setAppValue(guid: string, id: string, value: any): Promise { await this.web.setValue(`${guid}::${id}`, JSON.stringify(value)); } private async clearAppValue(guid: string, id: string): Promise { await this.web.clearValue(`${guid}::${id}`); } public async init(): Promise { return (await this.getAppValue("", "login", null)) as Login | null; } public async setLogin(login: Login): Promise { await this.setAppValue("", "login", login); } public async clearLogin(): Promise { await this.clearAppValue("", "login"); } public async getSeal(guid: string): Promise<{ publicKey: string; privateKey: string } | null> { return (await this.getAppValue(guid, "seal", null)) as { publicKey: string; privateKey: string; } | null; } public async setSeal(guid: string, seal: { publicKey: string; privateKey: string }): Promise { await this.setAppValue(guid, "seal", seal); } public async clearSeal(guid: string): Promise { await this.clearAppValue(guid, "seal"); } public async getProfileRevision(guid: string): Promise { return 0; } public async setProfileRevision(guid: string, revision: number): Promise {} public async getProfileData(guid: string): Promise { return defaultProfileEntity; } public async setProfileData(guid: string, data: ProfileEntity): Promise {} public async getSettingsRevision(guid: string): Promise { return 0; } public async setSettingsRevision(guid: string, revision: number): Promise {} public async getSettingsData(guid: string): Promise { return defaultConfigEntity; } public async setSettingsData(guid: string, data: ConfigEntity): Promise {} public async getContactRevision(guid: string): Promise { return 0; } public async setContactRevision(guid: string, revision: number): Promise {} public async getContacts(guid: string): Promise<{ cardId: string; item: CardItem }[]> { return []; } public async addContactCard(guid: string, cardId: string, item: CardItem): Promise {} public async removeContactCard(guid: string, cardId: string): Promise {} public async setContactCardRevision(guid: string, cardId: string, revision: number): Promise {} public async setContactCardProfile(guid: string, cardId: string, profile: CardProfile): Promise {} public async setContactCardDetail(guid: string, cardId: string, detail: CardDetail): Promise {} public async setContactCardBlocked(guid: string, cardId: string, blocked: boolean): Promise {} public async setContactCardOffsync(guid: string, cardId: string, offsync: boolean): Promise {} public async setContactCardProfileRevision(guid: string, cardId: string, revision: number): Promise {} public async setContactCardArticlesRevision(guid: string, cardId: string, revision: number): Promise {} public async setContactCardChannelsRevision(guid: string, cardId: string, revision: number): Promise {} public async getContactCardArticles(guid: string): Promise<{ cardId: string; articleId: string; item: ArticleItem }[]> { return []; } public async addContactCardArticle(guid: string, cardId: string, articleId: string, item: ArticleItem): Promise {} public async removeContactCardArticle(guid: string, cardId: string, articleId: string): Promise {} public async setContactCardArticleDetail(guid: string, cardId: string, articleId: string, detail: ChannelDetail, unsealedData: string | null): Promise {} public async setContactCardArticleUnsealed(guid: string, cardId: string, articleId: string, unsealedData: string | null): Promise {} public async getContactCardChannels(guid: string): Promise<{ cardId: string; channelId: string; item: ChannelItem }[]> { return []; } public async addContactCardChannel(guid: string, cardId: string, channelId: string, item: ChannelItem): Promise {} public async removeContactCardChannel(guid: string, cardId: string, channelId: string): Promise {} public async setContactCardChannelBlocked(guid: string, cardId: string, channelId: string, blocked: boolean): Promise {} public async setContactCardChannelDetail(guid: string, cardId: string, channelId: string, detail: ChannelDetail, unsealedData: string): Promise {} public async setContactCardChannelSummary(guid: string, cardId: string, channelId: string, summary: ChannelSummary, unsealedData: string): Promise {} public async setContactCardChannelUnsealedDetail(guid: string, cardId: string, channelId: string, data: string | null): Promise {} public async setContactCardChannelUnsealedSummary(guid: string, cardId: string, channelId: string, data: string | null): Promise {} public async setContactCardChannelTopicSyncRevision(guid: string, cardId: string, channelId: string, revision: number): Promise {} public async setContactCardChannelTopicRemoteRevision(guid: string, cardId: string, channelId: string, revision: number): Promise {} } export class NoStore implements Store { constructor() {} public async init(): Promise { return null; } public async setLogin(login: Login): Promise {} public async clearLogin(): Promise {} public async getSeal(guid: string): Promise<{ publicKey: string; privateKey: string } | null> { return null; } public async setSeal(guid: string, seal: { publicKey: string; privateKey: string }): Promise {} public async clearSeal(guid: string): Promise {} public async getProfileRevision(guid: string): Promise { return 0; } public async setProfileRevision(guid: string, revision: number): Promise {} public async getProfileData(guid: string): Promise { return defaultProfileEntity; } public async setProfileData(guid: string, data: ProfileEntity): Promise {} public async getSettingsRevision(guid: string): Promise { return 0; } public async setSettingsRevision(guid: string, revision: number): Promise {} public async getSettingsData(guid: string): Promise { return defaultConfigEntity; } public async setSettingsData(guid: string, data: ConfigEntity): Promise {} public async getContactRevision(guid: string): Promise { return 0; } public async setContactRevision(guid: string, revision: number): Promise {} public async getContacts(guid: string): Promise<{ cardId: string; item: CardItem }[]> { return []; } public async addContactCard(guid: string, cardId: string, item: CardItem): Promise {} public async removeContactCard(guid: string, cardId: string): Promise {} public async setContactCardRevision(guid: string, cardId: string, revision: number): Promise {} public async setContactCardProfile(guid: string, cardId: string, profile: CardProfile): Promise {} public async setContactCardDetail(guid: string, cardId: string, detail: CardDetail): Promise {} public async setContactCardBlocked(guid: string, cardId: string, blocked: boolean): Promise {} public async setContactCardOffsync(guid: string, cardId: string, offsync: boolean): Promise {} public async setContactCardProfileRevision(guid: string, cardId: string, revision: number): Promise {} public async setContactCardArticlesRevision(guid: string, cardId: string, revision: number): Promise {} public async setContactCardChannelsRevision(guid: string, cardId: string, revision: number): Promise {} public async getContactCardArticles(guid: string): Promise<{ cardId: string; articleId: string; item: ArticleItem }[]> { return []; } public async addContactCardArticle(guid: string, cardId: string, articleId: string, item: ArticleItem): Promise {} public async removeContactCardArticle(guid: string, cardId: string, articleId: string): Promise {} public async setContactCardArticleDetail(guid: string, cardId: string, articleId: string, detail: ChannelDetail, unsealedData: string | null): Promise {} public async setContactCardArticleUnsealed(guid: string, cardId: string, articleId: string, unsealedData: string | null): Promise {} public async getContactCardChannels(guid: string): Promise<{ cardId: string; channelId: string; item: ChannelItem }[]> { return []; } public async addContactCardChannel(guid: string, cardId: string, channelId: string, item: ChannelItem): Promise {} public async removeContactCardChannel(guid: string, cardId: string, channelId: string): Promise {} public async setContactCardChannelBlocked(guid: string, cardId: string, channelId: string, blocked: boolean): Promise {} public async setContactCardChannelDetail(guid: string, cardId: string, channelId: string, detail: ChannelDetail, unsealedData: string): Promise {} public async setContactCardChannelSummary(guid: string, cardId: string, channelId: string, summary: ChannelSummary, unsealedData: string): Promise {} public async setContactCardChannelUnsealedDetail(guid: string, cardId: string, channelId: string, data: string | null): Promise {} public async setContactCardChannelUnsealedSummary(guid: string, cardId: string, channelId: string, data: string | null): Promise {} public async setContactCardChannelTopicSyncRevision(guid: string, cardId: string, channelId: string, revision: number): Promise {} public async setContactCardChannelTopicRemoteRevision(guid: string, cardId: string, channelId: string, revision: number): Promise {} }