implementing contact module

This commit is contained in:
Roland Osborne 2024-10-01 16:01:44 -07:00
parent b582d99475
commit 563a0ca46d
7 changed files with 165 additions and 16 deletions

View File

@ -5,7 +5,7 @@ import { JSEncrypt } from 'jsencrypt'
export class WebCrypto implements Crypto {
// generate salt for pbk function
public async pbkdfSalt(): { saltHex: string } {
public async pbkdfSalt(): Promise<{ saltHex: string }> {
const salt = CryptoJS.lib.WordArray.random(128 / 8);
const saltHex = salt.toString();
return { saltHex };

View File

@ -44,7 +44,7 @@ export function Access() {
await actions.adminLogin()
}
otpClose()
} catch (err: {message: string}) {
} catch (err: any) {
console.log(err.message)
if (
err.message === '405' ||

View File

@ -1,4 +1,4 @@
import React, { useSettings } from './useSettings.hook'
import { useSettings } from './useSettings.hook'
import {
Modal,
Textarea,
@ -42,7 +42,7 @@ import {
} from '@tabler/icons-react'
import { modals } from '@mantine/modals'
import { useDisclosure } from '@mantine/hooks'
import { useCallback, useState, useRef } from 'react'
import React, { useCallback, useState, useRef } from 'react'
import Cropper from 'react-easy-crop'
import { Area } from 'react-easy-crop/types'
@ -179,7 +179,7 @@ export function Settings({ showLogout }: { showLogout: boolean }) {
try {
await actions.confirmMFA();
mfaClose();
} catch (err) {
} catch (err: any) {
if (err.message === '401') {
setAuthMessage(state.strings.mfaError);
} else if (err.message === '429') {

View File

@ -3,7 +3,12 @@ import type { Contact, Logging } from './api';
import type { Card, Topic, Asset, Tag, Profile, Participant} from './types';
import type { CardEntity } from './entities';
import type { ArticleRevision, ArticleDetail, ChannelRevision, ChannelSummary, ChannelDetail, CardRevision, CardNotification, CardProfile, CardDetail } from './items';
import { defaultConfigItem } from './items';
import { Store } from './store';
import { getCards } from './net/getCards';
const CLOSE_POLL_MS = 100;
const RETRY_POLL_MS = 2000;
export class ContactModule implements Contact {
@ -35,9 +40,9 @@ export class ContactModule implements Contact {
this.node = node;
this.secure = secure;
this.log = log;
this.store = store;
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 }>>;
@ -122,7 +127,7 @@ export class ContactModule implements Contact {
});
// load map of channels
const channels = await this.store.getContactCardChannles(this.guid);
const channels = await this.store.getContactCardChannels(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 }>>);
@ -135,7 +140,109 @@ export class ContactModule implements Contact {
await this.sync();
}
private getCardEntry(id: string) { item: CardItem, card: Card } {
const entry = this.cardEntries.get(id);
if (entry) {
return entry;
}
const item = JSON.parse(JSON.strifying(defaultCardItem));
const card = this.setCard(item);
const cardEntry = { item, card };
this.cardEntries.set(id, cardEntry);
return cardEntry;
}
private async sync(): Promise<void> {
if (!this.syncing) {
this.syncing = true;
while (this.nextRevision && !this.closing) {
if (this.revision !== this.nextRevision) {
const nextRev = this.nextRevision;
try {
const { guid, node, secure, token } = this;
const delta = await getCards(node, secure, token, this.revision);
for (const entity of delta) {
const { id, revision, data } = entity;
if (data) {
const entry = this.getCardEntry(id);
if (data.detailRevision !== entry.detail.revison) {
// update detail
entry.detail.revision = data.detailRevision;
// store detail
}
if (data.profileRevision !== entry.profile.revision) {
// update profile
entry.profile.revision = data.profileRevision;
// store profile
}
if (data.notifiedProfile !== entry.remote.profile) {
entry.remote.profile = data.notifiedProfile;
try {
// sync profile
}
catch (err) {
this.log.warn(err);
entry.offsync = true;
// store offsync
}
// store remote
}
if (data.notifiedArticle !== entry.remote.article) {
entry.remote.article = data.notifiedArticle;
try {
// sync articles
}
catch (err) {
this.log.warn(err);
entry.offsync = true;
// store offsync
}
this.emitCardArticles(id);
// store remote
}
if (data.notifiedChannel !== entry.remote.channel) {
entry.remote.channel = data.notifiedChannel;
try {
//sync channels
}
catch (err) {
this.log.warn(err);
entry.offsync = true;
this.emitCardChannels(id);
// store offsync
}
}
}
else {
this.cardEntries.delete(id);
}
}
this.emitCards();
await this.store.setContactRevision(guid, nextRev);
this.revision = nextRev;
if (this.nextRevision === nextRev) {
this.nextRevision = null;
}
this.log.info(`card revision: ${nextRev}`);
}
catch (err) {
this.log.warn(err);
await new Promise(r => setTimeout(r, RETRY_POLL_MS));
}
}
if (this.revision === this.nextRevsion) {
this.nextRevision = null;
}
}
this.syncing = false;
}
}
public addCardListener(ev: (cards: Card[]) => void): void {
@ -240,7 +347,8 @@ export class ContactModule implements Contact {
}
public async setRevision(rev: number): Promise<void> {
console.log('set contact revision:', rev);
this.nextRevision = rev;
await this.sync();
}
public async addCard(server: string, guid: string): Promise<string> {

View File

@ -9,14 +9,14 @@ export type CardEntity = {
notifiedProfile: number,
notifiedArticle: number,
notifiedChannel: number,
cardDetail: {
cardDetail?: {
status: string,
statusUpdated: number,
token: string,
notes: string,
groups: [ string ]
},
cardProfile: {
cardProfile?: {
guid: string,
handle: string,
name: string,

View File

@ -1,8 +1,3 @@
export type CardRevision = {
detail: number,
profile: number,
}
export type CardNotification = {
profile: number,
article: number,
@ -10,12 +5,14 @@ export type CardNotification = {
}
export type CardDetail = {
revision: number,
status: string,
statusUpdated: number,
token: string,
}
export type CardProfile = {
revision: number,
handle: string,
guid: string,
name: string,
@ -82,13 +79,46 @@ export type ArticleDetail = {
export type CardItem = {
offsync: boolean,
blocked: boolean,
revision: CardRevision,
revision: number,
profile: CardProfile,
detail: CardDetail,
remote: CardNotification,
sync: CardNotification,
}
export const defaultCardItem = {
offsync: false,
blocked: false,
cardRevision: 0,
profileRevision: 0,
profile: {
handle: '',
guid: '',
name: '',
description: '',
location: '',
imageSet: false,
node: '',
seal: '',
},
detailRevision: 0,
detail: {
status: '',
statusUpdateed: 0,
token: 0,
},
remote: {
profile: 0,
article: 0,
channel: 0,
},
sync: {
profile: 0,
article: 0,
channel: 0,
},
};
export type ArticleItem = {
blocked: boolean,
detail: ArticleDetail,

View File

@ -0,0 +1,11 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
import { CardEntity } from '../entities';
export async function getCards(node: string, secure: boolean, token: string, revision: number): Promise<CardEntity[]> {
const param = revision ? `&revision=${revision}` : '';
const endpoint = `http${secure ? 's' : ''}://${node}/contact/cards?agent=${token}${param}`;
const cards = await fetchWithTimeout(endpoint, { method: 'GET' });
checkResponse(cards.status);
return await cards.json();
}