From 5206941fa72a6bb259b7f74b2353c3a9e1de77c6 Mon Sep 17 00:00:00 2001 From: balzack Date: Tue, 3 Dec 2024 07:54:47 -0800 Subject: [PATCH] implementing focus module --- .../src/conversation/useConversation.hook.ts | 2 +- app/sdk/src/focus.ts | 316 +++++++++--------- app/sdk/src/net/addFlag.ts | 2 +- 3 files changed, 163 insertions(+), 157 deletions(-) diff --git a/app/client/web/src/conversation/useConversation.hook.ts b/app/client/web/src/conversation/useConversation.hook.ts index 2dc7ff1f..1ceabb42 100644 --- a/app/client/web/src/conversation/useConversation.hook.ts +++ b/app/client/web/src/conversation/useConversation.hook.ts @@ -66,7 +66,7 @@ export function useConversation() { } const topicId = await focus.addTopic(true, 'sealedtopic', (assets: {assetId: string, appId: string}[])=>{ console.log(assets); - return { text: 'addedasset', assets: [{ encrypted: { type: 'image', thumb: '0', parts: '1' } }] }; + return { text: 'almost done', assets: [{ encrypted: { type: 'image', thumb: '0', parts: '1' } }] }; }, [asset], (percent: number)=>{ console.log(percent); }); diff --git a/app/sdk/src/focus.ts b/app/sdk/src/focus.ts index d8bb38b4..2c37825d 100644 --- a/app/sdk/src/focus.ts +++ b/app/sdk/src/focus.ts @@ -49,14 +49,14 @@ export class FocusModule implements Focus { private closeMedia: (()=>Promise)[]; private unsealAll: boolean; private markRead: ()=>Promise; - private flagChannelTopic: (string)=>Promise; + private flagChannelTopic: (id: string)=>Promise; 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 | null, sealEnabled: boolean, revision: number, markRead: ()=>Promise, flagChannelTopic: (string)=>Promise) { + 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, markRead: ()=>Promise, flagChannelTopic: (id: string)=>Promise) { this.cardId = cardId; this.channelId = channelId; this.log = log; @@ -385,167 +385,173 @@ export class FocusModule implements Focus { } } else { const topicId = await this.addRemoteChannelTopic(type, {}, false); - const appAsset = [] as {assetId: string, appId: string}[]; - if (sealed) { - 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 { 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, progress); - split.push({ partId, blockIv: ivHex }); - } - const assetItem = { - assetId: `${assetItems.length}`, - encrypted: true, - hosting: HostingMode.Split, - split, - } - appAsset.push({appId: transform.appId, assetId: assetItem.assetId}); - assetItems.push(assetItem); - } else { - throw new Error('transform not supported') - } - } - } - } else { - for (const asset of files) { - const transforms = []; - const transformMap = new Map(); - for (let transform of asset.transforms) { - if (transform.type === TransformType.Thumb && asset.type === AssetType.Image) { - transforms.push('ithumb;photo'); - transformMap.set('ithumb;photo', transform.appId); - } 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.type === AssetType.Image) { - transforms.push('icopy;photo'); - transformMap.set('icopy;photo', transform.appId); - } 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.type === AssetType.Video) { - transforms.push('vcopy;video'); - transformMap.set('vcopy;video', transform.appId); - } 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.type === AssetType.Video) { - transforms.push('vlq;video'); - transformMap.set('vlq;video', transform.appId); - } 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.type === AssetType.Binary) { - const assetId = await this.mirrorFile(asset.source, topicId, progress); - const assetItem = { - assetId: `${assetItems.length}`, - hosting: HostingMode.Basic, - basic: 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, progress); - for (let transformAsset of transformAssets) { - const assetItem = { - assetId: `${assetItems.length}`, - hosting: HostingMode.Basic, - basic: transformAsset.assetId, - } - if (transformMap.has(transformAsset.transform)) { - const appId = transformMap.get(transformAsset.transform) || '' //or to make build happy - appAsset.push({appId, assetId: transformAsset.assetId }); + try { + const appAsset = [] as {assetId: string, appId: string}[]; + if (sealed) { + 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 { 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, progress); + split.push({ partId, blockIv: ivHex }); + } + const assetItem = { + assetId: `${assetItems.length}`, + encrypted: true, + hosting: HostingMode.Split, + split, + } + appAsset.push({appId: transform.appId, assetId: assetItem.assetId}); + assetItems.push(assetItem); + } else { + throw new Error('transform not supported') + } + } + } + } else { + for (const asset of files) { + const transforms = []; + const transformMap = new Map(); + for (let transform of asset.transforms) { + if (transform.type === TransformType.Thumb && asset.type === AssetType.Image) { + transforms.push('ithumb;photo'); + transformMap.set('ithumb;photo', transform.appId); + } 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.type === AssetType.Image) { + transforms.push('icopy;photo'); + transformMap.set('icopy;photo', transform.appId); + } 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.type === AssetType.Video) { + transforms.push('vcopy;video'); + transformMap.set('vcopy;video', transform.appId); + } 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.type === AssetType.Video) { + transforms.push('vlq;video'); + transformMap.set('vlq;video', transform.appId); + } 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.type === AssetType.Binary) { + const assetId = await this.mirrorFile(asset.source, topicId, progress); + const assetItem = { + assetId: `${assetItems.length}`, + hosting: HostingMode.Basic, + basic: 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, progress); + for (let transformAsset of transformAssets) { + const assetItem = { + assetId: `${assetItems.length}`, + hosting: HostingMode.Basic, + basic: transformAsset.assetId, + } + if (transformMap.has(transformAsset.transform)) { + const appId = transformMap.get(transformAsset.transform) || '' //or to make build happy + appAsset.push({appId, assetId: transformAsset.assetId }); + assetItems.push(assetItem); + } } } } } - } - const { text, textColor, textSize, assets } = subject(appAsset); + const { text, textColor, textSize, assets } = subject(appAsset); - const getAsset = (assetId: string) => { - const index = parseInt(assetId); - const item = assetItems[index]; - if (!item) { - throw new Error('invalid assetId in subject'); + 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; + } else if (item.hosting === HostingMode.Split) { + return item.split; + } else if (item.hosting === HostingMode.Basic) { + return item.basic; + } else { + throw new Error('unknown hosting mode'); + } } - if (item.hosting === HostingMode.Inline) { - return item.inline; - } else if (item.hosting === HostingMode.Split) { - return item.split; - } else if (item.hosting === HostingMode.Basic) { - return item.basic; - } else { - throw new Error('unknown hosting mode'); - } - } - const filtered = !assets ? [] : assets.filter((asset: any)=>{ - if (sealed && asset.encrypted) { - return true; - } else if (!sealed && !asset.encrypted) { - return true; + const filtered = !assets ? [] : assets.filter((asset: any)=>{ + if (sealed && asset.encrypted) { + return true; + } else if (!sealed && !asset.encrypted) { + return true; + } else { + return false; + } + }); + 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) { + const { thumb, full } = asset.image; + return { image: { thumb: getAsset(thumb), full: getAsset(full) } }; + } else if (asset.video) { + const { thumb, lq, hd } = asset.video; + return { video: { thumb: getAsset(thumb), lq: getAsset(lq), hd: getAsset(hd) } }; + } else if (asset.audio) { + const { label, full } = asset.audio; + return { audio: { label, full: getAsset(full) } }; + } else if (asset.binary) { + const { label, extension, data } = asset.binary; + return { binary: { label, extension, data: getAsset(data) } }; + } + }); + 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); + const data = { messageEncrypted: encryptedDataB64, messageIv: ivHex }; + await this.setRemoteChannelTopicSubject(topicId, type, data); } else { - return false; + await this.setRemoteChannelTopicSubject(topicId, type, updated); } - }); - 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) { - const { thumb, full } = asset.image; - return { image: { thumb: getAsset(thumb), full: getAsset(full) } }; - } else if (asset.video) { - const { thumb, lq, hd } = asset.video; - return { video: { thumb: getAsset(thumb), lq: getAsset(lq), hd: getAsset(hd) } }; - } else if (asset.audio) { - const { label, full } = asset.audio; - return { audio: { label, full: getAsset(full) } }; - } else if (asset.binary) { - const { label, extension, data } = asset.binary; - return { binary: { label, extension, data: getAsset(data) } }; - } - }); - 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); - const data = { messageEncrypted: encryptedDataB64, messageIv: ivHex }; - await this.setRemoteChannelTopicSubject(topicId, type, data); - } else { - await this.setRemoteChannelTopicSubject(topicId, type, updated); + } catch (err) { + this.log.error(err); + await this.removeRemoteChannelTopic(topicId); + throw new Error('failed to add topic'); } return topicId; @@ -767,7 +773,7 @@ export class FocusModule implements Focus { await this.setTopicBlocked(topicId); const entry = this.topicEntries.get(topicId); if (entry) { - entry.item.topic = this.setTopic(topicId, entry.item); + entry.topic = this.setTopic(topicId, entry.item); this.emitTopics(); } } @@ -776,7 +782,7 @@ export class FocusModule implements Focus { await this.clearTopicBlocked(topicId); const entry = this.topicEntries.get(topicId); if (entry) { - entry.item.topic = this.setTopic(topicId, entry.item); + entry.topic = this.setTopic(topicId, entry.item); this.emitTopics(); } } diff --git a/app/sdk/src/net/addFlag.ts b/app/sdk/src/net/addFlag.ts index f7f5da0e..e7ad65a8 100644 --- a/app/sdk/src/net/addFlag.ts +++ b/app/sdk/src/net/addFlag.ts @@ -1,6 +1,6 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -export async function addFlag(node: string, secure: boolean, guid: string, data: { channelId?: string; articleId?: string }) { +export async function addFlag(node: string, secure: boolean, guid: string, data: { channelId?: string; articleId?: string, topicId?: string }) { const endpoint = `http${secure ? 's' : ''}://${node}/account/flag/${guid}`; const response = await fetchWithTimeout(endpoint, { method: 'POST', body: JSON.stringify(data) }); checkResponse(response.status);