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