implementing focus module

This commit is contained in:
Roland Osborne 2024-12-03 17:14:25 -08:00
parent 5ae031f268
commit 2ddf3e0d94
7 changed files with 5340 additions and 7864 deletions

View File

@ -1,7 +1,7 @@
import { useState, useContext, useEffect, useRef } from 'react' import { useState, useContext, useEffect, useRef } from 'react'
import { AppContext } from '../context/AppContext' import { AppContext } from '../context/AppContext'
import { DisplayContext } from '../context/DisplayContext' import { DisplayContext } from '../context/DisplayContext'
import { Focus, Topic, AssetSource, HostingMode, TransformType } from 'databag-client-sdk' import { Focus, FocusDetail, Topic, AssetSource, HostingMode, TransformType } from 'databag-client-sdk'
import { ContextType } from '../context/ContextType' import { ContextType } from '../context/ContextType'
const img = const img =
@ -31,14 +31,14 @@ export function useConversation() {
const setTopics = (topics: Topic[]) => { const setTopics = (topics: Topic[]) => {
console.log(topics); console.log(topics);
} }
const setStatus = (status: string) => { const setDetail = (focused: { cardId: string | null, channelId: string, detail: FocusDetail | null }) => {
console.log(status); console.log(focused);
} }
focus.addTopicListener(setTopics); focus.addTopicListener(setTopics);
focus.addStatusListener(setStatus); focus.addDetailListener(setDetail);
return () => { return () => {
focus.removeTopicListener(setTopics); focus.removeTopicListener(setTopics);
focus.removeStatusListener(setStatus); focus.removeDetailListener(setDetail);
} }
} }
}, [app.state.focus]); }, [app.state.focus]);

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
import type { Channel, Topic, AssetSource, Asset, Tag, Article, Group, Card, Profile, Call, Config, NodeConfig, NodeAccount, Participant } from './types'; import type { Channel, Topic, AssetSource, Asset, Tag, Article, Group, Card, Profile, Call, FocusDetail, Config, NodeConfig, NodeAccount, Participant } from './types';
export interface Session { export interface Session {
getSettings(): Settings; getSettings(): Settings;
@ -138,8 +138,8 @@ export interface Focus {
addTopicListener(ev: (topics: Topic[]) => void): void; addTopicListener(ev: (topics: Topic[]) => void): void;
removeTopicListener(ev: (topics: Topic[]) => void): void; removeTopicListener(ev: (topics: Topic[]) => void): void;
addStatusListener(ev: (status: string) => void): void; addDetailListener(ev: (focused: { cardId: string | null, channelId: string, detail: FocusDetail | null }) => void): void;
removeStatusListener(ev: (status: string) => void): void; removeDetailListener(ev: (focused: { cardId: string | null, channelId: string, detail: FocusDetail | null }) => void): void;
} }
export interface Node { export interface Node {

View File

@ -2,7 +2,7 @@ import { EventEmitter } from 'eventemitter3';
import type { Contact, Focus } from './api'; import type { Contact, Focus } from './api';
import { Logging } from './logging'; import { Logging } from './logging';
import { FocusModule } from './focus'; import { FocusModule } from './focus';
import type { Card, Channel, Article, Topic, Asset, Tag, Profile, Participant } from './types'; import type { Card, Channel, Article, Topic, Asset, Tag, FocusDetail, Profile, Participant } from './types';
import { type CardEntity, avatar } from './entities'; import { type CardEntity, avatar } from './entities';
import type { ArticleDetail, ChannelSummary, ChannelDetail, CardProfile, CardDetail, CardItem, ChannelItem, ArticleItem } from './items'; import type { ArticleDetail, ChannelSummary, ChannelDetail, CardProfile, CardDetail, CardItem, ChannelItem, ArticleItem } from './items';
import { defaultCardItem, defaultChannelItem } from './items'; import { defaultCardItem, defaultChannelItem } from './items';
@ -504,6 +504,20 @@ export class ContactModule implements Contact {
}; };
entry.item.unsealedDetail = null; entry.item.unsealedDetail = null;
await this.unsealChannelDetail(cardId, id, entry.item); await this.unsealChannelDetail(cardId, id, entry.item);
if (this.focus) {
const { dataType, data, enableImage, enableAudio, enableVideo, enableBinary } = detail;
const sealed = dataType === 'sealed';
const focusDetail = {
sealed,
dataType,
data: sealed ? entry.item.unsealedDetail : data,
enableImage,
enableAudio,
enableVideo,
enableBinary,
}
this.focus.setDetail(cardId, id, focusDetail);
}
entry.channel = this.setChannel(cardId, id, entry.item); entry.channel = this.setChannel(cardId, id, entry.item);
await this.store.setContactCardChannelDetail(guid, cardId, id, entry.item.detail, entry.item.unsealedDetail); await this.store.setContactCardChannelDetail(guid, cardId, id, entry.item.detail, entry.item.unsealedDetail);
} }
@ -526,10 +540,10 @@ export class ContactModule implements Contact {
this.setChannelUnread(cardId, id); this.setChannelUnread(cardId, id);
entry.channel = this.setChannel(cardId, id, entry.item); entry.channel = this.setChannel(cardId, id, entry.item);
await this.store.setContactCardChannelSummary(guid, cardId, id, entry.item.summary, entry.item.unsealedSummary); await this.store.setContactCardChannelSummary(guid, cardId, id, entry.item.summary, entry.item.unsealedSummary);
}
if (this.focus) { if (this.focus) {
await this.focus.setRevision(cardId, id, topicRevision); await this.focus.setRevision(cardId, id, topicRevision);
} }
}
} else { } else {
const channels = this.getChannelEntries(cardId); const channels = this.getChannelEntries(cardId);
channels.delete(id); channels.delete(id);
@ -632,6 +646,7 @@ export class ContactModule implements Contact {
const channelsEntry = this.channelEntries.get(cardId); const channelsEntry = this.channelEntries.get(cardId);
const channelEntry = channelsEntry?.get(channelId); const channelEntry = channelsEntry?.get(channelId);
if (cardEntry && channelEntry) { if (cardEntry && channelEntry) {
// allocate focus
const node = cardEntry.item.profile.node; const node = cardEntry.item.profile.node;
const guid = cardEntry.item.profile.guid; const guid = cardEntry.item.profile.guid;
const token = cardEntry.item.detail.token; const token = cardEntry.item.detail.token;
@ -640,6 +655,20 @@ export class ContactModule implements Contact {
const sealEnabled = Boolean(this.seal); const sealEnabled = Boolean(this.seal);
const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(node); const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(node);
this.focus = new FocusModule(this.log, this.store, this.crypto, this.media, cardId, channelId, this.guid, { node, secure: !insecure, token: `${guid}.${token}` }, channelKey, sealEnabled, revision, markRead, flagTopic); this.focus = new FocusModule(this.log, this.store, this.crypto, this.media, cardId, channelId, this.guid, { node, secure: !insecure, token: `${guid}.${token}` }, channelKey, sealEnabled, revision, markRead, flagTopic);
// set current detail
const { dataType, data, enableImage, enableAudio, enableVideo, enableBinary } = channelEntry.item.detail;
const sealed = dataType === 'sealed';
const focusDetail = {
sealed,
dataType,
data: sealed ? channelEntry.item.unsealedDetail : data,
enableImage,
enableAudio,
enableVideo,
enableBinary,
}
this.focus.setDetail(cardId, channelId, focusDetail);
} else { } else {
this.focus = new FocusModule(this.log, this.store, this.crypto, this.media, cardId, channelId, this.guid, null, null, false, 0, markRead, flagTopic); this.focus = new FocusModule(this.log, this.store, this.crypto, this.media, cardId, channelId, this.guid, null, null, false, 0, markRead, flagTopic);
} }
@ -1022,6 +1051,19 @@ export class ContactModule implements Contact {
if (item.channelKey) { if (item.channelKey) {
const { data } = await this.crypto.aesDecrypt(subjectEncrypted, subjectIv, item.channelKey); const { data } = await this.crypto.aesDecrypt(subjectEncrypted, subjectIv, item.channelKey);
item.unsealedDetail = data; item.unsealedDetail = data;
if (this.focus) {
const { dataType, enableImage, enableAudio, enableVideo, enableBinary } = item.detail;
const focusDetail = {
sealed: true,
dataType,
data,
enableImage,
enableAudio,
enableVideo,
enableBinary,
}
this.focus.setDetail(cardId, channelId, focusDetail);
}
return true; return true;
} }
} catch (err) { } catch (err) {

View File

@ -2,7 +2,7 @@ import { EventEmitter } from 'eventemitter3';
import type { Focus } from './api'; import type { Focus } from './api';
import type { TopicItem, AssetItem, TopicDetail } 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, AssetType } from './types'; import { TransformType, HostingMode, AssetType, FocusDetail } 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';
@ -50,6 +50,7 @@ export class FocusModule implements Focus {
private unsealAll: boolean; private unsealAll: boolean;
private markRead: ()=>Promise<void>; private markRead: ()=>Promise<void>;
private flagChannelTopic: (id: string)=>Promise<void>; private flagChannelTopic: (id: string)=>Promise<void>;
private focusDetail: FocusDetail | null;
private markers: Set<string>; private markers: Set<string>;
@ -79,6 +80,7 @@ export class FocusModule implements Focus {
this.closing = false; this.closing = false;
this.closeMedia = []; this.closeMedia = [];
this.nextRevision = null; this.nextRevision = null;
this.focusDetail = null;
this.loadMore = false; this.loadMore = false;
this.unsealAll = false; this.unsealAll = false;
this.localComplete = false; this.localComplete = false;
@ -829,20 +831,36 @@ export class FocusModule implements Focus {
this.emitter.emit('topic', topics); this.emitter.emit('topic', topics);
} }
public addStatusListener(ev: (status: string) => void) { public addDetailListener(ev: (focused: { cardId: string | null, channelId: string, detail: FocusDetail | null }) => void) {
this.emitter.on('status', ev); const { cardId, channelId } = this;
const status = this.connection ? 'connected' : 'disconnected' const access = Boolean(this.connection && (!this.focusDetail?.sealed || (this.sealEnabled && this.channelKey)))
ev(status); const detail = access ? this.focusDetail : null;
this.emitter.on('detail', ev);
ev({ cardId, channelId, detail });
} }
public removeStatusListener(ev: (status: string) => void) { public removeDetailListener(ev: (focused: { cardId: string | null, channelId: string, detail: FocusDetail | null }) => void) {
this.emitter.off('status', ev); this.emitter.off('detail', ev);
}
private emitDetail() {
const { cardId, channelId } = this;
const access = Boolean(this.connection && (!this.focusDetail?.sealed || (this.sealEnabled && this.channelKey)))
const detail = access ? this.focusDetail : null;
this.emitter.emit('detail', { cardId, channelId, detail });
} }
public disconnect(cardId: string | null, channelId: string) { public disconnect(cardId: string | null, channelId: string) {
if (cardId === this.cardId && channelId === this.channelId) { if (cardId === this.cardId && channelId === this.channelId) {
this.connection = null; this.connection = null;
this.emitStatus(); this.emitDetail();
}
}
public setDetail(cardId: string | null, channelId: string, detail: FocusDetail) {
if (cardId === this.cardId && channelId === this.channelId) {
this.focusDetail = detail;
this.emitDetail();
} }
} }
@ -856,6 +874,7 @@ export class FocusModule implements Focus {
public async setSealEnabled(enable: boolean) { public async setSealEnabled(enable: boolean) {
this.sealEnabled = enable; this.sealEnabled = enable;
this.unsealAll = true; this.unsealAll = true;
this.emitDetail();
await this.sync(); await this.sync();
} }
@ -863,6 +882,7 @@ export class FocusModule implements Focus {
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;
this.emitDetail();
await this.sync(); await this.sync();
} }
} }
@ -877,11 +897,6 @@ export class FocusModule implements Focus {
}); });
} }
private emitStatus() {
const status = this.connection ? 'connected' : 'disconnected'
this.emitter.emit('status', status);
}
private getTopicData(item: TopicItem): { data: any, assets: AssetItem[] } { private getTopicData(item: TopicItem): { data: any, assets: AssetItem[] } {
const topicDetail = item.detail.sealed ? item.unsealedDetail : item.detail.data; const topicDetail = item.detail.sealed ? item.unsealedDetail : item.detail.data;
const { revision } = item.detail; const { revision } = item.detail;

View File

@ -141,6 +141,20 @@ export class StreamModule {
entry.item.unsealedDetail = null; entry.item.unsealedDetail = null;
await this.unsealChannelDetail(id, entry.item); await this.unsealChannelDetail(id, entry.item);
entry.channel = this.setChannel(id, entry.item); entry.channel = this.setChannel(id, entry.item);
if (this.focus) {
const { dataType, data, enableImage, enableAudio, enableVideo, enableBinary } = detail;
const sealed = dataType === 'sealed';
const focusDetail = {
sealed,
dataType,
data: sealed ? entry.item.unsealedDetail : data,
enableImage,
enableAudio,
enableVideo,
enableBinary,
}
this.focus.setDetail(null, id, focusDetail);
}
await this.store.setContentChannelDetail(guid, id, entry.item.detail, entry.item.unsealedDetail); await this.store.setContentChannelDetail(guid, id, entry.item.detail, entry.item.unsealedDetail);
} }
@ -375,8 +389,6 @@ export class StreamModule {
focus.close(); focus.close();
} }
const entry = this.channelEntries.get(channelId);
const markRead = async () => { const markRead = async () => {
try { try {
await this.setUnreadChannel(channelId, false); await this.setUnreadChannel(channelId, false);
@ -390,14 +402,25 @@ export class StreamModule {
await addFlag(node, secure, guid, { channelId, topicId }); await addFlag(node, secure, guid, { channelId, topicId });
} }
if (entry) { const entry = this.channelEntries.get(channelId);
const channelKey = entry.item.channelKey; const channelKey = entry ? entry.item.channelKey : null;
const revision = entry ? entry.item.summary.revision : 0;
const sealEnabled = Boolean(this.seal); const sealEnabled = Boolean(this.seal);
const revision = entry.item.summary.revision;
this.focus = new FocusModule(this.log, this.store, this.crypto, this.media, null, channelId, this.guid, { node, secure, token }, channelKey, sealEnabled, revision, markRead, flagTopic); this.focus = new FocusModule(this.log, this.store, this.crypto, this.media, null, channelId, this.guid, { node, secure, token }, channelKey, sealEnabled, revision, markRead, flagTopic);
if (entry) {
const { dataType, data, enableImage, enableAudio, enableVideo, enableBinary } = entry.item.detail;
const sealed = dataType === 'sealed';
const focusDetail = {
sealed,
dataType,
data: sealed ? entry.item.unsealedDetail : data,
enableImage,
enableAudio,
enableVideo,
enableBinary,
} }
else { this.focus.setDetail(null, channelId, focusDetail);
this.focus = new FocusModule(this.log, this.store, this.crypto, this.media, null, channelId, this.guid, null, null, false, 0, markRead, flagTopic);
} }
return this.focus; return this.focus;
@ -542,6 +565,19 @@ export class StreamModule {
if (item.channelKey) { if (item.channelKey) {
const { data } = await this.crypto.aesDecrypt(subjectEncrypted, subjectIv, item.channelKey); const { data } = await this.crypto.aesDecrypt(subjectEncrypted, subjectIv, item.channelKey);
item.unsealedDetail = data; item.unsealedDetail = data;
if (this.focus) {
const { dataType, enableImage, enableAudio, enableVideo, enableBinary } = item.detail;
const focusDetail = {
sealed: true,
dataType,
data,
enableImage,
enableAudio,
enableVideo,
enableBinary,
}
this.focus.setDetail(null, channelId, focusDetail);
}
return true; return true;
} }
} catch (err) { } catch (err) {

View File

@ -65,6 +65,16 @@ export type Channel = {
members: Member[]; members: Member[];
}; };
export type FocusDetail = {
sealed: boolean;
dataType: string;
data: any;
enableImage: boolean;
enableAudio: boolean;
enableVideo: boolean;
enableBinary: boolean;
}
export type Member = { export type Member = {
guid: string; guid: string;
//pushEnabled: boolean; //pushEnabled: boolean;