implementing focus module

This commit is contained in:
balzack 2024-12-02 13:58:39 -08:00
parent 0e24bf1bb4
commit 40bb9c2a12
13 changed files with 223 additions and 206 deletions

View File

@ -151,7 +151,7 @@ const crypto = new TestCrypto();
test('received contact updates', async () => { test('received contact updates', async () => {
let testCards: Card[] = []; let testCards: Card[] = [];
const update = (cards: Card[]) => { testCards = cards } 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.addCardListener(update);
contact.setRevision(1) contact.setRevision(1)
await waitFor(() => testCards.length === 1); await waitFor(() => testCards.length === 1);
@ -162,7 +162,7 @@ test('received contact updates', async () => {
test('adds new contact', async () => { test('adds new contact', async () => {
let testCards: Card[] = []; let testCards: Card[] = [];
const update = (cards: Card[]) => { testCards = cards } 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.addCardListener(update);
contact.setRevision(3) contact.setRevision(3)
await waitFor(() => testCards.length === 1); await waitFor(() => testCards.length === 1);
@ -175,7 +175,7 @@ test('adds new contact', async () => {
test('removes contact', async () => { test('removes contact', async () => {
let testCards: Card[] = []; let testCards: Card[] = [];
const update = (cards: Card[]) => { testCards = cards } 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.addCardListener(update);
contact.setRevision(8) contact.setRevision(8)
await waitFor(() => testCards.length === 1); await waitFor(() => testCards.length === 1);
@ -188,7 +188,7 @@ test('removes contact', async () => {
test('connects and disconnects with known contact', async () => { test('connects and disconnects with known contact', async () => {
let testCards: Card[] = []; let testCards: Card[] = [];
const update = (cards: Card[]) => { testCards = cards } 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.addCardListener(update);
contact.setRevision(11) contact.setRevision(11)
await waitFor(() => testCards.length === 1); await waitFor(() => testCards.length === 1);

View File

@ -129,10 +129,10 @@ jest.mock('../src/net/fetchUtil', () => {
url == 'http://test_url/content/channels?agent=test_token&types=%5B%5D') { 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)] }); 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' || } 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)] }); 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' || } 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)] }); 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' || } else if (url == 'https://URL_A/content/channels/CHAN1/detail?contact=G000A.T000A' ||
url == 'http://test_url/content/channels/CHAN1/detail?agent=test_token') { url == 'http://test_url/content/channels/CHAN1/detail?agent=test_token') {
@ -159,8 +159,8 @@ const store = new TestStore();
test('received contact updates', async () => { test('received contact updates', async () => {
const cardChannels = new Map<string | null, Channel[]>(); const cardChannels = new Map<string | null, Channel[]>();
const stream = new StreamModule(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, '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 content = new ContentModule(log, null, contact, stream);
const channelUpdate = ({ channels, cardId }: { channels: Channel[]; cardId: string | null }) => { const channelUpdate = ({ channels, cardId }: { channels: Channel[]; cardId: string | null }) => {
@ -170,6 +170,7 @@ test('received contact updates', async () => {
await contact.setRevision(1); await contact.setRevision(1);
await waitFor(() => cardChannels.get('C000A')?.length === 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].data.subject === 'test_subject_0');
await waitFor(() => cardChannels.get('C000A')?.[0].lastTopic.data.message === 'test_message_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 () => { test('received stream updates', async () => {
const streamChannels = new Map<string | null, Channel[]>(); const streamChannels = new Map<string | null, Channel[]>();
const stream = new StreamModule(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, '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 content = new ContentModule(log, null, contact, stream);
const channelUpdate = ({ channels, cardId }: { channels: Channel[]; cardId: string | null }) => { const channelUpdate = ({ channels, cardId }: { channels: Channel[]; cardId: string | null }) => {

View File

@ -72,19 +72,8 @@ export interface Contact {
getBlockedCards(): Promise<Card[]>; getBlockedCards(): Promise<Card[]>;
getRegistry(handle: string | null, server: string | null): Promise<Profile[]>; getRegistry(handle: string | null, server: string | null): Promise<Profile[]>;
leaveChannel(cardId: string, channelId: string): Promise<void>;
flagChannel(cardId: string, channelId: string): Promise<void>;
setBlockedChannel(cardId: string, channelId: string, blocked: boolean): Promise<void>;
getBlockedChannels(): Promise<Channel[]>;
setUnreadChannel(cardId: string, channelId: string, unread: boolean): Promise<void>;
getChannelNotifications(cardId: string, channelId: string): Promise<boolean>;
setChannelNotifications(cardId: string, channelId: string, enabled: boolean): Promise<void>;
addCardListener(ev: (cards: Card[]) => void): void; addCardListener(ev: (cards: Card[]) => void): void;
removeCardListener(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 { export interface Content {

View File

@ -274,17 +274,19 @@ export type Login = {
pushSupported: boolean; 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 = { export type BasicEntity = {
text: string, text: string,
textColor: string, textColor: string,
textSize: string, textSize: string,
assets: { assets: 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?: { extension: string, data: string }
}
} }
export type SealedBasicEntity = { export type SealedBasicEntity = {

View File

@ -1,13 +1,13 @@
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import type { Focus } from './api'; 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 type { Topic, Asset, AssetSource, Participant } from './types';
import { TransformType, HostingMode } from './types'; import { TransformType, HostingMode, AssetType } from './types';
import type { Logging } from './logging'; import type { Logging } from './logging';
import { Store } from './store'; import { Store } from './store';
import { Crypto } from './crypto'; import { Crypto } from './crypto';
import { Media } from './media'; import { Media } from './media';
import { HostingMode } from './types'; import { BasicEntity, BasicAsset, SealedBasicEntity, TopicDetailEntity } from './entities';
import { defaultTopicItem } from './items'; import { defaultTopicItem } from './items';
import { getChannelTopics } from './net/getChannelTopics'; import { getChannelTopics } from './net/getChannelTopics';
import { getChannelTopicDetail } from './net/getChannelTopicDetail'; import { getChannelTopicDetail } from './net/getChannelTopicDetail';
@ -38,22 +38,23 @@ export class FocusModule implements Focus {
private connection: { node: string; secure: boolean; token: string } | null; private connection: { node: string; secure: boolean; token: string } | null;
private syncing: boolean; private syncing: boolean;
private closing: boolean; private closing: boolean;
private nextRevision: number; private nextRevision: number | null;
private cacheView: {revision: number | null, marker: number | null}; private storeView: {revision: number | null, marker: number | null};
private storeView: {topicId: string, position: number} | null; private cacheView: {topicId: string, position: number} | null;
private localComplete: boolean; private localComplete: boolean;
private remoteComplete: boolean; private remoteComplete: boolean;
private sealEnabled: boolean; private sealEnabled: boolean;
private channelKey: string | null; private channelKey: string | null;
private loadMore: boolean; private loadMore: boolean;
private closeMedia: (()=>Promsie<void>)[]; private closeMedia: (()=>Promise<void>)[];
private unsealAll: boolean;
private markers: Set<string>; private markers: Set<string>;
// view of topics // view of topics
private topicEntries: Map<string, { item: TopicItem; topic: Topic }>; private topicEntries: Map<string, { item: TopicItem; topic: Topic }>;
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.cardId = cardId;
this.channelId = channelId; this.channelId = channelId;
this.log = log; this.log = log;
@ -75,6 +76,7 @@ export class FocusModule implements Focus {
this.closeMedia = []; this.closeMedia = [];
this.nextRevision = null; this.nextRevision = null;
this.loadMore = false; this.loadMore = false;
this.unsealAll = false;
this.localComplete = false; this.localComplete = false;
this.remoteComplete = false; this.remoteComplete = false;
this.init(revision); this.init(revision);
@ -83,7 +85,7 @@ export class FocusModule implements Focus {
private async init(revision: number) { private async init(revision: number) {
const { guid } = this; const { guid } = this;
this.nextRevision = revision; this.nextRevision = revision;
this.storeView = this.getChannelTopicRevision(); this.storeView = await this.getChannelTopicRevision();
// load markers // load markers
const values = await this.store.getMarkers(guid); const values = await this.store.getMarkers(guid);
@ -101,7 +103,6 @@ export class FocusModule implements Focus {
private async sync(): Promise<void> { private async sync(): Promise<void> {
if (!this.syncing) { if (!this.syncing) {
this.syncing = true; this.syncing = true;
const { guid, node, secure, token, channelTypes } = this;
while ((this.loadMore || this.unsealAll || this.nextRevision) && !this.closing && this.connection) { while ((this.loadMore || this.unsealAll || this.nextRevision) && !this.closing && this.connection) {
if (this.loadMore) { if (this.loadMore) {
try { try {
@ -138,14 +139,14 @@ export class FocusModule implements Focus {
entry.item.position = detail.created; entry.item.position = detail.created;
await this.unsealTopicDetail(entry.item); await this.unsealTopicDetail(entry.item);
entry.topic = this.setTopic(id, 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 { } 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) { if (delta.topics.length === 0) {
this.remoteCompleted = true; this.remoteComplete = true;
} }
const rev = this.storeView.revision ? this.storeView.revision : delta.revision; const rev = this.storeView.revision ? this.storeView.revision : delta.revision;
const mark = delta.topics.length ? delta.marker : null; const mark = delta.topics.length ? delta.marker : null;
@ -170,24 +171,25 @@ export class FocusModule implements Focus {
const { id, revision, data } = entity; const { id, revision, data } = entity;
if (data) { if (data) {
const { detailRevision, topicDetail } = 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); const entry = await this.getTopicEntry(id);
if (detailRevision > entry.item.detail.revision) { if (detailRevision > entry.item.detail.revision) {
const detail = topicDetail ? topicDetail : await this.getRemoteChannelTopicDetail(id);
entry.item.detail = this.getTopicDetail(detail, detailRevision); entry.item.detail = this.getTopicDetail(detail, detailRevision);
entry.item.unsealedDetail = null; entry.item.unsealedDetail = null;
entry.item.position = detail.created; entry.item.position = detail.created;
await this.unsealTopicDetail(entry.item); await this.unsealTopicDetail(entry.item);
entry.topic = this.setTopic(id, 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 { } else {
const item = { detail, position: detail.created, unsealedDetail: null }; const itemDetail= this.getTopicDetail(detail, detailRevision);
await this.store.addLocalChannelTopic(id, item); const item = { detail: itemDetail, position: detail.created, unsealedDetail: null };
await this.addLocalChannelTopic(id, item);
} }
} else { } else {
this.topicEntries.delete(id); this.topicEntries.delete(id);
await this.store.removeLocalChannelTopic(id); await this.removeLocalChannelTopic(id);
} }
} }
this.storeView = { revision: nextRev, marker: delta.marker }; this.storeView = { revision: nextRev, marker: delta.marker };
@ -213,7 +215,7 @@ export class FocusModule implements Focus {
try { try {
const { item } = entry; const { item } = entry;
if (await this.unsealTopicDetail(item)) { if (await this.unsealTopicDetail(item)) {
await this.setLocalChannelTopicUnsealedDetail(guid, topicId, item.unsealedDetail); await this.setLocalChannelTopicUnsealedDetail(topicId, item.unsealedDetail);
entry.topic = this.setTopic(topicId, item); entry.topic = this.setTopic(topicId, item);
} }
} catch (err) { } catch (err) {
@ -234,7 +236,7 @@ export class FocusModule implements Focus {
if (!connection) { if (!connection) {
throw new Error('disconnected from channel'); throw new Error('disconnected from channel');
} }
const { node, secure, token } = this.connection; const { node, secure, token } = connection;
const params = `${cardId ? 'contact' : 'agent'}=${token}` const params = `${cardId ? 'contact' : 'agent'}=${token}`
const url = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}/assets/${blockId}?${params}` 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) { if (!connection) {
throw new Error('disconnected from channel'); throw new Error('disconnected from channel');
} }
const { node, secure, token } = this.connection; const { node, secure, token } = connection;
const params = `${cardId ? 'contact' : 'agent'}=${token}` const params = `${cardId ? 'contact' : 'agent'}=${token}`
const url = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}/blocks?${params}` 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(); const xhr = new XMLHttpRequest();
xhr.open('POST', url, true); xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'text/plain'); xhr.setRequestHeader('Content-Type', 'text/plain');
xhr.progress = progress; xhr.onprogress = (ev: ProgressEvent<EventTarget>)=>{ progress(0) };
xhr.onload = () => { xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) { if (xhr.status >= 200 && xhr.status < 300) {
try { try {
@ -293,7 +295,7 @@ export class FocusModule implements Focus {
if (!connection) { if (!connection) {
throw new Error('disconnected from channel'); 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 params = `${cardId ? 'contact' : 'agent'}=${token}&body=multipart`
const url = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}/blocks?${params}` const url = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}/blocks?${params}`
const formData = new FormData(); const formData = new FormData();
@ -302,7 +304,7 @@ export class FocusModule implements Focus {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('POST', url, true); xhr.open('POST', url, true);
xhr.progress = progress; xhr.onprogress = (ev: ProgressEvent<EventTarget>)=>{ progress(0) };
xhr.onload = () => { xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) { if (xhr.status >= 200 && xhr.status < 300) {
try { try {
@ -326,7 +328,7 @@ export class FocusModule implements Focus {
if (!connection) { if (!connection) {
throw new Error('disconnected from channel'); 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 params = `${cardId ? 'contact' : 'agent'}=${token}&transforms=${encodeURIComponent(JSON.stringify(transforms))}`
const url = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}/assets?${params}` const url = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}/assets?${params}`
const formData = new FormData(); const formData = new FormData();
@ -335,7 +337,7 @@ export class FocusModule implements Focus {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('POST', url, true); xhr.open('POST', url, true);
xhr.progress = progress; xhr.onprogress = (ev: ProgressEvent<EventTarget>)=>{ progress(0) };
xhr.onload = () => { xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) { if (xhr.status >= 200 && xhr.status < 300) {
try { try {
@ -365,9 +367,12 @@ export class FocusModule implements Focus {
if (files.length == 0) { if (files.length == 0) {
const data = subject([]); const data = subject([]);
if (sealed) { if (sealed) {
if (!crypto || !channelKey) {
throw new Error('duplicate throw for build warning');
}
const subjectString = JSON.stringify(data); const subjectString = JSON.stringify(data);
const { ivHex } = await crypto.aesIv(); 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 }; const dataEncrypted = { messageEncrypted: encryptedDataB64, messageIv: ivHex };
return await this.addRemoteChannelTopic(type, dataEncrypted, true); return await this.addRemoteChannelTopic(type, dataEncrypted, true);
} else { } else {
@ -389,14 +394,21 @@ export class FocusModule implements Focus {
appAsset.push({appId: transform.appId, assetId: assetItem.assetId}); appAsset.push({appId: transform.appId, assetId: assetItem.assetId});
assetItems.push(assetItem); assetItems.push(assetItem);
} else if (transform.type === TransformType.Copy) { } 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 }[]; const split = [] as { partId: string, blockIv: string }[];
for (let i = 0; i * ENCRYPT_BLOCK_SIZE < mediaFile.size; i++) { 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 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 base64Data = await mediaFile.getData(i * ENCRYPT_BLOCK_SIZE, length);
const { ivHex } = await crypto.aesIv(); const { ivHex } = await crypto.aesIv();
const { encryptedDataB64 } = await crypto.aesEncrypt(base64Data, ivHex, channelKey); 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 }); split.push({ partId, blockIv: ivHex });
} }
const assetItem = { const assetItem = {
@ -405,7 +417,7 @@ export class FocusModule implements Focus {
hosting: HostingMode.Split, hosting: HostingMode.Split,
split, split,
} }
appAsset.push({appId: transform.appId, assetId: assetItems.assetId}); appAsset.push({appId: transform.appId, assetId: assetItem.assetId});
assetItems.push(assetItem); assetItems.push(assetItem);
} else { } else {
throw new Error('transform not supported') throw new Error('transform not supported')
@ -417,55 +429,53 @@ export class FocusModule implements Focus {
const transforms = []; const transforms = [];
const transformMap = new Map<string, string>(); const transformMap = new Map<string, string>();
for (let transform of asset.transforms) { 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'); transforms.push('ithumb;photo');
transformMap.set('ithumb;photo', transform.appId); 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'); transforms.push('ilg;photo');
transformMap.set('ilg;photo', transform.appId); 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'); transforms.push('icopy;photo');
transformMap.set('icopy;photo', transform.appId); 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'); transforms.push('vthumb;video');
transformMap.set('vthumb;video', transform.appId); 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'); transforms.push('vcopy;video');
transformMap.set('vcopy;video', transform.appId); 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'); transforms.push('vhd;video');
transformMap.set('vhd;video', transform.appId); 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'); transforms.push('vlq;video');
transformMap.set('vlq;video', transform.appId); 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'); transforms.push('acopy;audio');
transformMap.set('acopy;audio', transform.appId); transformMap.set('acopy;audio', transform.appId);
} else if (transform.type === TransformType.Copy && asset.mimeType === 'binary') { } else if (transform.type === TransformType.Copy && asset.type === AssetType.Binary) {
const assetId = await this.mirrorFile(asset.source, topicId, (percent: number)=>{console.log(`progress: ${percent}`)}); const assetId = await this.mirrorFile(asset.source, topicId, progress);
const assetItem = { const assetItem = {
assetId: `${assetItems.length}`, assetId: `${assetItems.length}`,
encrytped: false,
hosting: HostingMode.Basic, hosting: HostingMode.Basic,
basic: assetId, basic: assetId,
} }
appAsset.push({appId: transform.appId, assetId: assetitem.assetId}); appAsset.push({appId: transform.appId, assetId: assetItem.assetId});
assetItems.push(assetItem); assetItems.push(assetItem);
} else { } else {
throw new Error('transform not supported'); throw new Error('transform not supported');
} }
} }
if (transforms.length > 0) { 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) { for (let transformAsset of transformAssets) {
const assetItem = { const assetItem = {
assetId: `${assetItems.length}`, assetId: `${assetItems.length}`,
encrypted: false,
hosting: HostingMode.Basic, hosting: HostingMode.Basic,
basic: transformAsset.assetId, basic: transformAsset.assetId,
} }
if (transformMap.has(transformAsset.transform)) { 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 }); appAsset.push({appId, assetId: transformAsset.assetId });
assetItems.push(assetItem); assetItems.push(assetItem);
} }
@ -482,17 +492,17 @@ export class FocusModule implements Focus {
throw new Error('invalid assetId in subject'); throw new Error('invalid assetId in subject');
} }
if (item.hosting === HostingMode.Inline) { if (item.hosting === HostingMode.Inline) {
return item.inline; return item.inline;
} } else if (item.hosting === HostingMode.Split) {
if (item.hosting === HostingMode.Split) {
return item.split; return item.split;
} } else if (item.hosting === HostingMode.Basic) {
if (item.hosting === HostingMode.Basic) {
return item.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) { if (sealed && asset.encrypted) {
return true; return true;
} else if (!sealed && !asset.encrypted) { } else if (!sealed && !asset.encrypted) {
@ -501,8 +511,8 @@ export class FocusModule implements Focus {
return false; return false;
} }
}); });
const mapped = filtered.map(asset => { const mapped = filtered.map((asset: any) => {
if (sealed) { if (asset.encrypted) {
const { type, thumb, parts } = asset.encrypted; const { type, thumb, parts } = asset.encrypted;
return { encrypted: { type, thumb: getAsset(thumb), parts: getAsset(parts) } }; return { encrypted: { type, thumb: getAsset(thumb), parts: getAsset(parts) } };
} else if (asset.image) { } else if (asset.image) {
@ -512,7 +522,7 @@ export class FocusModule implements Focus {
const { thumb, lq, hd } = asset.video; const { thumb, lq, hd } = asset.video;
return { video: { thumb: getAsset(thumb), lq: getAsset(lq), hd: getAsset(hd) } }; return { video: { thumb: getAsset(thumb), lq: getAsset(lq), hd: getAsset(hd) } };
} else if (asset.audio) { } else if (asset.audio) {
const { label, fulle } = asset.audio; const { label, full } = asset.audio;
return { audio: { label, full: getAsset(full) } }; return { audio: { label, full: getAsset(full) } };
} else if (asset.binary) { } else if (asset.binary) {
const { label, extension, data } = asset.binary; const { label, extension, data } = asset.binary;
@ -521,6 +531,9 @@ export class FocusModule implements Focus {
}); });
const updated = { text, textColor, textSize, assets: mapped }; const updated = { text, textColor, textSize, assets: mapped };
if (sealed) { if (sealed) {
if (!crypto || !channelKey) {
throw new Error('duplicate throw to make build happy')
}
const subjectString = JSON.stringify({ message: updated }); const subjectString = JSON.stringify({ message: updated });
const { ivHex } = await crypto.aesIv(); const { ivHex } = await crypto.aesIv();
const { encryptedDataB64 } = await crypto.aesEncrypt(subjectString, ivHex, channelKey); 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) { 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; const { sealEnabled, channelKey, crypto } = this;
if (sealed && (!sealEnabled || !channelKey || !crypto)) { if (sealed && (!sealEnabled || !channelKey || !crypto)) {
throw new Error('encryption not set'); throw new Error('encryption not set');
} }
const { assets: assetItems } = this.getTopicData(item);
const item = this.topicEntries(topicId);
if (!item) {
throw new Error('topic not found');
}
const { assets: assetItems } = getTopicData(item);
const appAsset = [] as {assetId: string, appId: string}[]; const appAsset = [] as {assetId: string, appId: string}[];
if (sealed) { if (sealed) {
for (const asset of assets) { for (const asset of files) {
for (const transform of asset.transforms) { for (const transform of asset.transforms) {
if (transform.type === TransformType.Thumb && transform.thumb) { if (transform.type === TransformType.Thumb && transform.thumb) {
const assetItem = { const assetItem = {
assetId: `${assetItems.length}`, assetId: `${assetItems.length}`,
encrytped: true,
hosting: HostingMode.Inline, hosting: HostingMode.Inline,
inline: await transform.thumb(), inline: await transform.thumb(),
} }
appAsset.push({appId: transform.appId, assetId: assetItem.assetId}); appAsset.push({appId: transform.appId, assetId: assetItem.assetId});
assetItems.push(assetItem); assetItems.push(assetItem);
} else if (transform.type === TransformType.Copy) { } 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 }[]; const split = [] as { partId: string, blockIv: string }[];
for (let i = 0; i * ENCRYPT_BLOCK_SIZE < mediaFile.size; i++) { 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 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 base64Data = await mediaFile.getData(i * ENCRYPT_BLOCK_SIZE, length);
const { ivHex } = await crypto.aesIv(); const { ivHex } = await crypto.aesIv();
const { encryptedDataB64 } = await crypto.aesEncrypt(base64Data, ivHex, channelKey); 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 }); split.push({ partId, blockIv: ivHex });
} }
const assetItem = { const assetItem = {
assetId: `${assetItems.length}`, assetId: `${assetItems.length}`,
encrypted: true,
hosting: HostingMode.Split, hosting: HostingMode.Split,
split, split,
} }
appAsset.push({appId: transform.appId, assetId: assetItems.assetId}); appAsset.push({appId: transform.appId, assetId: assetItem.assetId});
assetItems.push(assetItem); assetItems.push(assetItem);
} else { } else {
throw new Error('transform not supported') throw new Error('transform not supported')
@ -585,54 +604,54 @@ export class FocusModule implements Focus {
} }
} }
} else { } else {
for (const asset of assets) { for (const asset of files) {
const transforms = []; const transforms = [];
const transformMap = new Map<string, string>(); const transformMap = new Map<string, string>();
for (let transform of asset.transforms) { 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'); transforms.push('ithumb;photo');
transformMap.set('ithumb;photo', transform.appId); 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'); transforms.push('icopy;photo');
transformMap.set('icopy;photo', transform.appId); 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'); transforms.push('vthumb;video');
transformMap.set('vthumb;video', transform.appId); 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'); transforms.push('vcopy;video');
transformMap.set('vcopy;video', transform.appId); 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'); transforms.push('vlq;video');
transformMap.set('vlq;video', transform.appId); 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'); transforms.push('acopy;audio');
transformMap.set('acopy;audio', transform.appId); transformMap.set('acopy;audio', transform.appId);
} else if (transform.type === TransformType.Copy && asset.mimeType === 'binary') { } else if (transform.type === TransformType.Copy && asset.type === AssetType.Binary) {
const assetId = await this.mirrorFile(asset.source, topicId, (percent: number)=>{console.log(`progress: ${percent}`)}); const assetId = await this.mirrorFile(asset.source, topicId, progress);
const assetItem = { const assetItem = {
assetId: `${assetItems.length}`, assetId: `${assetItems.length}`,
encrytped: false,
hosting: HostingMode.Basic, hosting: HostingMode.Basic,
basic: assetId, basic: assetId,
} }
appAsset.push({appId: transform.appId, assetId: assetitem.assetId}); appAsset.push({appId: transform.appId, assetId: assetItem.assetId});
assetItems.push(assetItem); assetItems.push(assetItem);
} else { } else {
throw new Error('transform not supported'); throw new Error('transform not supported');
} }
} }
if (transforms.length > 0) { 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 (transformAsset of transformAssets) { for (let transformAsset of transformAssets) {
const assetItem = { const assetItem = {
assetId: `${assetItem.size}`, assetId: `${assetItems.length}`,
encrypted: false,
hosting: HostingMode.Basic, hosting: HostingMode.Basic,
basic: transformAsset.assetId, basic: transformAsset.assetId,
} }
const appId = transformMap.get(assetItem.assetId) if (transformMap.get(assetItem.assetId)) {
appAsset.push({appId, assetId: assetItem.assetId }); const appId = transformMap.get(assetItem.assetId) || '' //or to make build happy
assetItems.push(assetItem); appAsset.push({appId, assetId: assetItem.assetId });
assetItems.push(assetItem);
}
} }
} }
} }
@ -641,18 +660,21 @@ export class FocusModule implements Focus {
const getAsset = (assetId: string) => { const getAsset = (assetId: string) => {
const index = parseInt(assetId); const index = parseInt(assetId);
const item = assetItems[index]; const item = assetItems[index];
if (!item) {
throw new Error('invalid assetId in subject');
}
if (item.hosting === HostingMode.Inline) { if (item.hosting === HostingMode.Inline) {
return item.inline; return item.inline;
} } if (item.hosting === HostingMode.Split) {
if (item.hosting === HostingMode.Split) {
return item.split; return item.split;
} } if (item.hosting === HostingMode.Basic) {
if (item.hosting === HostingMode.Basic) {
return item.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) { if (sealed && asset.encrypted) {
return true; return true;
} else if (!sealed && !asset.encrypted) { } else if (!sealed && !asset.encrypted) {
@ -661,7 +683,7 @@ export class FocusModule implements Focus {
return false; return false;
} }
}); });
const mapped = filtered.map(asset => { const mapped = filtered.map((asset: any) => {
if (sealed) { if (sealed) {
const { type, thumb, parts } = asset.encrypted; const { type, thumb, parts } = asset.encrypted;
return { encrypted: { type, thumb: getAsset(thumb), parts: getAsset(parts) } }; return { encrypted: { type, thumb: getAsset(thumb), parts: getAsset(parts) } };
@ -672,7 +694,7 @@ export class FocusModule implements Focus {
const { thumb, lq, hd } = asset.video; const { thumb, lq, hd } = asset.video;
return { video: { thumb: getAsset(thumb), lq: getAsset(lq), hd: getAsset(hd) } }; return { video: { thumb: getAsset(thumb), lq: getAsset(lq), hd: getAsset(hd) } };
} else if (asset.audio) { } else if (asset.audio) {
const { label, fulle } = asset.audio; const { label, full } = asset.audio;
return { audio: { label, full: getAsset(full) } }; return { audio: { label, full: getAsset(full) } };
} else if (asset.binary) { } else if (asset.binary) {
const { label, extension, data } = asset.binary; const { label, extension, data } = asset.binary;
@ -682,6 +704,9 @@ export class FocusModule implements Focus {
const updated = { text, textColor, textSize, assets: mapped }; const updated = { text, textColor, textSize, assets: mapped };
if (sealed) { if (sealed) {
if (!crypto || !channelKey) {
throw new Error('duplicate throw to make build happy')
}
const subjectString = JSON.stringify({ message: updated }); const subjectString = JSON.stringify({ message: updated });
const { ivHex } = await crypto.aesIv(); const { ivHex } = await crypto.aesIv();
const { encryptedDataB64 } = await crypto.aesEncrypt(subjectString, ivHex, channelKey); const { encryptedDataB64 } = await crypto.aesEncrypt(subjectString, ivHex, channelKey);
@ -693,39 +718,34 @@ export class FocusModule implements Focus {
} }
} }
public async removeTopic(topicId: string) {} public async removeTopic(topicId: string) {
await this.removeRemoteChannelTopic(topicId);
public async setUnreadChannel() {} }
public async clearUnreadChannel() {}
public async getTopicAssetUrl(topicId: string, assetId: string, progress: (percent: number) => boolean): Promise<string> { public async getTopicAssetUrl(topicId: string, assetId: string, progress: (percent: number) => boolean): Promise<string> {
const entry = this.topicEntries.get(topicId); const entry = this.topicEntries.get(topicId);
if (!entry) { if (!entry) {
throw new Error('topic entry not found'); throw new Error('topic entry not found');
} }
const { assets } = this.getTopicData(entry); const { assets } = this.getTopicData(entry.item);
const asset = assets.find(item => item.assetId === assetid); const asset = assets.find(item => item.assetId === assetId);
if (!asset) { if (!asset) {
throw new Error('asset entry not found'); throw new Error('asset entry not found');
} }
if (asset.hosting === HostingMode.Inline) { if (asset.hosting === HostingMode.Inline && asset.inline) {
return `data://${asset.inline}`; return `data://${asset.inline}`;
} else if (asset.hosting === HostingMode.Basic) { } else if (asset.hosting === HostingMode.Basic && asset.basic) {
return this.getRemoteChannelTopicAsseturl(topicId, assset.basic); return this.getRemoteChannelTopicAssetUrl(topicId, asset.basic);
} else if (asset.hosting === HostingMode.Split) { } else if (asset.hosting === HostingMode.Split && asset.split) {
const write = this.media.write();
this.closeMedia.push(write.close);
const { sealEnabled, channelKey, crypto, media } = this; const { sealEnabled, channelKey, crypto, media } = this;
if (!sealEnabled || !channelKey || !crypto || !media) { if (!sealEnabled || !channelKey || !crypto || !media) {
throw new Error('media decryption not set'); throw new Error('media file decryption not set');
}
if (!asset.split || !asset.split.length) {
throw new Error('invalid split media');
} }
const write = await media.write();
this.closeMedia.push(write.close);
for (let i = 0; i < asset.split.length; i++) { for (let i = 0; i < asset.split.length; i++) {
const block = await this.downloadBlock(topiccId, asset.split[i].blockId); const block = await this.downloadBlock(topicId, asset.split[i].partId);
const { data } = await this.crypto.aesDecrypt(block, asset.split[i].partIv, channelKey); const { data } = await crypto.aesDecrypt(block, asset.split[i].blockIv, channelKey);
await write.setData(data); await write.setData(data);
} }
return await write.getUrl(); 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 flagTopic(topicId: string) {}
public async setBlockTopic(topicId: string) {} public async setBlockTopic(topicId: string) {}
@ -741,6 +770,11 @@ export class FocusModule implements Focus {
public async clearBlockTopic(topicId: string) {} public async clearBlockTopic(topicId: string) {}
private async unsealTopicDetail(item: TopicItem): Promise<boolean> { private async unsealTopicDetail(item: TopicItem): Promise<boolean> {
if (item.detail.sealed && !item.unsealedDetail && this.sealEnabled && this.channelKey && this.crypto) { if (item.detail.sealed && !item.unsealedDetail && this.sealEnabled && this.channelKey && this.crypto) {
try { try {
@ -810,7 +844,7 @@ export class FocusModule implements Focus {
await this.sync(); 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) { if (cardId === this.cardId && channelId === this.channelId) {
this.channelKey = channelKey; this.channelKey = channelKey;
this.unsealAll = true; this.unsealAll = true;
@ -830,7 +864,7 @@ export class FocusModule implements Focus {
private emitStatus() { private emitStatus() {
const status = this.connection ? 'connected' : 'disconnected' const status = this.connection ? 'connected' : 'disconnected'
ev(status); this.emitter.emit('status', status);
} }
private getTopicData(item: TopicItem): { data: any, assets: AssetItem[] } { private getTopicData(item: TopicItem): { data: any, assets: AssetItem[] } {
@ -843,15 +877,12 @@ export class FocusModule implements Focus {
const { text, textColor, textSize, assets } = topicDetail; const { text, textColor, textSize, assets } = topicDetail;
let index: number = 0; let index: number = 0;
const assetItems = new Set<AssetItem>(); const assetItems = new Set<AssetItem>();
const dataAssets = !assets ? [] : assets.map(({ encrypted, image, audio, video, binary }) => { const dataAssets = !assets ? [] : assets.map(({ encrypted, image, audio, video, binary }: BasicAsset) => {
if (encrypted) { if (encrypted) {
const { type, thumb, label, extension, parts } = encrypted; const { type, thumb, label, extension, parts } = encrypted;
if (thumb) { if (thumb) {
const asset = { const asset = {
assetId: `${revision}.${index}`, assetId: `${revision}.${index}`,
mimeType: 'image/png',
extension: 'png',
encrypted: false,
hosting: HostingMode.Inline, hosting: HostingMode.Inline,
inline: thumb, inline: thumb,
} }
@ -860,9 +891,6 @@ export class FocusModule implements Focus {
} }
const asset = { const asset = {
assetId: `${revision}.${index}`, assetId: `${revision}.${index}`,
mimeType: type,
extension: extension,
encrypted: true,
hosting: HostingMode.Split, hosting: HostingMode.Split,
split: parts, split: parts,
} }
@ -870,18 +898,15 @@ export class FocusModule implements Focus {
index += 1; index += 1;
if (thumb) { 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 { } else {
return { type, data: `${revision}.${index-1}` } return { encrypted: { type, data: `${revision}.${index-1}`, label, extension } }
} }
} else { } 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) { if (thumb) {
const asset = { const asset = {
assetId: `${revision}.${index}`, assetId: `${revision}.${index}`,
mimeType: 'image/png',
extension: 'png',
encrypted: false,
hosting: HostingMode.Basic, hosting: HostingMode.Basic,
basic: thumb, basic: thumb,
} }
@ -890,20 +915,20 @@ export class FocusModule implements Focus {
} }
const asset = { const asset = {
assetId: `${revision}.${index}`, assetId: `${revision}.${index}`,
mimeType: image ? 'image' : audio ? 'audio' : video ? 'video' : 'binary',
extension: extension,
encrypted: false,
hosting: HostingMode.Basic, hosting: HostingMode.Basic,
basic: full || hd || lq, basic: full || hd || lq,
} }
assetItems.add(asset); assetItems.add(asset);
index += 1; index += 1;
const type = image ? 'image' : audio ? 'audio' : video ? 'video' : 'binary';
const assetEntry = {} as any;
if (thumb) { 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 { } 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()) }; 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, status: item.detail.status,
transform: item.detail.transform, transform: item.detail.transform,
assets: assets.map(asset => { assets: assets.map(asset => {
const { assetId, mimeType, hosting, extension } = asset; const { assetId, hosting } = asset;
return { assetId, mimeType, hosting, extension }; 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; const { guid, dataType, data, created, updated, status, transform } = entity;
return { return {
revision, revision,
@ -967,8 +992,8 @@ export class FocusModule implements Focus {
} }
} }
private async setChannelTopicRevision(sync: { revision: number, marker: number}) { private async setChannelTopicRevision(sync: { revision: number | null, marker: number | null}) {
const { guid, cardId, channelId, revision } = this; const { guid, cardId, channelId } = this;
if (cardId) { if (cardId) {
await this.store.setContactCardChannelTopicRevision(guid, cardId, channelId, sync); 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; const { guid, cardId, channelId } = this;
if (cardId) { if (cardId) {
return await this.store.getContactCardChannelTopics(guid, cardId, channelId, BATCH_COUNT, offset); return await this.store.getContactCardChannelTopics(guid, cardId, channelId, BATCH_COUNT, offset);
@ -1009,7 +1034,7 @@ export class FocusModule implements Focus {
if (cardId) { if (cardId) {
await this.store.setContactCardChannelTopicDetail(guid, cardId, channelId, topicId, detail, unsealedDetail, position); await this.store.setContactCardChannelTopicDetail(guid, cardId, channelId, topicId, detail, unsealedDetail, position);
} else { } 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) { if (cardId) {
await this.store.setContactCardChannelTopicUnsealedDetail(guid, cardId, channelId, topicId, unsealedDetail); await this.store.setContactCardChannelTopicUnsealedDetail(guid, cardId, channelId, topicId, unsealedDetail);
} else { } 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) { if (!connection) {
throw new Error('disconnected channel'); 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}` 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) { if (!connection) {
throw new Error('disconnected channel'); throw new Error('disconnected channel');
} }
const { node, secure, token } = this.connection const { node, secure, token } = connection
if (cardId) { if (cardId) {
return await getContactChannelTopics(node, secure, token, channelId, revision, (begin || !revision) ? BATCH_COUNT : null, begin, end); return await getContactChannelTopics(node, secure, token, channelId, revision, (begin || !revision) ? BATCH_COUNT : null, begin, end);
} else { } else {
@ -1049,7 +1074,7 @@ export class FocusModule implements Focus {
if (!connection) { if (!connection) {
throw new Error('disconnected channel'); throw new Error('disconnected channel');
} }
const { node, secure, token } = this.connection const { node, secure, token } = connection
if (cardId) { if (cardId) {
return await getContactChannelTopicDetail(node, secure, token, channelId, topicId); return await getContactChannelTopicDetail(node, secure, token, channelId, topicId);
} else { } else {
@ -1062,7 +1087,7 @@ export class FocusModule implements Focus {
if (!connection) { if (!connection) {
throw new Error('disconnected channel'); throw new Error('disconnected channel');
} }
const { node, secure, token } = this.connection; const { node, secure, token } = connection;
if (cardId) { if (cardId) {
return await addContactChannelTopic(node, secure, token, channelId, dataType, data, confirm); return await addContactChannelTopic(node, secure, token, channelId, dataType, data, confirm);
} else { } else {
@ -1075,7 +1100,7 @@ export class FocusModule implements Focus {
if (!connection) { if (!connection) {
throw new Error('disconnected from channel'); throw new Error('disconnected from channel');
} }
const { node, secure, token } = this.connection; const { node, secure, token } = connection;
if (cardId) { if (cardId) {
return await setContactChannelTopicSubject(node, secure, token, channelId, topicId, dataType, data); return await setContactChannelTopicSubject(node, secure, token, channelId, topicId, dataType, data);
} else { } 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; const { cardId, channelId, connection } = this;
if (!connection) { if (!connection) {
throw new Error('disconnected from channel'); throw new Error('disconnected from channel');
} }
const { node, secure, token } = this.connection; const { node, secure, token } = connection;
if (cardId) { if (cardId) {
return await removeContactChannelTopicSubject(node, secure, token, channelId, topicId); return await removeContactChannelTopic(node, secure, token, channelId, topicId);
} else { } else {
return await removeChannelTopic(node, secure, token, channelId, topicId); return await removeChannelTopic(node, secure, token, channelId, topicId);
} }

View File

@ -157,7 +157,6 @@ export type TopicDetail = {
sealed: boolean; sealed: boolean;
dataType: string; dataType: string;
data: any; data: any;
assets: AssetItem[];
created: number; created: number;
updated: number; updated: number;
status: string; status: string;
@ -172,11 +171,8 @@ export type TopicItem = {
export type AssetItem = { export type AssetItem = {
assetId: string; assetId: string;
mimeType: string;
extension: string;
encrypted: boolean;
hosting: HostingMode; hosting: HostingMode;
split?: { blockId: string, blockIv: string }[]; split?: { partId: string, blockIv: string }[];
basic?: string; basic?: string;
inline?: string; inline?: string;
}; };

View File

@ -1,14 +1,14 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil'; import { checkResponse, fetchWithTimeout } from './fetchUtil';
import { TopicEntity } from '../entities'; 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 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 endpoint = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics?agent=${token}${params}`;
const topics = await fetchWithTimeout(endpoint, { method: 'GET' }); const topics = await fetchWithTimeout(endpoint, { method: 'GET' });
checkResponse(topics.status); checkResponse(topics.status);
return { return {
marker: parseInt(topics.headers.get('topic-marker')), marker: parseInt(topics.headers.get('topic-marker') || '0'),
revision: parseInt(topics.headers.get('topic-revision')), revision: parseInt(topics.headers.get('topic-revision') || '0'),
topics: await topics.json(), topics: await topics.json(),
} }
} }

View File

@ -7,8 +7,8 @@ export async function getContactChannelTopics(node: string, secure: boolean, gui
const topics = await fetchWithTimeout(endpoint, { method: 'GET' }); const topics = await fetchWithTimeout(endpoint, { method: 'GET' });
checkResponse(topics.status); checkResponse(topics.status);
return { return {
marker: parseInt(topics.headers.get('topic-marker')), marker: parseInt(topics.headers.get('topic-marker') || '0'),
revision: parseInt(topics.headers.get('topic-revision')), revision: parseInt(topics.headers.get('topic-revision') || '0'),
topics: await topics.json(), topics: await topics.json(),
} }
} }

View File

@ -1,7 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil'; import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function removeChannelTopic(node: string, secure: boolean, token: string, channelId: string, topicId: string): Promise<void> { export async function removeChannelTopic(node: string, secure: boolean, token: string, channelId: string, topicId: string): Promise<void> {
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' }); const { status } = await fetchWithTimeout(endpoint, { method: 'DELETE' });
checkResponse(status); checkResponse(status);
} }

View File

@ -1,6 +1,6 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil'; 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 endpoint = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}?contact=${guidToken}`;
const response = await fetchWithTimeout(endpoint, { method: 'DELETE' }); const response = await fetchWithTimeout(endpoint, { method: 'DELETE' });
checkResponse(response.status); checkResponse(response.status);

View File

@ -75,7 +75,7 @@ export interface Store {
setContentChannelUnsealedSummary(guid: string, channelId: string, data: string | null): Promise<void>; setContentChannelUnsealedSummary(guid: string, channelId: string, data: string | null): Promise<void>;
getContentChannelTopicRevision(guid: string, channelId: string): Promise<{ revision: number | null, marker: number | null }>; getContentChannelTopicRevision(guid: string, channelId: string): Promise<{ revision: number | null, marker: number | null }>;
setContentChannelTopicRevision(guid: string, channelId: string, sync: { revision: number, marker: number }): Promise<void>; setContentChannelTopicRevision(guid: string, channelId: string, sync: { revision: number | null, marker: number | null }): Promise<void>;
getContentChannelTopics(guid: string, channelId: string, count: number, position: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]>; 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<void>; addContentChannelTopic(guid: string, channelId: string, topicId: string, item: TopicItem): Promise<void>;
removeContentChannelTopic(guid: string, channelId: string, topicId: string): Promise<void>; removeContentChannelTopic(guid: string, channelId: string, topicId: string): Promise<void>;
@ -83,7 +83,7 @@ export interface Store {
setContentChannelTopicUnsealedDetail(guid: string, channelId: string, topicId: string, unsealedDetail: any): Promise<void>; setContentChannelTopicUnsealedDetail(guid: string, channelId: string, topicId: string, unsealedDetail: any): Promise<void>;
getContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string): Promise<{ revision: number | null, marker: number | null }>; 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<void>; setContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string, sync: { revision: number | null, marker: number | null }): Promise<void>;
getContactCardChannelTopics(guid: string, cardId: string, channelId: string, count: number, position: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]>; 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<void>; addContactCardChannelTopic(guid: string, cardId: string, channelId: string, topicId: string, item: TopicItem): Promise<void>;
removeContactCardChannelTopic(guid: string, cardId: string, channelId: string, topicId: string): Promise<void>; removeContactCardChannelTopic(guid: string, cardId: string, channelId: string, topicId: string): Promise<void>;
@ -499,7 +499,7 @@ export class OfflineStore implements Store {
public async getContentChannelTopicRevision(guid: string, channelId: string): Promise<{ revision: number | null, marker: number | null }> { 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 }); 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<void> { public async setContentChannelTopicRevision(guid: string, channelId: string, sync: { revision: number | null, marker: number | null }): Promise<void> {
await this.setTableValue(guid, 'channel', [{field: 'sync', value: JSON.stringify(sync)}], [{field: 'channel_id', value: channelId}]); 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 }[]> { 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 }> { 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 }); 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<void> { public async setContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string, sync: { revision: number | null, marker: number | null }): Promise<void> {
return await this.setTableValue(guid, 'card_channel', [{field: 'sync', value: JSON.stringify(sync)}], [{field: 'card_id', value: cardId}, {field: 'channel_id', value: channelId}]); 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 }[]> { 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 }> { public async getContentChannelTopicRevision(guid: string, channelId: string): Promise<{ revision: number | null, marker: number | null }> {
return { revision: null, marker: null }; return { revision: null, marker: null };
} }
public async setContentChannelTopicRevision(guid: string, channelId: string, sync: { revision: number, marker: number }): Promise<void> { public async setContentChannelTopicRevision(guid: string, channelId: string, sync: { revision: number | null, marker: number | null }): Promise<void> {
} }
public async getContentChannelTopics(guid: string, channelId: string, count: number, position: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]> { public async getContentChannelTopics(guid: string, channelId: string, count: number, position: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]> {
return []; 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 }> { public async getContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string): Promise<{ revision: number | null, marker: number | null }> {
return { revision: null, marker: null }; return { revision: null, marker: null };
} }
public async setContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string, sync: { revision: number, marker: number }): Promise<void> { public async setContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string, sync: { revision: number | null, marker: number | null }): Promise<void> {
} }
public async getContactCardChannelTopics(guid: string, cardId: string, channelId: string, count: number, position: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]> { public async getContactCardChannelTopics(guid: string, cardId: string, channelId: string, count: number, position: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]> {
return []; return [];
@ -912,7 +912,7 @@ export class NoStore implements Store {
public async getContentChannelTopicRevision(guid: string, channelId: string): Promise<{ revision: number | null, marker: number | null }> { public async getContentChannelTopicRevision(guid: string, channelId: string): Promise<{ revision: number | null, marker: number | null }> {
return { revision: null, marker: null }; return { revision: null, marker: null };
} }
public async setContentChannelTopicRevision(guid: string, channelId: string, sync: { revision: number, marker: number }): Promise<void> { public async setContentChannelTopicRevision(guid: string, channelId: string, sync: { revision: number | null, marker: number | null }): Promise<void> {
} }
public async getContentChannelTopics(guid: string, channelId: string, count: number, position: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]> { public async getContentChannelTopics(guid: string, channelId: string, count: number, position: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]> {
return []; 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 }> { public async getContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string): Promise<{ revision: number | null, marker: number | null }> {
return { revision: null, marker: null }; return { revision: null, marker: null };
} }
public async setContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string, sync: { revision: number, marker: number }): Promise<void> { public async setContactCardChannelTopicRevision(guid: string, cardId: string, channelId: string, sync: { revision: number | null, marker: number | null }): Promise<void> {
} }
public async getContactCardChannelTopics(guid: string, cardId: string, channelId: string, count: number, position: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]> { public async getContactCardChannelTopics(guid: string, cardId: string, channelId: string, count: number, position: { topicId: string, position: number } | null): Promise<{ topicId: string, item: TopicItem }[]> {
return []; return [];

View File

@ -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); this.focus = new FocusModule(this.log, this.store, this.crypto, this.media, null, channelId, this.guid, { node, secure, token }, channelKey, sealEnabled, revision);
} }
else { 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; return this.focus;
@ -518,9 +518,9 @@ export class StreamModule {
item.channelKey = await this.getChannelKey(seals); item.channelKey = await this.getChannelKey(seals);
if (this.focus) { if (this.focus) {
try { try {
await this.focus.setChannelKey(null, channelId, this.channelKey); await this.focus.setChannelKey(null, channelId, item.channelKey);
} catch (err) { } catch (err) {
log.warn(err); this.log.warn(err);
} }
} }
} }
@ -544,9 +544,9 @@ export class StreamModule {
item.channelKey = await this.getChannelKey(seals); item.channelKey = await this.getChannelKey(seals);
if (this.focus) { if (this.focus) {
try { try {
await this.focus.setChannelKey(null, channelId, this.channelKey); await this.focus.setChannelKey(null, channelId, item.channelKey);
} catch (err) { } catch (err) {
log.warn(err); this.log.warn(err);
} }
} }
} }

View File

@ -89,6 +89,7 @@ export type Topic = {
topicId: string; topicId: string;
guid: string; guid: string;
sealed: boolean; sealed: boolean;
blocked: boolean;
dataType: string; dataType: string;
data: any; data: any;
created: number; created: number;
@ -123,18 +124,21 @@ export enum TransformType {
LowQuality = 'low', // transcode to low quality LowQuality = 'low', // transcode to low quality
} }
export enum AssetType {
Image = 'image',
Video = 'video',
Audio = 'audio',
Binary = 'binary',
}
export type AssetSource = { export type AssetSource = {
name: string; type: AssetType;
type: string;
extension: string;
source: any; source: any;
transforms: {type: TransformType, appId: string, position?: number, thumb?: ()=>Promise<string>}[], transforms: {type: TransformType, appId: string, position?: number, thumb?: ()=>Promise<string>}[],
} }
export type Asset = { export type Asset = {
assetIndex: string; assetId: string;
mimeType: string;
extension: string;
hosting: HostingMode; hosting: HostingMode;
}; };