From 40bb9c2a12b1bee4943ba67c6cf35937b00801d7 Mon Sep 17 00:00:00 2001 From: balzack Date: Mon, 2 Dec 2024 13:58:39 -0800 Subject: [PATCH] implementing focus module --- app/sdk/__tests__/contact.tsts.ts | 8 +- app/sdk/__tests__/content.tests.ts | 13 +- app/sdk/src/api.ts | 11 - app/sdk/src/entities.ts | 16 +- app/sdk/src/focus.ts | 319 ++++++++++--------- app/sdk/src/items.ts | 6 +- app/sdk/src/net/getChannelTopics.ts | 6 +- app/sdk/src/net/getContactChannelTopics.ts | 4 +- app/sdk/src/net/removeChannelTopic.ts | 2 +- app/sdk/src/net/removeContactChannelTopic.ts | 2 +- app/sdk/src/store.ts | 16 +- app/sdk/src/stream.ts | 10 +- app/sdk/src/types.ts | 16 +- 13 files changed, 223 insertions(+), 206 deletions(-) diff --git a/app/sdk/__tests__/contact.tsts.ts b/app/sdk/__tests__/contact.tsts.ts index 862c9bb2..7c6d912d 100644 --- a/app/sdk/__tests__/contact.tsts.ts +++ b/app/sdk/__tests__/contact.tsts.ts @@ -151,7 +151,7 @@ const crypto = new TestCrypto(); test('received contact updates', async () => { let testCards: Card[] = []; const update = (cards: Card[]) => { testCards = cards } - const contact = new ContactModule(log, store, crypto, 'test_guid', 'test_token', 'test_url', false, [], []); + const contact = new ContactModule(log, store, crypto, null, 'test_guid', 'test_token', 'test_url', false, [], []); contact.addCardListener(update); contact.setRevision(1) await waitFor(() => testCards.length === 1); @@ -162,7 +162,7 @@ test('received contact updates', async () => { test('adds new contact', async () => { let testCards: Card[] = []; const update = (cards: Card[]) => { testCards = cards } - const contact = new ContactModule(log, store, crypto, 'test_guid', 'test_token', 'test_url', false, [], []); + const contact = new ContactModule(log, store, crypto, null, 'test_guid', 'test_token', 'test_url', false, [], []); contact.addCardListener(update); contact.setRevision(3) await waitFor(() => testCards.length === 1); @@ -175,7 +175,7 @@ test('adds new contact', async () => { test('removes contact', async () => { let testCards: Card[] = []; const update = (cards: Card[]) => { testCards = cards } - const contact = new ContactModule(log, store, crypto, 'test_guid', 'test_token', 'test_url', false, [], []); + const contact = new ContactModule(log, store, crypto, null, 'test_guid', 'test_token', 'test_url', false, [], []); contact.addCardListener(update); contact.setRevision(8) await waitFor(() => testCards.length === 1); @@ -188,7 +188,7 @@ test('removes contact', async () => { test('connects and disconnects with known contact', async () => { let testCards: Card[] = []; const update = (cards: Card[]) => { testCards = cards } - const contact = new ContactModule(log, store, crypto, 'test_guid', 'test_token', 'test_url', false, [], []); + const contact = new ContactModule(log, store, crypto, null, 'test_guid', 'test_token', 'test_url', false, [], []); contact.addCardListener(update); contact.setRevision(11) await waitFor(() => testCards.length === 1); diff --git a/app/sdk/__tests__/content.tests.ts b/app/sdk/__tests__/content.tests.ts index 69acae02..5f8a5ee9 100644 --- a/app/sdk/__tests__/content.tests.ts +++ b/app/sdk/__tests__/content.tests.ts @@ -129,10 +129,10 @@ jest.mock('../src/net/fetchUtil', () => { url == 'http://test_url/content/channels?agent=test_token&types=%5B%5D') { return Promise.resolve({ status: 200, json: () => [getChannel('test_subject_0', 'test_message_0', 1)] }); } else if (url == 'https://URL_A/content/channels?contact=G000A.T000A&viewRevision=1&channelRevision=1&types=%5B%5D' || - url == 'http://test_url/content/channels?agent=test_token&revision=1&types=%5B%5D') { + url == 'http://test_url/content/channels?agent=test_token&channelRevision=1&types=%5B%5D') { return Promise.resolve({ status: 200, json: () => [getChannel('test_subject_1', 'test_message_1', 2)] }); } else if (url == 'https://URL_A/content/channels?contact=G000A.T000A&viewRevision=1&channelRevision=2&types=%5B%5D' || - url == 'http://test_url/content/channels?agent=test_token&revision=2&types=%5B%5D') { + url == 'http://test_url/content/channels?agent=test_token&channelRevision=2&types=%5B%5D') { return Promise.resolve({ status: 200, json: () => [getChannel('test_subject_2', 'test_message_2', 3)] }); } else if (url == 'https://URL_A/content/channels/CHAN1/detail?contact=G000A.T000A' || url == 'http://test_url/content/channels/CHAN1/detail?agent=test_token') { @@ -159,8 +159,8 @@ const store = new TestStore(); test('received contact updates', async () => { const cardChannels = new Map(); - const stream = new StreamModule(log, store, null, 'test_guid', 'test_token', 'test_url', false, []); - const contact = new ContactModule(log, store, null, 'test_guid', 'test_token', 'test_url', false, [], []); + const stream = new StreamModule(log, store, null, null, 'test_guid', 'test_token', 'test_url', false, []); + const contact = new ContactModule(log, store, null, null, 'test_guid', 'test_token', 'test_url', false, [], []); const content = new ContentModule(log, null, contact, stream); const channelUpdate = ({ channels, cardId }: { channels: Channel[]; cardId: string | null }) => { @@ -170,6 +170,7 @@ test('received contact updates', async () => { await contact.setRevision(1); await waitFor(() => cardChannels.get('C000A')?.length === 1); + await waitFor(() => cardChannels.get('C000A')?.[0].data.subject === 'test_subject_0'); await waitFor(() => cardChannels.get('C000A')?.[0].lastTopic.data.message === 'test_message_0'); @@ -186,8 +187,8 @@ test('received contact updates', async () => { test('received stream updates', async () => { const streamChannels = new Map(); - const stream = new StreamModule(log, store, null, 'test_guid', 'test_token', 'test_url', false, []); - const contact = new ContactModule(log, store, null, 'test_guid', 'test_token', 'test_url', false, [], []); + const stream = new StreamModule(log, store, null, null, 'test_guid', 'test_token', 'test_url', false, []); + const contact = new ContactModule(log, store, null, null, 'test_guid', 'test_token', 'test_url', false, [], []); const content = new ContentModule(log, null, contact, stream); const channelUpdate = ({ channels, cardId }: { channels: Channel[]; cardId: string | null }) => { diff --git a/app/sdk/src/api.ts b/app/sdk/src/api.ts index 3c9aaffa..04448739 100644 --- a/app/sdk/src/api.ts +++ b/app/sdk/src/api.ts @@ -72,19 +72,8 @@ export interface Contact { getBlockedCards(): Promise; getRegistry(handle: string | null, server: string | null): Promise; - leaveChannel(cardId: string, channelId: string): Promise; - flagChannel(cardId: string, channelId: string): Promise; - setBlockedChannel(cardId: string, channelId: string, blocked: boolean): Promise; - getBlockedChannels(): Promise; - setUnreadChannel(cardId: string, channelId: string, unread: boolean): Promise; - getChannelNotifications(cardId: string, channelId: string): Promise; - setChannelNotifications(cardId: string, channelId: string, enabled: boolean): Promise; - addCardListener(ev: (cards: Card[]) => void): void; removeCardListener(ev: (cards: Card[]) => void): void; - - addChannelListener(ev: (arg: { channels: Channel[]; cardId: string | null }) => void): void; - removeChannelListener(ev: (arg: { channels: Channel[]; cardId: string | null }) => void): void; } export interface Content { diff --git a/app/sdk/src/entities.ts b/app/sdk/src/entities.ts index de593615..67b55807 100644 --- a/app/sdk/src/entities.ts +++ b/app/sdk/src/entities.ts @@ -274,17 +274,19 @@ export type Login = { pushSupported: boolean; }; +export type BasicAsset = { + encrypted?: { type: string, thumb: string, label: string, extension: string, parts: { blockIv: string, partId: string }[] }, + image?: { thumb: string, full: string }, + audio?: { label: string, full: string }, + video?: { thumb: string, lq: string, hd: string }, + binary?: { label: string, extension: string, data: string } +} + export type BasicEntity = { text: string, textColor: string, textSize: string, - assets: { - encrypted?: { type: string, thumb: string, label: string, extension: string, parts: { blockIv: string, partId: string }[] }, - image?: { thumb: string, full: string }, - audio?: { label: string, full: string }, - video?: { thumb: string, lq: string, hd: string }, - binary?: { extension: string, data: string } - } + assets: BasicAsset[], } export type SealedBasicEntity = { diff --git a/app/sdk/src/focus.ts b/app/sdk/src/focus.ts index 7cb1b354..6b55fb0f 100644 --- a/app/sdk/src/focus.ts +++ b/app/sdk/src/focus.ts @@ -1,13 +1,13 @@ import { EventEmitter } from 'eventemitter3'; import type { Focus } from './api'; -import type { TopicItem} from './items'; +import type { TopicItem, AssetItem, TopicDetail } from './items'; import type { Topic, Asset, AssetSource, Participant } from './types'; -import { TransformType, HostingMode } from './types'; +import { TransformType, HostingMode, AssetType } from './types'; import type { Logging } from './logging'; import { Store } from './store'; import { Crypto } from './crypto'; import { Media } from './media'; -import { HostingMode } from './types'; +import { BasicEntity, BasicAsset, SealedBasicEntity, TopicDetailEntity } from './entities'; import { defaultTopicItem } from './items'; import { getChannelTopics } from './net/getChannelTopics'; import { getChannelTopicDetail } from './net/getChannelTopicDetail'; @@ -38,22 +38,23 @@ export class FocusModule implements Focus { private connection: { node: string; secure: boolean; token: string } | null; private syncing: boolean; private closing: boolean; - private nextRevision: number; - private cacheView: {revision: number | null, marker: number | null}; - private storeView: {topicId: string, position: number} | null; + private nextRevision: number | null; + private storeView: {revision: number | null, marker: number | null}; + private cacheView: {topicId: string, position: number} | null; private localComplete: boolean; private remoteComplete: boolean; private sealEnabled: boolean; private channelKey: string | null; private loadMore: boolean; - private closeMedia: (()=>Promsie)[]; + private closeMedia: (()=>Promise)[]; + private unsealAll: boolean; private markers: Set; // view of topics private topicEntries: Map; - constructor(log: Logging, store: Store, crypto: Crypto | null, media: Media | null, cardId: string | null, channelId: string, guid: string, connection: { node: string; secure: boolean; token: string } | null, channelKey: string, sealEnabled: boolean, revision: number) { + constructor(log: Logging, store: Store, crypto: Crypto | null, media: Media | null, cardId: string | null, channelId: string, guid: string, connection: { node: string; secure: boolean; token: string } | null, channelKey: string | null, sealEnabled: boolean, revision: number) { this.cardId = cardId; this.channelId = channelId; this.log = log; @@ -75,6 +76,7 @@ export class FocusModule implements Focus { this.closeMedia = []; this.nextRevision = null; this.loadMore = false; + this.unsealAll = false; this.localComplete = false; this.remoteComplete = false; this.init(revision); @@ -83,7 +85,7 @@ export class FocusModule implements Focus { private async init(revision: number) { const { guid } = this; this.nextRevision = revision; - this.storeView = this.getChannelTopicRevision(); + this.storeView = await this.getChannelTopicRevision(); // load markers const values = await this.store.getMarkers(guid); @@ -101,7 +103,6 @@ export class FocusModule implements Focus { private async sync(): Promise { if (!this.syncing) { this.syncing = true; - const { guid, node, secure, token, channelTypes } = this; while ((this.loadMore || this.unsealAll || this.nextRevision) && !this.closing && this.connection) { if (this.loadMore) { try { @@ -138,14 +139,14 @@ export class FocusModule implements Focus { entry.item.position = detail.created; await this.unsealTopicDetail(entry.item); entry.topic = this.setTopic(id, entry.item); - await this.setLocalChannelTopicDetail(id, detail, entry.item.unsealedDetail, detail.created); + await this.setLocalChannelTopicDetail(id, entry.item.detail, entry.item.unsealedDetail, detail.created); } } else { - log.error('ignoring unexpected delete entry on initial load'); + this.log.error('ignoring unexpected delete entry on initial load'); } } if (delta.topics.length === 0) { - this.remoteCompleted = true; + this.remoteComplete = true; } const rev = this.storeView.revision ? this.storeView.revision : delta.revision; const mark = delta.topics.length ? delta.marker : null; @@ -170,24 +171,25 @@ export class FocusModule implements Focus { const { id, revision, data } = entity; if (data) { const { detailRevision, topicDetail } = data; - if (!this.cacheView || this.cacheView.position > detail.created || (this.cacheView.position === detail.created && this.cacheView.topicId >= topicId)) { + const detail = topicDetail ? topicDetail : await this.getRemoteChannelTopicDetail(id); + if (!this.cacheView || this.cacheView.position > detail.created || (this.cacheView.position === detail.created && this.cacheView.topicId >= id)) { const entry = await this.getTopicEntry(id); if (detailRevision > entry.item.detail.revision) { - const detail = topicDetail ? topicDetail : await this.getRemoteChannelTopicDetail(id); entry.item.detail = this.getTopicDetail(detail, detailRevision); entry.item.unsealedDetail = null; entry.item.position = detail.created; await this.unsealTopicDetail(entry.item); entry.topic = this.setTopic(id, entry.item); - await this.setLocalChannelTopicDetail(id, detail, entry.item.unsealedDetail, detail.created); + await this.setLocalChannelTopicDetail(id, entry.item.detail, entry.item.unsealedDetail, detail.created); } } else { - const item = { detail, position: detail.created, unsealedDetail: null }; - await this.store.addLocalChannelTopic(id, item); + const itemDetail= this.getTopicDetail(detail, detailRevision); + const item = { detail: itemDetail, position: detail.created, unsealedDetail: null }; + await this.addLocalChannelTopic(id, item); } } else { this.topicEntries.delete(id); - await this.store.removeLocalChannelTopic(id); + await this.removeLocalChannelTopic(id); } } this.storeView = { revision: nextRev, marker: delta.marker }; @@ -213,7 +215,7 @@ export class FocusModule implements Focus { try { const { item } = entry; if (await this.unsealTopicDetail(item)) { - await this.setLocalChannelTopicUnsealedDetail(guid, topicId, item.unsealedDetail); + await this.setLocalChannelTopicUnsealedDetail(topicId, item.unsealedDetail); entry.topic = this.setTopic(topicId, item); } } catch (err) { @@ -234,7 +236,7 @@ export class FocusModule implements Focus { if (!connection) { throw new Error('disconnected from channel'); } - const { node, secure, token } = this.connection; + const { node, secure, token } = connection; const params = `${cardId ? 'contact' : 'agent'}=${token}` const url = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}/assets/${blockId}?${params}` @@ -261,7 +263,7 @@ export class FocusModule implements Focus { if (!connection) { throw new Error('disconnected from channel'); } - const { node, secure, token } = this.connection; + const { node, secure, token } = connection; const params = `${cardId ? 'contact' : 'agent'}=${token}` const url = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}/blocks?${params}` @@ -269,7 +271,7 @@ export class FocusModule implements Focus { const xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.setRequestHeader('Content-Type', 'text/plain'); - xhr.progress = progress; + xhr.onprogress = (ev: ProgressEvent)=>{ progress(0) }; xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { try { @@ -293,7 +295,7 @@ export class FocusModule implements Focus { if (!connection) { throw new Error('disconnected from channel'); } - const { node, secure, token } = this.connection; + const { node, secure, token } = connection; const params = `${cardId ? 'contact' : 'agent'}=${token}&body=multipart` const url = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}/blocks?${params}` const formData = new FormData(); @@ -302,7 +304,7 @@ export class FocusModule implements Focus { return new Promise(function (resolve, reject) { const xhr = new XMLHttpRequest(); xhr.open('POST', url, true); - xhr.progress = progress; + xhr.onprogress = (ev: ProgressEvent)=>{ progress(0) }; xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { try { @@ -326,7 +328,7 @@ export class FocusModule implements Focus { if (!connection) { throw new Error('disconnected from channel'); } - const { node, secure, token } = this.connection; + const { node, secure, token } = connection; const params = `${cardId ? 'contact' : 'agent'}=${token}&transforms=${encodeURIComponent(JSON.stringify(transforms))}` const url = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}/assets?${params}` const formData = new FormData(); @@ -335,7 +337,7 @@ export class FocusModule implements Focus { return new Promise(function (resolve, reject) { const xhr = new XMLHttpRequest(); xhr.open('POST', url, true); - xhr.progress = progress; + xhr.onprogress = (ev: ProgressEvent)=>{ progress(0) }; xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { try { @@ -365,9 +367,12 @@ export class FocusModule implements Focus { if (files.length == 0) { const data = subject([]); if (sealed) { + if (!crypto || !channelKey) { + throw new Error('duplicate throw for build warning'); + } const subjectString = JSON.stringify(data); const { ivHex } = await crypto.aesIv(); - const { encryptedDataB64 } = await crypto.aesEncrypt(decryptedString, ivHex, channelKey); + const { encryptedDataB64 } = await crypto.aesEncrypt(subjectString, ivHex, channelKey); const dataEncrypted = { messageEncrypted: encryptedDataB64, messageIv: ivHex }; return await this.addRemoteChannelTopic(type, dataEncrypted, true); } else { @@ -389,14 +394,21 @@ export class FocusModule implements Focus { appAsset.push({appId: transform.appId, assetId: assetItem.assetId}); assetItems.push(assetItem); } else if (transform.type === TransformType.Copy) { - const mediaFile = await this.media.read(asset.source); + const { media } = this; + if (!media) { + throw new Error('media file processing support not enabled'); + } + if (!crypto || !channelKey) { + throw new Error('duplicate throw for build warning'); + } + const mediaFile = await media.read(asset.source); const split = [] as { partId: string, blockIv: string }[]; for (let i = 0; i * ENCRYPT_BLOCK_SIZE < mediaFile.size; i++) { const length = mediaFile.size - (i * ENCRYPT_BLOCK_SIZE) > ENCRYPT_BLOCK_SIZE ? ENCRYPT_BLOCK_SIZE : mediaFile.size - (i * ENCRYPT_BLOCK_SIZE); const base64Data = await mediaFile.getData(i * ENCRYPT_BLOCK_SIZE, length); const { ivHex } = await crypto.aesIv(); const { encryptedDataB64 } = await crypto.aesEncrypt(base64Data, ivHex, channelKey); - const partId = await this.uploadBlock(encryptedDataB64, topicId, (percent: number) => { console.log(`percent: ${percent}`) }); + const partId = await this.uploadBlock(encryptedDataB64, topicId, progress); split.push({ partId, blockIv: ivHex }); } const assetItem = { @@ -405,7 +417,7 @@ export class FocusModule implements Focus { hosting: HostingMode.Split, split, } - appAsset.push({appId: transform.appId, assetId: assetItems.assetId}); + appAsset.push({appId: transform.appId, assetId: assetItem.assetId}); assetItems.push(assetItem); } else { throw new Error('transform not supported') @@ -417,55 +429,53 @@ export class FocusModule implements Focus { const transforms = []; const transformMap = new Map(); for (let transform of asset.transforms) { - if (transform.type === TransformType.Thumb && asset.mimeType === 'image') { + if (transform.type === TransformType.Thumb && asset.type === AssetType.Image) { transforms.push('ithumb;photo'); transformMap.set('ithumb;photo', transform.appId); - } else if (transform.type === TransformType.HighQualirty && asset.mimeType === 'image') { + } else if (transform.type === TransformType.HighQuality && asset.type === AssetType.Image) { transforms.push('ilg;photo'); transformMap.set('ilg;photo', transform.appId); - } else if (transform.type === TransformType.Copy && asset.mimeType === 'image') { + } else if (transform.type === TransformType.Copy && asset.type === AssetType.Image) { transforms.push('icopy;photo'); transformMap.set('icopy;photo', transform.appId); - } else if (transform.type === TransformType.Thumb && asset.mimeType === 'video') { + } else if (transform.type === TransformType.Thumb && asset.type === AssetType.Video) { transforms.push('vthumb;video'); transformMap.set('vthumb;video', transform.appId); - } else if (transform.type === TransformType.Copy && asset.mimeType === 'video') { + } else if (transform.type === TransformType.Copy && asset.type === AssetType.Video) { transforms.push('vcopy;video'); transformMap.set('vcopy;video', transform.appId); - } else if (transform.type === TransformType.HighQuality && asset.mimeType === 'video') { + } else if (transform.type === TransformType.HighQuality && asset.type === AssetType.Video) { transforms.push('vhd;video'); transformMap.set('vhd;video', transform.appId); - } else if (transform.type === TransformType.LowQuality && asset.mimeType === 'video') { + } else if (transform.type === TransformType.LowQuality && asset.type === AssetType.Video) { transforms.push('vlq;video'); transformMap.set('vlq;video', transform.appId); - } else if (transform.type === TransformType.Copy && asset.mimeType === 'audio') { + } else if (transform.type === TransformType.Copy && asset.type === AssetType.Audio) { transforms.push('acopy;audio'); transformMap.set('acopy;audio', transform.appId); - } else if (transform.type === TransformType.Copy && asset.mimeType === 'binary') { - const assetId = await this.mirrorFile(asset.source, topicId, (percent: number)=>{console.log(`progress: ${percent}`)}); + } else if (transform.type === TransformType.Copy && asset.type === AssetType.Binary) { + const assetId = await this.mirrorFile(asset.source, topicId, progress); const assetItem = { assetId: `${assetItems.length}`, - encrytped: false, hosting: HostingMode.Basic, basic: assetId, } - appAsset.push({appId: transform.appId, assetId: assetitem.assetId}); + appAsset.push({appId: transform.appId, assetId: assetItem.assetId}); assetItems.push(assetItem); } else { throw new Error('transform not supported'); } } if (transforms.length > 0) { - const transformAssets = await this.transformFile(asset.source, topicId, transforms, (percent: number)=>{console.log(`progress: ${percent}`)}); + const transformAssets = await this.transformFile(asset.source, topicId, transforms, progress); for (let transformAsset of transformAssets) { const assetItem = { assetId: `${assetItems.length}`, - encrypted: false, hosting: HostingMode.Basic, basic: transformAsset.assetId, } if (transformMap.has(transformAsset.transform)) { - const appId = transformMap.get(transformAsset.transform) + const appId = transformMap.get(transformAsset.transform) || '' //or to make build happy appAsset.push({appId, assetId: transformAsset.assetId }); assetItems.push(assetItem); } @@ -482,17 +492,17 @@ export class FocusModule implements Focus { throw new Error('invalid assetId in subject'); } if (item.hosting === HostingMode.Inline) { - return item.inline; - } - if (item.hosting === HostingMode.Split) { + return item.inline; + } else if (item.hosting === HostingMode.Split) { return item.split; - } - if (item.hosting === HostingMode.Basic) { + } else if (item.hosting === HostingMode.Basic) { return item.basic; + } else { + throw new Error('unknown hosting mode'); } } - const filtered = !assets ? [] : assets.filter(asset => { + const filtered = !assets ? [] : assets.filter((asset: any)=>{ if (sealed && asset.encrypted) { return true; } else if (!sealed && !asset.encrypted) { @@ -501,8 +511,8 @@ export class FocusModule implements Focus { return false; } }); - const mapped = filtered.map(asset => { - if (sealed) { + const mapped = filtered.map((asset: any) => { + if (asset.encrypted) { const { type, thumb, parts } = asset.encrypted; return { encrypted: { type, thumb: getAsset(thumb), parts: getAsset(parts) } }; } else if (asset.image) { @@ -512,7 +522,7 @@ export class FocusModule implements Focus { const { thumb, lq, hd } = asset.video; return { video: { thumb: getAsset(thumb), lq: getAsset(lq), hd: getAsset(hd) } }; } else if (asset.audio) { - const { label, fulle } = asset.audio; + const { label, full } = asset.audio; return { audio: { label, full: getAsset(full) } }; } else if (asset.binary) { const { label, extension, data } = asset.binary; @@ -521,6 +531,9 @@ export class FocusModule implements Focus { }); const updated = { text, textColor, textSize, assets: mapped }; if (sealed) { + if (!crypto || !channelKey) { + throw new Error('duplicate throw to make build happy') + } const subjectString = JSON.stringify({ message: updated }); const { ivHex } = await crypto.aesIv(); const { encryptedDataB64 } = await crypto.aesEncrypt(subjectString, ivHex, channelKey); @@ -536,48 +549,54 @@ export class FocusModule implements Focus { public async setTopicSubject(topicId: string, type: string, subject: (assets: {assetId: string, appId: string}[])=>any, files: AssetSource[], progress: (percent: number)=>boolean) { + const entry = this.topicEntries.get(topicId); + if (!entry) { + throw new Error('topic not found'); + } + const { item } = entry; + const { sealed } = item.detail; const { sealEnabled, channelKey, crypto } = this; if (sealed && (!sealEnabled || !channelKey || !crypto)) { throw new Error('encryption not set'); } - - const item = this.topicEntries(topicId); - if (!item) { - throw new Error('topic not found'); - } - const { assets: assetItems } = getTopicData(item); + const { assets: assetItems } = this.getTopicData(item); const appAsset = [] as {assetId: string, appId: string}[]; if (sealed) { - for (const asset of assets) { + for (const asset of files) { for (const transform of asset.transforms) { if (transform.type === TransformType.Thumb && transform.thumb) { const assetItem = { assetId: `${assetItems.length}`, - encrytped: true, hosting: HostingMode.Inline, inline: await transform.thumb(), } appAsset.push({appId: transform.appId, assetId: assetItem.assetId}); assetItems.push(assetItem); } else if (transform.type === TransformType.Copy) { - const mediaFile = await this.file.read(asset.source); + const { media } = this; + if (!media) { + throw new Error('media file processing support not enabled'); + } + if (!crypto || !channelKey) { + throw new Error('duplicate throw for build warning'); + } + const mediaFile = await media.read(asset.source); const split = [] as { partId: string, blockIv: string }[]; for (let i = 0; i * ENCRYPT_BLOCK_SIZE < mediaFile.size; i++) { const length = mediaFile.size - (i * ENCRYPT_BLOCK_SIZE) > ENCRYPT_BLOCK_SIZE ? ENCRYPT_BLOCK_SIZE : mediaFile.size - (i * ENCRYPT_BLOCK_SIZE); const base64Data = await mediaFile.getData(i * ENCRYPT_BLOCK_SIZE, length); const { ivHex } = await crypto.aesIv(); const { encryptedDataB64 } = await crypto.aesEncrypt(base64Data, ivHex, channelKey); - const partId = await this.uploadBlock(encryptedDataB64, topicId, (percent: number) => { console.log(`percent: ${percent}`) }); + const partId = await this.uploadBlock(encryptedDataB64, topicId, progress); split.push({ partId, blockIv: ivHex }); } const assetItem = { assetId: `${assetItems.length}`, - encrypted: true, hosting: HostingMode.Split, split, } - appAsset.push({appId: transform.appId, assetId: assetItems.assetId}); + appAsset.push({appId: transform.appId, assetId: assetItem.assetId}); assetItems.push(assetItem); } else { throw new Error('transform not supported') @@ -585,54 +604,54 @@ export class FocusModule implements Focus { } } } else { - for (const asset of assets) { + for (const asset of files) { const transforms = []; const transformMap = new Map(); for (let transform of asset.transforms) { - if (transform.type === TransformType.Thumb && asset.mimeType === 'image') { + if (transform.type === TransformType.Thumb && asset.type === AssetType.Image) { transforms.push('ithumb;photo'); transformMap.set('ithumb;photo', transform.appId); - } else if (transform.type === TransformType.Copy && asset.mimeType === 'image') { + } else if (transform.type === TransformType.Copy && asset.type === AssetType.Image) { transforms.push('icopy;photo'); transformMap.set('icopy;photo', transform.appId); - } else if (transform.type === TransformType.Thumb && asset.mimeType === 'video') { + } else if (transform.type === TransformType.Thumb && asset.type === AssetType.Video) { transforms.push('vthumb;video'); transformMap.set('vthumb;video', transform.appId); - } else if (transform.type === TransformType.Copy && asset.mimeType === 'video') { + } else if (transform.type === TransformType.Copy && asset.type === AssetType.Video) { transforms.push('vcopy;video'); transformMap.set('vcopy;video', transform.appId); - } else if (transform.type === TransformType.LowQuality && asset.mimeType === 'video') { + } else if (transform.type === TransformType.LowQuality && asset.type === AssetType.Video) { transforms.push('vlq;video'); transformMap.set('vlq;video', transform.appId); - } else if (transform.type === TransformType.Copy && asset.mimeType === 'audio') { + } else if (transform.type === TransformType.Copy && asset.type === AssetType.Audio) { transforms.push('acopy;audio'); transformMap.set('acopy;audio', transform.appId); - } else if (transform.type === TransformType.Copy && asset.mimeType === 'binary') { - const assetId = await this.mirrorFile(asset.source, topicId, (percent: number)=>{console.log(`progress: ${percent}`)}); + } else if (transform.type === TransformType.Copy && asset.type === AssetType.Binary) { + const assetId = await this.mirrorFile(asset.source, topicId, progress); const assetItem = { assetId: `${assetItems.length}`, - encrytped: false, hosting: HostingMode.Basic, basic: assetId, } - appAsset.push({appId: transform.appId, assetId: assetitem.assetId}); + appAsset.push({appId: transform.appId, assetId: assetItem.assetId}); assetItems.push(assetItem); } else { throw new Error('transform not supported'); } } if (transforms.length > 0) { - const transformAssets = await this.transformFile(asset.source, topicId, transforms, (percent: number)=>{console.log(`progress: ${percent}`)}); - for (transformAsset of transformAssets) { + const transformAssets = await this.transformFile(asset.source, topicId, transforms, progress); + for (let transformAsset of transformAssets) { const assetItem = { - assetId: `${assetItem.size}`, - encrypted: false, + assetId: `${assetItems.length}`, hosting: HostingMode.Basic, basic: transformAsset.assetId, } - const appId = transformMap.get(assetItem.assetId) - appAsset.push({appId, assetId: assetItem.assetId }); - assetItems.push(assetItem); + if (transformMap.get(assetItem.assetId)) { + const appId = transformMap.get(assetItem.assetId) || '' //or to make build happy + appAsset.push({appId, assetId: assetItem.assetId }); + assetItems.push(assetItem); + } } } } @@ -641,18 +660,21 @@ export class FocusModule implements Focus { const getAsset = (assetId: string) => { const index = parseInt(assetId); const item = assetItems[index]; + if (!item) { + throw new Error('invalid assetId in subject'); + } if (item.hosting === HostingMode.Inline) { return item.inline; - } - if (item.hosting === HostingMode.Split) { + } if (item.hosting === HostingMode.Split) { return item.split; - } - if (item.hosting === HostingMode.Basic) { + } if (item.hosting === HostingMode.Basic) { return item.basic; + } else { + throw new Error('unknown hosting mode'); } } - const filtered = !assets ? [] : assets.filter(asset => { + const filtered = !assets ? [] : assets.filter((asset: any) => { if (sealed && asset.encrypted) { return true; } else if (!sealed && !asset.encrypted) { @@ -661,7 +683,7 @@ export class FocusModule implements Focus { return false; } }); - const mapped = filtered.map(asset => { + const mapped = filtered.map((asset: any) => { if (sealed) { const { type, thumb, parts } = asset.encrypted; return { encrypted: { type, thumb: getAsset(thumb), parts: getAsset(parts) } }; @@ -672,7 +694,7 @@ export class FocusModule implements Focus { const { thumb, lq, hd } = asset.video; return { video: { thumb: getAsset(thumb), lq: getAsset(lq), hd: getAsset(hd) } }; } else if (asset.audio) { - const { label, fulle } = asset.audio; + const { label, full } = asset.audio; return { audio: { label, full: getAsset(full) } }; } else if (asset.binary) { const { label, extension, data } = asset.binary; @@ -682,6 +704,9 @@ export class FocusModule implements Focus { const updated = { text, textColor, textSize, assets: mapped }; if (sealed) { + if (!crypto || !channelKey) { + throw new Error('duplicate throw to make build happy') + } const subjectString = JSON.stringify({ message: updated }); const { ivHex } = await crypto.aesIv(); const { encryptedDataB64 } = await crypto.aesEncrypt(subjectString, ivHex, channelKey); @@ -693,39 +718,34 @@ export class FocusModule implements Focus { } } - public async removeTopic(topicId: string) {} - - public async setUnreadChannel() {} - - public async clearUnreadChannel() {} + public async removeTopic(topicId: string) { + await this.removeRemoteChannelTopic(topicId); + } public async getTopicAssetUrl(topicId: string, assetId: string, progress: (percent: number) => boolean): Promise { const entry = this.topicEntries.get(topicId); if (!entry) { throw new Error('topic entry not found'); } - const { assets } = this.getTopicData(entry); - const asset = assets.find(item => item.assetId === assetid); + const { assets } = this.getTopicData(entry.item); + const asset = assets.find(item => item.assetId === assetId); if (!asset) { throw new Error('asset entry not found'); } - if (asset.hosting === HostingMode.Inline) { + if (asset.hosting === HostingMode.Inline && asset.inline) { return `data://${asset.inline}`; - } else if (asset.hosting === HostingMode.Basic) { - return this.getRemoteChannelTopicAsseturl(topicId, assset.basic); - } else if (asset.hosting === HostingMode.Split) { - const write = this.media.write(); - this.closeMedia.push(write.close); + } else if (asset.hosting === HostingMode.Basic && asset.basic) { + return this.getRemoteChannelTopicAssetUrl(topicId, asset.basic); + } else if (asset.hosting === HostingMode.Split && asset.split) { const { sealEnabled, channelKey, crypto, media } = this; if (!sealEnabled || !channelKey || !crypto || !media) { - throw new Error('media decryption not set'); - } - if (!asset.split || !asset.split.length) { - throw new Error('invalid split media'); + throw new Error('media file decryption not set'); } + const write = await media.write(); + this.closeMedia.push(write.close); for (let i = 0; i < asset.split.length; i++) { - const block = await this.downloadBlock(topiccId, asset.split[i].blockId); - const { data } = await this.crypto.aesDecrypt(block, asset.split[i].partIv, channelKey); + const block = await this.downloadBlock(topicId, asset.split[i].partId); + const { data } = await crypto.aesDecrypt(block, asset.split[i].blockIv, channelKey); await write.setData(data); } return await write.getUrl(); @@ -734,6 +754,15 @@ export class FocusModule implements Focus { } } + + + + + + public async setUnreadChannel() {} + + public async clearUnreadChannel() {} + public async flagTopic(topicId: string) {} public async setBlockTopic(topicId: string) {} @@ -741,6 +770,11 @@ export class FocusModule implements Focus { public async clearBlockTopic(topicId: string) {} + + + + + private async unsealTopicDetail(item: TopicItem): Promise { if (item.detail.sealed && !item.unsealedDetail && this.sealEnabled && this.channelKey && this.crypto) { try { @@ -810,7 +844,7 @@ export class FocusModule implements Focus { await this.sync(); } - public async setChannelKey(cardId: string | null, channelId: string, channelKey: string) { + public async setChannelKey(cardId: string | null, channelId: string, channelKey: string | null) { if (cardId === this.cardId && channelId === this.channelId) { this.channelKey = channelKey; this.unsealAll = true; @@ -830,7 +864,7 @@ export class FocusModule implements Focus { private emitStatus() { const status = this.connection ? 'connected' : 'disconnected' - ev(status); + this.emitter.emit('status', status); } private getTopicData(item: TopicItem): { data: any, assets: AssetItem[] } { @@ -843,15 +877,12 @@ export class FocusModule implements Focus { const { text, textColor, textSize, assets } = topicDetail; let index: number = 0; const assetItems = new Set(); - const dataAssets = !assets ? [] : assets.map(({ encrypted, image, audio, video, binary }) => { + const dataAssets = !assets ? [] : assets.map(({ encrypted, image, audio, video, binary }: BasicAsset) => { if (encrypted) { const { type, thumb, label, extension, parts } = encrypted; if (thumb) { const asset = { assetId: `${revision}.${index}`, - mimeType: 'image/png', - extension: 'png', - encrypted: false, hosting: HostingMode.Inline, inline: thumb, } @@ -860,9 +891,6 @@ export class FocusModule implements Focus { } const asset = { assetId: `${revision}.${index}`, - mimeType: type, - extension: extension, - encrypted: true, hosting: HostingMode.Split, split: parts, } @@ -870,18 +898,15 @@ export class FocusModule implements Focus { index += 1; if (thumb) { - return { type, thumb: `${revision}.${index-2}`, data: `${revision}.${index-1}` } + return { encrypted: { type, thumb: `${revision}.${index-2}`, data: `${revision}.${index-1}`, label, extension } } } else { - return { type, data: `${revision}.${index-1}` } + return { encrypted: { type, data: `${revision}.${index-1}`, label, extension } } } } else { - const { thumb, label, full, lq, hd, extension, data } = binary || image || audio || video; + const { thumb, label, full, lq, hd, extension, data } = (binary || image || audio || video) as any; if (thumb) { const asset = { assetId: `${revision}.${index}`, - mimeType: 'image/png', - extension: 'png', - encrypted: false, hosting: HostingMode.Basic, basic: thumb, } @@ -890,20 +915,20 @@ export class FocusModule implements Focus { } const asset = { assetId: `${revision}.${index}`, - mimeType: image ? 'image' : audio ? 'audio' : video ? 'video' : 'binary', - extension: extension, - encrypted: false, hosting: HostingMode.Basic, basic: full || hd || lq, } assetItems.add(asset); index += 1; + const type = image ? 'image' : audio ? 'audio' : video ? 'video' : 'binary'; + const assetEntry = {} as any; if (thumb) { - return { type: asset.mimeType, thumb: `${revision}.${index-2}`, data: `${revision}.${index-1}` } + assetEntry[type] = { thumb: `${revision}.${index-2}`, data: `${revision}.${index-1}`, label, extension } } else { - return { type: asset.mimeType, data: `${revision}.${index-1}` } + assetEntry[type] = { data: `${revision}.${index-1}`, label, extension } } + return assetEntry; } }) return { data: { text, textColor, textSize, assets: dataAssets }, assets: Array.from(assetItems.values()) }; @@ -923,13 +948,13 @@ export class FocusModule implements Focus { status: item.detail.status, transform: item.detail.transform, assets: assets.map(asset => { - const { assetId, mimeType, hosting, extension } = asset; - return { assetId, mimeType, hosting, extension }; + const { assetId, hosting } = asset; + return { assetId, hosting }; }), } } - private getTopicDetail(entity: TopicDetailEntity, revision: number) { + private getTopicDetail(entity: TopicDetailEntity, revision: number): TopicDetail { const { guid, dataType, data, created, updated, status, transform } = entity; return { revision, @@ -967,8 +992,8 @@ export class FocusModule implements Focus { } } - private async setChannelTopicRevision(sync: { revision: number, marker: number}) { - const { guid, cardId, channelId, revision } = this; + private async setChannelTopicRevision(sync: { revision: number | null, marker: number | null}) { + const { guid, cardId, channelId } = this; if (cardId) { await this.store.setContactCardChannelTopicRevision(guid, cardId, channelId, sync); } @@ -977,7 +1002,7 @@ export class FocusModule implements Focus { } } - private async getLocalChannelTopics(offset: {topicId: string, position: number}) { + private async getLocalChannelTopics(offset: {topicId: string, position: number} | null) { const { guid, cardId, channelId } = this; if (cardId) { return await this.store.getContactCardChannelTopics(guid, cardId, channelId, BATCH_COUNT, offset); @@ -1009,7 +1034,7 @@ export class FocusModule implements Focus { if (cardId) { await this.store.setContactCardChannelTopicDetail(guid, cardId, channelId, topicId, detail, unsealedDetail, position); } else { - await this.store.setContentChannelTopicDetail(guid, channelId, topicId, unsealedDetail); + await this.store.setContentChannelTopicDetail(guid, channelId, topicId, detail, unsealedDetail, position); } } @@ -1018,7 +1043,7 @@ export class FocusModule implements Focus { if (cardId) { await this.store.setContactCardChannelTopicUnsealedDetail(guid, cardId, channelId, topicId, unsealedDetail); } else { - await this.store.setContentChannelTopicDetail(guid, channelId, topicId, unsealedDetail); + await this.store.setContentChannelTopicUnsealedDetail(guid, channelId, topicId, unsealedDetail); } } @@ -1027,7 +1052,7 @@ export class FocusModule implements Focus { if (!connection) { throw new Error('disconnected channel'); } - const { node, secure, token } = this.connection; + const { node, secure, token } = connection; return `http${secure ? 's' : ''}//${node}/content/channels/${channelId}/topics/${topicId}/assets/${assetId}?${cardId ? 'contact' : 'agent'}=${token}` } @@ -1036,7 +1061,7 @@ export class FocusModule implements Focus { if (!connection) { throw new Error('disconnected channel'); } - const { node, secure, token } = this.connection + const { node, secure, token } = connection if (cardId) { return await getContactChannelTopics(node, secure, token, channelId, revision, (begin || !revision) ? BATCH_COUNT : null, begin, end); } else { @@ -1049,7 +1074,7 @@ export class FocusModule implements Focus { if (!connection) { throw new Error('disconnected channel'); } - const { node, secure, token } = this.connection + const { node, secure, token } = connection if (cardId) { return await getContactChannelTopicDetail(node, secure, token, channelId, topicId); } else { @@ -1062,7 +1087,7 @@ export class FocusModule implements Focus { if (!connection) { throw new Error('disconnected channel'); } - const { node, secure, token } = this.connection; + const { node, secure, token } = connection; if (cardId) { return await addContactChannelTopic(node, secure, token, channelId, dataType, data, confirm); } else { @@ -1075,7 +1100,7 @@ export class FocusModule implements Focus { if (!connection) { throw new Error('disconnected from channel'); } - const { node, secure, token } = this.connection; + const { node, secure, token } = connection; if (cardId) { return await setContactChannelTopicSubject(node, secure, token, channelId, topicId, dataType, data); } else { @@ -1083,14 +1108,14 @@ export class FocusModule implements Focus { } } - private async removeRemoveChannelTopic(topicId: string) { + private async removeRemoteChannelTopic(topicId: string) { const { cardId, channelId, connection } = this; if (!connection) { throw new Error('disconnected from channel'); } - const { node, secure, token } = this.connection; + const { node, secure, token } = connection; if (cardId) { - return await removeContactChannelTopicSubject(node, secure, token, channelId, topicId); + return await removeContactChannelTopic(node, secure, token, channelId, topicId); } else { return await removeChannelTopic(node, secure, token, channelId, topicId); } diff --git a/app/sdk/src/items.ts b/app/sdk/src/items.ts index 66f9a891..89185195 100644 --- a/app/sdk/src/items.ts +++ b/app/sdk/src/items.ts @@ -157,7 +157,6 @@ export type TopicDetail = { sealed: boolean; dataType: string; data: any; - assets: AssetItem[]; created: number; updated: number; status: string; @@ -172,11 +171,8 @@ export type TopicItem = { export type AssetItem = { assetId: string; - mimeType: string; - extension: string; - encrypted: boolean; hosting: HostingMode; - split?: { blockId: string, blockIv: string }[]; + split?: { partId: string, blockIv: string }[]; basic?: string; inline?: string; }; diff --git a/app/sdk/src/net/getChannelTopics.ts b/app/sdk/src/net/getChannelTopics.ts index c08f45ad..c1e6e742 100644 --- a/app/sdk/src/net/getChannelTopics.ts +++ b/app/sdk/src/net/getChannelTopics.ts @@ -1,14 +1,14 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; import { TopicEntity } from '../entities'; -export async function getChannelTopics(node: string, secure: boolean, token: string, channelId: stirng, revision: number | null, count: number | null, begin: number | null, end: number | null): Promise<{marker: number, revision: number, topics: TopicEntity[]}> { +export async function getChannelTopics(node: string, secure: boolean, token: string, channelId: string, revision: number | null, count: number | null, begin: number | null, end: number | null): Promise<{marker: number, revision: number, topics: TopicEntity[]}> { const params = (revision ? `&revision=${revision}` : '') + (count ? `&count=${count}` : '') + (begin ? `&begin=${begin}` : '') + (end ? `&end=${end}` : ''); const endpoint = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics?agent=${token}${params}`; const topics = await fetchWithTimeout(endpoint, { method: 'GET' }); checkResponse(topics.status); return { - marker: parseInt(topics.headers.get('topic-marker')), - revision: parseInt(topics.headers.get('topic-revision')), + marker: parseInt(topics.headers.get('topic-marker') || '0'), + revision: parseInt(topics.headers.get('topic-revision') || '0'), topics: await topics.json(), } } diff --git a/app/sdk/src/net/getContactChannelTopics.ts b/app/sdk/src/net/getContactChannelTopics.ts index 7f4f509d..c1120a77 100644 --- a/app/sdk/src/net/getContactChannelTopics.ts +++ b/app/sdk/src/net/getContactChannelTopics.ts @@ -7,8 +7,8 @@ export async function getContactChannelTopics(node: string, secure: boolean, gui const topics = await fetchWithTimeout(endpoint, { method: 'GET' }); checkResponse(topics.status); return { - marker: parseInt(topics.headers.get('topic-marker')), - revision: parseInt(topics.headers.get('topic-revision')), + marker: parseInt(topics.headers.get('topic-marker') || '0'), + revision: parseInt(topics.headers.get('topic-revision') || '0'), topics: await topics.json(), } } diff --git a/app/sdk/src/net/removeChannelTopic.ts b/app/sdk/src/net/removeChannelTopic.ts index a1b58aa3..8e9be736 100644 --- a/app/sdk/src/net/removeChannelTopic.ts +++ b/app/sdk/src/net/removeChannelTopic.ts @@ -1,7 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; export async function removeChannelTopic(node: string, secure: boolean, token: string, channelId: string, topicId: string): Promise { - const endpoint = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/${topics}/${topicId}?agent=${token}`; + const endpoint = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}?agent=${token}`; const { status } = await fetchWithTimeout(endpoint, { method: 'DELETE' }); checkResponse(status); } diff --git a/app/sdk/src/net/removeContactChannelTopic.ts b/app/sdk/src/net/removeContactChannelTopic.ts index 2b225855..bb05b7e3 100644 --- a/app/sdk/src/net/removeContactChannelTopic.ts +++ b/app/sdk/src/net/removeContactChannelTopic.ts @@ -1,6 +1,6 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -export async function removeContactChannelTopic(node: string, secure: boolean, guidToken: string, channelId: string) { +export async function removeContactChannelTopic(node: string, secure: boolean, guidToken: string, channelId: string, topicId: string) { const endpoint = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}?contact=${guidToken}`; const response = await fetchWithTimeout(endpoint, { method: 'DELETE' }); checkResponse(response.status); diff --git a/app/sdk/src/store.ts b/app/sdk/src/store.ts index d6a5ce7d..915ebe13 100644 --- a/app/sdk/src/store.ts +++ b/app/sdk/src/store.ts @@ -75,7 +75,7 @@ export interface Store { setContentChannelUnsealedSummary(guid: string, channelId: string, data: string | null): Promise; getContentChannelTopicRevision(guid: string, channelId: string): Promise<{ revision: number | null, marker: number | null }>; - setContentChannelTopicRevision(guid: string, channelId: string, sync: { revision: number, marker: number }): Promise; + setContentChannelTopicRevision(guid: string, channelId: string, sync: { revision: number | null, marker: number | null }): Promise; getContentChannelTopics(guid: string, channelId: string, count: number, position: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]>; addContentChannelTopic(guid: string, channelId: string, topicId: string, item: TopicItem): Promise; removeContentChannelTopic(guid: string, channelId: string, topicId: string): Promise; @@ -83,7 +83,7 @@ export interface Store { setContentChannelTopicUnsealedDetail(guid: string, channelId: string, topicId: string, unsealedDetail: any): Promise; getContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string): Promise<{ revision: number | null, marker: number | null }>; - setContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string, sync: { revision: number, marker: number }): Promise; + setContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string, sync: { revision: number | null, marker: number | null }): Promise; getContactCardChannelTopics(guid: string, cardId: string, channelId: string, count: number, position: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]>; addContactCardChannelTopic(guid: string, cardId: string, channelId: string, topicId: string, item: TopicItem): Promise; removeContactCardChannelTopic(guid: string, cardId: string, channelId: string, topicId: string): Promise; @@ -499,7 +499,7 @@ export class OfflineStore implements Store { public async getContentChannelTopicRevision(guid: string, channelId: string): Promise<{ revision: number | null, marker: number | null }> { return await this.getTableValue(guid, 'channel', 'sync', [{field: 'channel_id', value: channelId}], { revision: null, marker: null }); } - public async setContentChannelTopicRevision(guid: string, channelId: string, sync: { revision: number, marker: number }): Promise { + public async setContentChannelTopicRevision(guid: string, channelId: string, sync: { revision: number | null, marker: number | null }): Promise { await this.setTableValue(guid, 'channel', [{field: 'sync', value: JSON.stringify(sync)}], [{field: 'channel_id', value: channelId}]); } public async getContentChannelTopics(guid: string, channelId: string, count: number, offset: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]> { @@ -536,7 +536,7 @@ export class OfflineStore implements Store { public async getContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string): Promise<{ revision: number | null, marker: number | null }> { return await this.getTableValue(guid, 'card_channel', 'sync', [{field: 'card_id', value: cardId},{field: 'channel_id', value: channelId}], { revision: null, marker: null }); } - public async setContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string, sync: { revision: number, marker: number }): Promise { + public async setContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string, sync: { revision: number | null, marker: number | null }): Promise { return await this.setTableValue(guid, 'card_channel', [{field: 'sync', value: JSON.stringify(sync)}], [{field: 'card_id', value: cardId}, {field: 'channel_id', value: channelId}]); } public async getContactCardChannelTopics(guid: string, cardId: string, channelId: string, count: number, offset: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]> { @@ -752,7 +752,7 @@ export class OnlineStore implements Store { public async getContentChannelTopicRevision(guid: string, channelId: string): Promise<{ revision: number | null, marker: number | null }> { return { revision: null, marker: null }; } - public async setContentChannelTopicRevision(guid: string, channelId: string, sync: { revision: number, marker: number }): Promise { + public async setContentChannelTopicRevision(guid: string, channelId: string, sync: { revision: number | null, marker: number | null }): Promise { } public async getContentChannelTopics(guid: string, channelId: string, count: number, position: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]> { return []; @@ -765,7 +765,7 @@ export class OnlineStore implements Store { public async getContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string): Promise<{ revision: number | null, marker: number | null }> { return { revision: null, marker: null }; } - public async setContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string, sync: { revision: number, marker: number }): Promise { + public async setContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string, sync: { revision: number | null, marker: number | null }): Promise { } public async getContactCardChannelTopics(guid: string, cardId: string, channelId: string, count: number, position: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]> { return []; @@ -912,7 +912,7 @@ export class NoStore implements Store { public async getContentChannelTopicRevision(guid: string, channelId: string): Promise<{ revision: number | null, marker: number | null }> { return { revision: null, marker: null }; } - public async setContentChannelTopicRevision(guid: string, channelId: string, sync: { revision: number, marker: number }): Promise { + public async setContentChannelTopicRevision(guid: string, channelId: string, sync: { revision: number | null, marker: number | null }): Promise { } public async getContentChannelTopics(guid: string, channelId: string, count: number, position: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]> { return []; @@ -925,7 +925,7 @@ export class NoStore implements Store { public async getContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string): Promise<{ revision: number | null, marker: number | null }> { return { revision: null, marker: null }; } - public async setContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string, sync: { revision: number, marker: number }): Promise { + public async setContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string, sync: { revision: number | null, marker: number | null }): Promise { } public async getContactCardChannelTopics(guid: string, cardId: string, channelId: string, count: number, position: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]> { return []; diff --git a/app/sdk/src/stream.ts b/app/sdk/src/stream.ts index 62b50b59..5dadd57e 100644 --- a/app/sdk/src/stream.ts +++ b/app/sdk/src/stream.ts @@ -382,7 +382,7 @@ export class StreamModule { this.focus = new FocusModule(this.log, this.store, this.crypto, this.media, null, channelId, this.guid, { node, secure, token }, channelKey, sealEnabled, revision); } else { - this.focus = new FocusModule(this.log, this.store, this.crypto, this.media, cardId, channelId, this.guid, null, null, false, 0); + this.focus = new FocusModule(this.log, this.store, this.crypto, this.media, null, channelId, this.guid, null, null, false, 0); } return this.focus; @@ -518,9 +518,9 @@ export class StreamModule { item.channelKey = await this.getChannelKey(seals); if (this.focus) { try { - await this.focus.setChannelKey(null, channelId, this.channelKey); + await this.focus.setChannelKey(null, channelId, item.channelKey); } catch (err) { - log.warn(err); + this.log.warn(err); } } } @@ -544,9 +544,9 @@ export class StreamModule { item.channelKey = await this.getChannelKey(seals); if (this.focus) { try { - await this.focus.setChannelKey(null, channelId, this.channelKey); + await this.focus.setChannelKey(null, channelId, item.channelKey); } catch (err) { - log.warn(err); + this.log.warn(err); } } } diff --git a/app/sdk/src/types.ts b/app/sdk/src/types.ts index 2a8716bf..7c006cd7 100644 --- a/app/sdk/src/types.ts +++ b/app/sdk/src/types.ts @@ -89,6 +89,7 @@ export type Topic = { topicId: string; guid: string; sealed: boolean; + blocked: boolean; dataType: string; data: any; created: number; @@ -123,18 +124,21 @@ export enum TransformType { LowQuality = 'low', // transcode to low quality } +export enum AssetType { + Image = 'image', + Video = 'video', + Audio = 'audio', + Binary = 'binary', +} + export type AssetSource = { - name: string; - type: string; - extension: string; + type: AssetType; source: any; transforms: {type: TransformType, appId: string, position?: number, thumb?: ()=>Promise}[], } export type Asset = { - assetIndex: string; - mimeType: string; - extension: string; + assetId: string; hosting: HostingMode; };