implemented channel management methods

This commit is contained in:
balzack 2024-10-30 14:32:31 -07:00
parent 3e16002b16
commit a733a2246f
11 changed files with 214 additions and 20 deletions

View File

@ -750,7 +750,11 @@ export class ContactModule implements Contact {
public async setBlockedCard(cardId: string, blocked: boolean): Promise<void> {
const entry = this.cardEntries.get(cardId);
if (entry) {
await this.setCardBlocked(cardId);
if (blocked) {
await this.setCardBlocked(cardId);
} else {
await this.clearCardBlocked(cardId);
}
entry.card = this.setCard(cardId, entry.item);
this.emitCards();
}
@ -761,7 +765,11 @@ export class ContactModule implements Contact {
if (entries) {
const entry = entries.get(channelId);
if (entry) {
await this.setChannelBlocked(cardId, channelId);
if (blocked) {
await this.setChannelBlocked(cardId, channelId);
} else {
await this.clearChannelBlocked(cardId, channelId);
}
entry.channel = this.setChannel(cardId, channelId, entry.item);
this.emitChannels(cardId);
}
@ -773,7 +781,11 @@ export class ContactModule implements Contact {
if (entries) {
const entry = entries.get(articleId);
if (entry) {
await this.setArticleBlocked(cardId, articleId);
if (blocked) {
await this.setArticleBlocked(cardId, articleId);
} else {
await this.clearArticleBlocked(cardId, articleId);
}
entry.article = this.setArticle(cardId, articleId, entry.item);
this.emitArticles(cardId);
}
@ -937,6 +949,23 @@ export class ContactModule implements Contact {
await this.sync();
}
public async getSeal(cardId: string, keyData: string): Promise<{ publicKey: string, sealedKey: string }> {
if (!this.crypto) {
throw new Error('crypto not set');
}
const card = this.cardEntries.get(cardId);
if (!card) {
throw new Error('specified card not found');
}
const publicKey = card.item.profile.seal;
if (!publicKey) {
throw new Error('seal key not set for card');
}
const sealed = await this.crypto.rsaEncrypt(keyData, publicKey);
const sealedKey = sealed.encryptedDataB64;
return { publicKey, sealedKey };
}
private async getChannelKey(seals: [{ publicKey: string; sealedKey: string }]): Promise<string | null> {
const seal = seals.find(({ publicKey }) => this.seal && publicKey === this.seal.publicKey);
if (seal && this.crypto && this.seal) {

View File

@ -3,20 +3,37 @@ import type { Channel } from './types';
import { ContactModule } from './contact';
import { StreamModule } from './stream';
import { Logging } from './logging';
import { Crypto } from './crypto';
export class ContentModule implements Content {
private crypto: Crypto | null;
private log: Logging;
private contact: ContactModule;
private stream: StreamModule;
constructor(log: Logging, contact: ContactModule, stream: StreamModule) {
constructor(log: Logging, crypto: Crypto | null, contact: ContactModule, stream: StreamModule) {
this.contact = contact;
this.stream = stream;
this.log = log;
this.crypto = crypto;
}
public async addChannel(sealed: boolean, type: string, subject: string, cardIds: string[]): Promise<string> {
return await this.stream.addChannel(sealed, type, subject, cardIds);
if (sealed) {
if (!this.crypto) {
throw new Error('crypto not set');
}
const { aesKeyHex } = await this.crypto.aesKey();
const seals = [];
for (let cardId of cardIds) {
const seal = await this.contact.getSeal(cardId, aesKeyHex);
seals.push(seal);
}
return await this.stream.addSealedChannel(type, subject, cardIds, aesKeyHex, seals);
}
else {
return await this.stream.addUnsealedChannel(type, subject, cardIds);
}
}
public async removeChannel(channelId: string): Promise<void> {

View File

@ -0,0 +1,12 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function addChannel(node: string, secure: boolean, token: string, type: string, data: any, cards: string[]) {
const params = { dataType: type, data: JSON.stringify(data), groups: [], cards };
const endpoint = `http${secure ? 's' : ''}://${node}/content/channels?agent=${token}`;
const channel = await fetchWithTimeout(endpoint, {
method: 'POST',
body: JSON.stringify(params),
});
checkResponse(channel.status);
return await channel.json();
}

View File

@ -0,0 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function clearChannelCard(node: string, secure: boolean, token: string, channelId: string, cardId: string): Promise<void> {
const endpoint = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/cards/${cardId}?agent=${token}`;
const { status } = await fetchWithTimeout(endpoint, { method: 'DELETE' });
checkResponse(status);
}

View File

@ -0,0 +1,9 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function getChannelNotifications(node: string, secure: boolean, token: string, channelId: string) {
const endpoint = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/notification?agent=${token}`;
const notify = await fetchWithTimeout(endpoint, { method: 'GET' });
checkResponse(notify.status);
return await notify.json();
}

View File

@ -0,0 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function removeChannel(node: string, secure: boolean, token: string, channelId: string): Promise<void> {
const endpoint = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}?agent=${token}`;
const { status } = await fetchWithTimeout(endpoint, { method: 'DELETE' });
checkResponse(status);
}

View File

@ -0,0 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function setChannelCard(node: string, secure: boolean, token: string, channelId: string, cardId: string): Promise<void> {
const endpoint = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/cards/${cardId}?agent=${token}`;
const { status } = await fetchWithTimeout(endpoint, { method: 'PUT' });
checkResponse(status);
}

View File

@ -0,0 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function setChannelNotifications(node: string, secure: boolean, token: string, channelId: string, flag: boolean) {
const endpoint = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/notification?agent=${token}`;
const { status } = await fetchWithTimeout(endpoint, { method: 'PUT', body: JSON.stringify(notify) });
checkResponse(status);
}

View File

@ -0,0 +1,12 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function setChannelSubject(node: string, secure: boolean, token: string, channelId: string, type: string, data: any): Promise<void> {
const params = { dataType: type, data: JSON.stringify(data) };
const endpoint = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/subject?agent=${token}`;
const { status } = await fetchWithTimeout(endpoint, {
method: 'PUT',
body: JSON.stringify(data),
});
checkResponse(status);
}

View File

@ -67,7 +67,7 @@ console.log(">>> ", channelTypes);
this.alias = new AliasModule(log, this.settings, this.store, guid, token, node, secure);
this.attribute = new AttributeModule(log, this.settings, this.store, guid, token, node, secure);
this.stream = new StreamModule(log, this.store, this.crypto, guid, token, node, secure, channelTypes);
this.content = new ContentModule(log, this.contact, this.stream);
this.content = new ContentModule(log, this.crypto, this.contact, this.stream);
this.ring = new RingModule(log);
this.connection = new Connection(log, token, node, secure);

View File

@ -6,10 +6,18 @@ import type { ChannelItem } from './items';
import type { Channel, Topic, Asset, Tag, Participant } from './types';
import { Store } from './store';
import { Crypto } from './crypto';
import { addChannel } from './net/addChannel';
import { removeChannel } from './net/removeChannel';
import { getChannels } from './net/getChannels';
import { getChannelDetail } from './net/getChannelDetail';
import { getChannelSummary } from './net/getChannelSummary';
import { defaultChannelItem } from './items';
import { setChannelSubject } from './net/setChannelSubject';
import { setChannelCard } from './net/setChannelCard';
import { clearChannelCard } from './net/clearChannelCard';
import { getChannelNotifications } from './net/getChannelNotifications';
import { setChannelNotifications } from './net/setChannelNotifications';
import { addFlag } from './net/addFlag';
const CLOSE_POLL_MS = 100;
const RETRY_POLL_MS = 2000;
@ -193,7 +201,6 @@ export class StreamModule {
public addChannelListener(ev: (arg: { channels: Channel[], cardId: string | null }) => void): void {
this.emitter.on('channel', ev);
const channels = Array.from(this.channelEntries, ([channelId, entry]) => entry.channel);
console.log("EMIT ON ADD", channels.length);
ev({ channels, cardId: null });
}
@ -203,7 +210,6 @@ console.log("EMIT ON ADD", channels.length);
private emitChannels() {
const channels = Array.from(this.channelEntries, ([channelId, entry]) => entry.channel);
console.log("EMIT", channels.length);
this.emitter.emit('channel', { channels, cardId: null });
}
@ -219,33 +225,114 @@ console.log("EMIT", channels.length);
await this.sync();
}
public async addChannel(sealed: boolean, type: string, subject: string, cardIds: string[]): Promise<string> {
return '';
public async addSealedChannel(type: string, subject: string, cardIds: string[], aesKeyHex: string, seals: { publicKey: string, sealedKey: string}[]): Promise<string> {
const { node, secure, token, crypto, seal } = this;
if (!crypto) {
throw new Error('crypto not set');
}
if (!seal) {
throw new Error('seal not set');
}
const sealKey = await this.crypto.rsaEncrypt(aesKeyHex, seal.publicKey);
const { ivHex } = await crypto.aesIv();
const { encryptedDataB64 } = await crypto.aesEncrypt(subject, ivHex, aesKeyHex);
const sealedSubject = { subjectEncrypted: encryptedDataB64, subjectIv: ivHex, seals: [ ...seals, sealKey ] };
return await addChannel(node, secure, token, type, sealedSubject, cardIds);
}
public async removeChannel(channelId: string): Promise<void> { }
public async addUnsealedChannel(type: string, subject: string, cardIds: string[]): Promise<string> {
const { node, secure, token } = this;
return await addChannel(node, secure, token, type, { subject }, cardIds);
}
public async setChannelSubject(channelId: string, subject: string): Promise<void> { }
public async removeChannel(channelId: string): Promise<void> {
const { node, secure, token } = this;
return await removeChannel(node, secure, token);
}
public async setChannelCard(channelId: string, cardId: string): Promise<void> { }
public async setChannelSubject(channelId: string, subject: string): Promise<void> {
const channel = this.channelEntries.get(channelId);
if (!channel) {
throw new Error('channel not found');
}
const { item } = channel;
const { node, secure, token, crypto, seal } = this;
if (item.sealed) {
if (!crypto) {
throw new Error('crypto not set');
}
if (!seal) {
throw new Error('seal not set');
}
const { subjectIv, seals } = JSON.parse(item.detail.data);
if (!item.channelKey) {
item.channelKey = await this.getChannelKey(seals);
}
const { encryptedDataB64 } = await crypto.aesEncrypt(subject, subjectIv, item.channelKey);
const sealedSubject = { subjectEncrypted, encryptedDataB64, subjectIv, seals };
await setChannelSubject(node, secure, token, channelId, item.dataType, sealedSubject);
}
else {
await setChannelSubject(node, secure, token, channelId, item.dataType, { subject });
}
}
public async clearChannelCard(channelId: string, cardId: string): Promise<void> { }
public async setChannelCard(channelId: string, cardId: string): Promise<void> {
const { node, secure, token } = this;
await setChannelCard(node, secure, token, channelId, cardId);
}
public async setBlockedChannel(channelId: string, blocked: boolean): Promise<void> { }
public async clearChannelCard(channelId: string, cardId: string): Promise<void> {
const { node, secure, token } = this;
await clearChannelCard(node, secure, token, channelid, cardId);
}
public async setBlockedChannel(channelId: string, blocked: boolean): Promise<void> {
const entry = this.channelEntries.get(channelId);
if (entry) {
if (blocked) {
await this.setChannelBlocked(channelId);
} else {
await this.clearChannelBlocked(channelId);
}
entry.card = this.setChannel(cardId, entry.item);
this.emitChannels();
}
}
public async getBlockedChannels(): Promise<Channel[]> {
return [];
return Array.from(this.channelEntries.entries())
.filter(([key, value]) => this.isChannelBlocked(key))
.map(([key, value]) => value.card);
}
public async flagChannel(channelId: string): Promise<void> { }
public async flagChannel(channelId: string): Promise<void> {
const { node, secure, guid } = this;
await addFlag(node, secure, guid, { channelId });
}
public async getChannelNotifications(channelId: string): Promise<boolean> {
return false;
const { node, secure, token } = this;
await getChannelNotifications(node, secure, token, channelId);
}
public async setChannelNotifications(channelId: string, enabled: boolean): Promise<void> { }
public async setChannelNotifications(channelId: string, enabled: boolean): Promise<void> {
const { node, secure, token } = this;
await setChannelNotifications(node, secure, token, channelId, enabled);
}
public async setUnreadChannel(channelId: string, unread: boolean): Promise<void> { }
public async setUnreadChannel(channelId: string, unread: boolean): Promise<void> {
const entry = this.channelEntries.get(channelId);
if (entry) {
if (unread) {
await this.setChannelUnread(channelId);
} else {
await this.clearChannelUnread(channelId);
}
entry.card = this.setChannel(cardId, entry.item);
this.emitChannels();
}
}
public async setFocus(channelId: string): Promise<Focus> {
const { node, secure, token, focus } = this;