mirror of
https://github.com/balzack/databag.git
synced 2025-05-04 15:35:16 +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 () => {
|
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);
|
||||||
|
@ -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 }) => {
|
||||||
|
@ -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 {
|
||||||
|
@ -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 = {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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 [];
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user