implementing contact module

This commit is contained in:
balzack 2024-09-30 23:53:19 -07:00
parent 646deacb8d
commit b582d99475
4 changed files with 145 additions and 59 deletions

View File

@ -21,13 +21,13 @@ export class ContactModule implements Contact {
private closing: boolean; private closing: boolean;
// view of cards // view of cards
private cardEntries: Map<string, { revision: CardRevision, profile: CardProfile, detail: CardDetails, card: Card }> private cardEntries: Map<string, { item: CardItem, card: Card }>
// view of articles // view of articles
private articleEntries: Map<string, Map<string, { revision: ArticleRevision, detail: ArticleDetail, article: Article }>> private articleEntries: Map<string, Map<string, { item: ArticleItem, article: Article }>>
// view of channels // view of channels
private channelEntries: Map<string, Map<string, { revision ChannelRevision, summary: ChannelSummary, detail: ChannelDetail, channel: Channel }>>; private channelEntries: Map<string, Map<string, { item: ChannelItem, channel: Channel }>>;
constructor(log: Logging, store: Store, guid: string, token: string, node: string, secure: boolean) { constructor(log: Logging, store: Store, guid: string, token: string, node: string, secure: boolean) {
this.guid = guid; this.guid = guid;
@ -37,6 +37,11 @@ export class ContactModule implements Contact {
this.log = log; this.log = log;
this.emitter = new EventEmitter(); this.emitter = new EventEmitter();
this.cardGuid = new Map<string, string>();
this.cardEntries = new Map<string, { item: CardItem, card: Card }>();
this.articleEntries = new Map<string, Map<string, { item: ArticleItem, article: Article }>>;
this.channelEntries = new Map<string, Map<string, { item: ChannelItem, channel: Channel }>>;
this.revision = 0; this.revision = 0;
this.sycning = true; this.sycning = true;
this.closing = false; this.closing = false;
@ -44,7 +49,90 @@ export class ContactModule implements Contact {
this.init(); this.init();
} }
private setCard(cardId: string, item: CardItem): Card {
const { offsync, blocked, profile, detail } = item;
const { guid, handle, name, description, location, imageSet, node } = profile;
const { status, statusUpdated } = detail;
return { cardId, offsync, blocked, status, statusUpdated, guid, handle, name, description, location, imageSet, node };
}
private setArticle(cardId: string, articleId: string, item: ArticleItem): Article {
const { unread, blocked, detail, unsealedData } = item;
const { dataType, sealed, data, created, updated } = detail;
const articleData = sealed ? unsealedData : data;
return { cardId, articleId, dataType, articleData, created, updated, status };
}
private setChannel(cardId: string, channelId: string, item: ChannelItem): Channel {
const { blocked, summary, detail, unsealedChannelData, unsealedTopicData } = item;
const { enableImage, enableAudio, enableVideo, enableBinary, members } = detail;
const channelData = detail.sealed ? unsealedChannelData : detail.data;
const { guid, status, transform } = summary;
const topicData = summary.sealed ? unsealedTopicData : summary.data;
const { pushEnabled } = members.find(({ member }) => (member === this.guid)) | {}
const contacts = members.filter(({ member }) => (member != this.guid)).map(({ member, pushEnabled }) => { guid: member, pushEnabled });
return {
channelId,
cardId,
lastTopic: {
guid,
sealed: summary.sealed,
dataType: summary.dataType,
data: topicData,
created: summary.created,
updated: summary.updated,
status,
transform,
},
unread,
sealed: channelSealed,
dataType: detail.dataType,
data: channelData,
created: detail.created,
updated: detail.updated,
enableImage,
enableAudio,
enableVideo,
enableBinary,
membership: { pushEnabled },
members: contacts,
};
}
private async init() { private async init() {
this.revision = await this.store.getContactRevision(this.guid);
// load map of cards
const cards = await this.store.getContacts(this.guid);
cards.forEach(({ cardId, item }) => {
const card = setCard(cardId, item);
this.cardEntries.set(cardId, { item, card });
})
// load map of articles
const articles = await this.store.getContactCardArticles(this.guid);
articles.forEach(({ cardId, articleId, item }) => {
if (!this.articleEntries.has(cardId)) {
this.articleEntries.set(cardId, new Map<string, Map<string, { item: ArticleItem, article: Article }>>);
}
const article = setArticle(cardId, articleId, item);
this.articleEntries.set(cardId).set(articleId, { item, article });
});
// load map of channels
const channels = await this.store.getContactCardChannles(this.guid);
channels.forEach(({ cardId, channelId, item }) => {
if (!this.channelEntries.has(cardId)) {
this.channelEntries.set(cardId, new Map<string, Map<string, { item: ChannelItem, channel: Channel }>>);
}
const channel = setChannel(cardId, channelId, item);
this.channelEntries.set(cardId).set(channelId, { item, channel });
});
this.syncing = false;
await this.sync();
} }
private async sync(): Promise<void> { private async sync(): Promise<void> {
@ -52,7 +140,7 @@ export class ContactModule implements Contact {
public addCardListener(ev: (cards: Card[]) => void): void { public addCardListener(ev: (cards: Card[]) => void): void {
this.emitter.on('card', ev); this.emitter.on('card', ev);
const cards = Array.from(cardEntries, ([cardId, entry]) => (entry.card)); const cards = Array.from(this.cardEntries, ([cardId, entry]) => (entry.card));
ev(cards); ev(cards);
} }
@ -60,7 +148,8 @@ export class ContactModule implements Contact {
this.emitter.off('card', ev); this.emitter.off('card', ev);
} }
private emitCards(cards: Card[]) { private emitCards() {
const cards = Array.from(this.cardEntries, ([cardId, entry]) => (entry.card));
this.emitter.emit('card', cards); this.emitter.emit('card', cards);
} }
@ -69,10 +158,8 @@ export class ContactModule implements Contact {
const cardId = id as string; const cardId = id as string;
this.emitter.on(`article::${cardId}`, ev); this.emitter.on(`article::${cardId}`, ev);
const entries = this.articleEntries.get(cardId); const entries = this.articleEntries.get(cardId);
if (entries) { const articles = entries ? Array.from(entries, ([articleId, entry]) => (entry.article)) : [];
const articles = Array.from(entries, ([articleId, entry]) => (entry.article)); ev({ cardId, articles });
ev({ cardId, articles });
}
} else { } else {
this.emitter.on('article', ev); this.emitter.on('article', ev);
this.articleEntries.forEach((entries, cardId) => { this.articleEntries.forEach((entries, cardId) => {
@ -91,21 +178,20 @@ export class ContactModule implements Contact {
} }
} }
private emitArticles(cardId: string, articles: Article[]) { private emitArticles(cardId: string) {
const entries = this.articleEntries.get(cardId);
const articles = entries ? Array.from(entries, ([articleId, entry]) => (entry.article)) : [];
this.emitter.emit('article', { cardId, articles }); this.emitter.emit('article', { cardId, articles });
this.emitter.emit(`article::${cardId}`, { cardId, articles }); this.emitter.emit(`article::${cardId}`, { cardId, articles });
} }
public addChannelListener(id: string | null, ev: (arg: { cardId: string, channels: Channel[] }) => void): void { public addChannelListener(id: string | null, ev: (arg: { cardId: string, channels: Channel[] }) => void): void {
if (id) { if (id) {
const cardId = id as string; const cardId = id as string;
this.emitter.on(`channel::${cardId}`, ev); this.emitter.on(`channel::${cardId}`, ev);
const entries = this.channelEntries.get(cardId); const entries = this.channelEntries.get(cardId);
if (entries) { const channels = entries ? Array.from(entries, ([channelId, entry]) => (entry.channel)) : [];
const channels = Array.from(entries, ([channelId, entry]) => (entry.channel)); ev({ cardId, channels });
ev({ cardId, channels });
}
} else { } else {
this.emitter.on('channel', ev); this.emitter.on('channel', ev);
this.channelEntries.forEach((entries, cardId) => { this.channelEntries.forEach((entries, cardId) => {
@ -124,28 +210,29 @@ export class ContactModule implements Contact {
} }
} }
private emitChannels(cardId: string, channels: Channel[]) { private emitChannels(cardId: string) {
const entries = this.channelEntries.get(cardId);
const channels = entries ? Array.from(entries, ([channelId, entry]) => (entry.channel)) : [];
this.emitter.emit('channel', { cardId, channels }); this.emitter.emit('channel', { cardId, channels });
this.emitter.emit(`channel::${cardId}`, { cardId, channels }); this.emitter.emit(`channel::${cardId}`, { cardId, channels });
} }
public addTopicRevisionListener(cardId: string, channelId: string, ev: (arg: { cardId: string, channelId: string, revision: number }) => void): void { public addTopicRevisionListener(cardId: string, channelId: string, ev: (arg: { cardId: string, channelId: string, revision: number }) => void): void {
this.emitter.on(`revision::${cardId}::${channelId}`, ev); this.emitter.on(`revision::${cardId}::${channelId}`, ev);
const card = this.channelEntries.get(cardId); const entries = this.channelEntries.get(cardId);
if (card) { const entry = entries ? entries.get(channelId) : null;
const channel = card.get(channelId); const revision = entry ? entry.revision.topic : 0;
if (channel) { ev({ cardId, channelId, revision });
const revision = channel.revision.topic;
ev({ cardId, channelId, revision });
}
}
} }
public removeTopicRevisionListener(cardId: string, channelId: string, ev: (arg: { cardId: string, channelId: string, revision: number }) => void): void { public removeTopicRevisionListener(cardId: string, channelId: string, ev: (arg: { cardId: string, channelId: string, revision: number }) => void): void {
this.emitter.off(`revision::${cardId}::${channelId}`, ev); this.emitter.off(`revision::${cardId}::${channelId}`, ev);
} }
public emitTopicRevision(cardId: string, channelId: string, revision: number) { public emitTopicRevision(cardId: string, channelId: string) {
const entries = this.channelEntries.get(cardId);
const entry = entries ? entries.get(channelId) : null;
const revision = entry ? entry.revision.topic : 0;
this.emitter.emit(`revision::${cardId}::${channelId}`, revision); this.emitter.emit(`revision::${cardId}::${channelId}`, revision);
} }

View File

@ -43,6 +43,7 @@ export type ChannelSummary = {
export type ChannelDetail = { export type ChannelDetail = {
revision: number, revision: number,
sealed: boolean,
dataType: string, dataType: string,
data: string, data: string,
created: number, created: number,
@ -66,6 +67,7 @@ export type ArticleRevision = {
} }
export type ArticleDetail = { export type ArticleDetail = {
sealed: boolean,
dataType: string, dataType: string,
data: string, data: string,
created: number, created: number,
@ -78,7 +80,6 @@ export type ArticleDetail = {
} }
export type CardItem = { export type CardItem = {
cardId: string,
offsync: boolean, offsync: boolean,
blocked: boolean, blocked: boolean,
revision: CardRevision, revision: CardRevision,
@ -89,8 +90,6 @@ export type CardItem = {
} }
export type ArticleItem = { export type ArticleItem = {
cardId: string | null,
articleId: string,
blocked: boolean, blocked: boolean,
detail: ArticleDetail, detail: ArticleDetail,
unsealedData: any, unsealedData: any,
@ -98,11 +97,11 @@ export type ArticleItem = {
} }
export type ChannelItem = { export type ChannelItem = {
cardId: string | null, unread: boolean,
channelId: string,
blocked: boolean, blocked: boolean,
summary: ChannelSummary, summary: ChannelSummary,
detail: ChannelDetail, detail: ChannelDetail,
unsealedData: any, unsealedChannelData: any,
unsealedTopicData: any,
revision: ChannelRevision, revision: ChannelRevision,
} }

View File

@ -24,7 +24,7 @@ export interface Store {
getContactRevision(guid: string): Promise<number>; getContactRevision(guid: string): Promise<number>;
setContactRevision(guid: string, revision: number): Promise<void>; setContactRevision(guid: string, revision: number): Promise<void>;
getContacts(guid: string): Promise<CardItem[]>; getContacts(guid: string): Promise<{ cardId: string, item: CardItem }[]>;
setContactCardRevision(guid: string, cardId: string, revision: CardRevision): Promise<void>; setContactCardRevision(guid: string, cardId: string, revision: CardRevision): Promise<void>;
setContactCardProfile(guid: string, cardId: string, profile: CardProfile): Promise<void>; setContactCardProfile(guid: string, cardId: string, profile: CardProfile): Promise<void>;
setContactCardDetail(guid: string, cardId: string, detail: CardDetail): Promise<void>; setContactCardDetail(guid: string, cardId: string, detail: CardDetail): Promise<void>;
@ -34,12 +34,12 @@ export interface Store {
setContactCardSyncRevision(guid: string, cardId: string, notification: CardNotification): Promise<void>; setContactCardSyncRevision(guid: string, cardId: string, notification: CardNotification): Promise<void>;
setContactCardOffsync(guid: string, cardId: string, offsync: boolean): Promise<void>; setContactCardOffsync(guid: string, cardId: string, offsync: boolean): Promise<void>;
getContactCardArticles(guid: string): Promise<ArticleItem[]>; getContactCardArticles(guid: string): Promise<{ cardId: string, articleId: string, item: ArticleItem }[]>;
setContactCardArticleRevision(guid: string, cardId: string, articleId: string, revision: ArticleRevision): Promise<void>; setContactCardArticleRevision(guid: string, cardId: string, articleId: string, revision: ArticleRevision): Promise<void>;
setContactCardArticleDetail(guid: string, cardId: string, articleId: string, detail: ChannelDetail, unsealedData: any): Promise<void>; setContactCardArticleDetail(guid: string, cardId: string, articleId: string, detail: ChannelDetail, unsealedData: any): Promise<void>;
setContactCardArticleUnsealed(guid: string, cardId: string, articleId: string, unsealedData: any): Promise<void>; setContactCardArticleUnsealed(guid: string, cardId: string, articleId: string, unsealedData: any): Promise<void>;
getContactCardChannels(guid: string): Promise<ChannelItem[]>; getContactCardChannels(guid: string): Promise<{ cardId: string, channelId: string, item: ChannelItem }[]>;
setContactCardChannelBlocked(guid: string, cardId: string, channelId: string, blocked: boolean): Promise<void>; setContactCardChannelBlocked(guid: string, cardId: string, channelId: string, blocked: boolean): Promise<void>;
setContactCardChannelRevision(guid: string, cardId: string, channelId: string, revision: ChannelRevision): Promise<void>; setContactCardChannelRevision(guid: string, cardId: string, channelId: string, revision: ChannelRevision): Promise<void>;
setContactCardChannelSummary(guid: string, cardId: string, channelId: string, summary: ChannelSummary): Promise<void>; setContactCardChannelSummary(guid: string, cardId: string, channelId: string, summary: ChannelSummary): Promise<void>;
@ -166,7 +166,7 @@ export class OfflineStore implements Store {
public async setContactRevision(guid: string, revision: number): Promise<void> { public async setContactRevision(guid: string, revision: number): Promise<void> {
} }
public async getContacts(guid: string): Promise<CardItem[]> { public async getContacts(guid: string): Promise<{ cardId: string, item: CardItem }[]> {
return []; return [];
} }
@ -191,7 +191,7 @@ export class OfflineStore implements Store {
public async setContactCardOffsync(guid: string, cardId: string, offsync: boolean): Promise<void> { public async setContactCardOffsync(guid: string, cardId: string, offsync: boolean): Promise<void> {
} }
public async getContactCardArticles(guid: string): Promise<ArticleItem[]> { public async getContactCardArticles(guid: string): Promise<{ cardId: string, articleId: string, item: ArticleItem }[]> {
return []; return [];
} }
@ -204,7 +204,7 @@ export class OfflineStore implements Store {
public async setContactCardArticleUnsealed(guid: string, cardId: string, articleId: string, unsealedData: any): Promise<void> { public async setContactCardArticleUnsealed(guid: string, cardId: string, articleId: string, unsealedData: any): Promise<void> {
} }
public async getContactCardChannels(guid: string): Promise<ChannelItem[]> { public async getContactCardChannels(guid: string): Promise<{ cardId: string, channelId: string, item: ChannelItem }[]> {
return []; return [];
} }
@ -315,7 +315,7 @@ export class OnlineStore implements Store {
public async setContactRevision(guid: string, revision: number): Promise<void> { public async setContactRevision(guid: string, revision: number): Promise<void> {
} }
public async getContacts(guid: string): Promise<CardItem[]> { public async getContacts(guid: string): Promise<{ cardId: string, item: CardItem }[]> {
return []; return [];
} }
@ -340,7 +340,7 @@ export class OnlineStore implements Store {
public async setContactCardOffsync(guid: string, cardId: string, offsync: boolean): Promise<void> { public async setContactCardOffsync(guid: string, cardId: string, offsync: boolean): Promise<void> {
} }
public async getContactCardArticles(guid: string): Promise<ArticleItem[]> { public async getContactCardArticles(guid: string): Promise<{ cardId: string, articleId: string, item: ArticleItem }[]> {
return []; return [];
} }
@ -353,7 +353,7 @@ export class OnlineStore implements Store {
public async setContactCardArticleUnsealed(guid: string, cardId: string, articleId: string, unsealedData: any): Promise<void> { public async setContactCardArticleUnsealed(guid: string, cardId: string, articleId: string, unsealedData: any): Promise<void> {
} }
public async getContactCardChannels(guid: string): Promise<ChannelItem[]> { public async getContactCardChannels(guid: string): Promise<{ cardId: string, channelId: string, item: ChannelItem }[]> {
return []; return [];
} }
@ -438,7 +438,7 @@ export class NoStore implements Store {
public async setContactRevision(guid: string, revision: number): Promise<void> { public async setContactRevision(guid: string, revision: number): Promise<void> {
} }
public async getContacts(guid: string): Promise<CardItem[]> { public async getContacts(guid: string): Promise<{ cardId: string, item: CardItem }[]> {
return []; return [];
} }
@ -463,7 +463,7 @@ export class NoStore implements Store {
public async setContactCardOffsync(guid: string, cardId: string, offsync: boolean): Promise<void> { public async setContactCardOffsync(guid: string, cardId: string, offsync: boolean): Promise<void> {
} }
public async getContactCardArticles(guid: string): Promise<ArticleItem[]> { public async getContactCardArticles(guid: string): Promise<{ cardId: string, articleId: string, item: ArticleItem }[]> {
return []; return [];
} }
@ -476,7 +476,7 @@ export class NoStore implements Store {
public async setContactCardArticleUnsealed(guid: string, cardId: string, articleId: string, unsealedData: any): Promise<void> { public async setContactCardArticleUnsealed(guid: string, cardId: string, articleId: string, unsealedData: any): Promise<void> {
} }
public async getContactCardChannels(guid: string): Promise<ChannelItem[]> { public async getContactCardChannels(guid: string): Promise<{ cardId: string, channelId: string, item: ChannelItem }[]> {
return []; return [];
} }

View File

@ -1,5 +1,7 @@
export type Card = { export type Card = {
id: string, cardId: string,
offsync: boolean,
blocked: boolean,
status: string, status: string,
statusUpdated: number, statusUpdated: number,
guid: string, guid: string,
@ -34,10 +36,11 @@ export type Activity = {
} }
export type Channel = { export type Channel = {
id: string, channelId: string,
cardId: string | null, cardId: string | null,
lastTopic: { lastTopic: {
guid: string, guid: string,
sealed: boolean,
dataType: string, dataType: string,
data: string, data: string,
created: number, created: number,
@ -47,9 +50,8 @@ export type Channel = {
} }
unread: boolean, unread: boolean,
sealed: boolean, sealed: boolean,
unsealed: boolean;
dataType: string, dataType: string,
data: any, data: string,
created: number, created: number,
updated: number, updated: number,
enableImage: boolean, enableImage: boolean,
@ -61,16 +63,15 @@ export type Channel = {
} }
export type Member = { export type Member = {
id: string, guid?: string,
guid: string,
pushEnabled: boolean, pushEnabled: boolean,
canAddTopic: boolean, //canAddTopic: boolean,
canAddTag: boolean, //canAddTag: boolean,
canAddAsset: boolean, //canAddAsset: boolean,
canAddParticipant: boolean, //canAddParticipant: boolean,
canSortTopic: boolean, //canSortTopic: boolean,
canSortTag: boolean, //canSortTag: boolean,
participants: Participant[]; //participants: Participant[];
} }
export type Participant = { export type Participant = {
@ -127,11 +128,10 @@ export type Group = {
} }
export type Article = { export type Article = {
id: string, articleId: string,
sealed: boolean, sealed: boolean,
unsealed: boolean,
dataType: string, dataType: string,
data: string, data?: string,
created: number, created: number,
updated: number, updated: number,
contacts?: { contacts?: {