mirror of
https://github.com/balzack/databag.git
synced 2025-04-24 02:25:26 +00:00
implementing contact module
This commit is contained in:
parent
b582d99475
commit
563a0ca46d
@ -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 };
|
||||
|
@ -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' ||
|
||||
|
@ -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') {
|
||||
|
@ -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> {
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
11
app/sdk/src/net/getCards.ts
Normal file
11
app/sdk/src/net/getCards.ts
Normal 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();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user