diff --git a/net/web/src/api/getContactProfile.js b/net/web/src/api/getContactProfile.js index 2d9577d9..9747bfdc 100644 --- a/net/web/src/api/getContactProfile.js +++ b/net/web/src/api/getContactProfile.js @@ -1,12 +1,12 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -export async function getContactProfile(server, guid, token) { +export async function getContactProfile(server, token) { let host = ""; if (server) { host = `https://${server}`; } - let profile = await fetchWithTimeout(`${host}/profile/message?contact=${guid}.${token}`, { method: 'GET', }); + let profile = await fetchWithTimeout(`${host}/profile/message?contact=${token}`, { method: 'GET', }); checkResponse(profile); return await profile.json() } diff --git a/net/web/src/api/setContactChannelTopicSubject.js b/net/web/src/api/setContactChannelTopicSubject.js index 66abdc43..d6be6042 100644 --- a/net/web/src/api/setContactChannelTopicSubject.js +++ b/net/web/src/api/setContactChannelTopicSubject.js @@ -1,7 +1,6 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; export async function setContactChannelTopicSubject(server, token, channelId, topicId, datatype, data) { -console.log(data); let host = ""; if (server) { host = `https://${server}`; diff --git a/net/web/src/context/useCardContext.hook.js b/net/web/src/context/useCardContext.hook.js index 40c0c094..04cc8f89 100644 --- a/net/web/src/context/useCardContext.hook.js +++ b/net/web/src/context/useCardContext.hook.js @@ -23,155 +23,177 @@ import { getContactChannelTopicAssetUrl } from 'api/getContactChannelTopicAssetU import { addCard } from 'api/addCard'; import { removeCard } from 'api/removeCard'; import { UploadContext } from 'context/UploadContext'; -import CryptoJS from 'crypto-js'; -import { JSEncrypt } from 'jsencrypt' export function useCardContext() { const [state, setState] = useState({ - init: false, + offsync: false, cards: new Map(), }); const upload = useContext(UploadContext); const access = useRef(null); - const revision = useRef(null); - const next = useRef(null); + const syncing = useRef(false); + const setRevision = useRef(null); + const curRevision = useRef(null); const cards = useRef(new Map()); - const resync = useRef([]); + const forceCard = useRef(null); + const force = useRef(false); const updateState = (value) => { setState((s) => ({ ...s, ...value })) } - const updateCard = async (cardId) => { - let card = cards.current.get(cardId); - const { cardDetail, cardProfile } = card.data; - - if (cardDetail.status === 'connected' && card.error) { - let message = await getContactProfile(cardProfile.node, cardProfile.guid, cardDetail.token); - await setCardProfile(access.current, card.id, message); - - card.channels = new Map(); - await updateContactChannels(card.data.cardProfile.node, card.id, card.data.cardProfile.guid, card.data.cardDetail.token, null, null, card.channels); - - card.data.articles = new Map(); - await updateContactArticles(card.data.cardProfile.node, card.id, card.data.cardProfile.guid, card.data.cardDetail.token, null, null, card.data.articles); - - cards.current.set(card.id, { ...card, error: false }); + const resync = async () => { + try { + force.current = true; + await sync(); } - } - - const unsealKey = (seals, sealKey) => { - let unsealedKey; - if (seals?.length) { - seals.forEach(seal => { - if (seal.publicKey === sealKey.public) { - let crypto = new JSEncrypt(); - crypto.setPrivateKey(sealKey.private); - unsealedKey = crypto.decrypt(seal.sealedKey); - } - }); + catch (err) { + console.log(err); } - return unsealedKey; }; - const updateCards = async () => { - let delta = await getCards(access.current, revision.current); - for (let card of delta) { - if (card.data) { - let cur = cards.current.get(card.id); - if (cur == null) { - cur = { id: card.id, data: { articles: new Map() }, error: false, notifiedProfile: null, channels: new Map() } - } - if (cur.data.detailRevision !== card.data.detailRevision) { - if (card.data.cardDetail != null) { - cur.data.cardDetail = card.data.cardDetail; - } - else { - cur.data.cardDetail = await getCardDetail(access.current, card.id); - } - cur.data.detailRevision = card.data.detailRevision; - } - if (cur.data.profileRevision !== card.data.profileRevision) { - if (card.data.cardProfile != null) { - cur.data.cardProfile = card.data.cardProfile; - } - else { - cur.data.cardProfile = await getCardProfile(access.current, card.id); - } - cur.data.profileRevision = card.data.profileRevision; - } - const { cardDetail, cardProfile } = cur.data; - if (cardDetail.status === 'connected' && !cur.error) { - try { - if (cur.data.profileRevision !== card.data.notifiedProfile && cur.notifiedProfile !== card.data.notifiedProfile) { - let message = await getContactProfile(cardProfile.node, cardProfile.guid, cardDetail.token); - await setCardProfile(access.current, card.id, message); + const resyncCard = async (cardId) => { + if (cardId) { + forceCard.current = cardId; + } + if (!syncing.current && forceCard.current) { + syncing.current = true; + forceCard.current = null; - // update remote profile - cur.notifiedProfile = card.data.notifiedProfile; - } - if (cur.data.notifiedView !== card.data.notifiedView) { - // update remote articles and channels - cur.data.articles = new Map(); - cur.channels = new Map(); - - await updateContactChannels(cur.data.cardProfile.node, card.id, cur.data.cardProfile.guid, cur.data.cardDetail.token, cur.data.notifiedView, cur.data.notifiedChannel, cur.channels); - await updateContactArticles(cur.data.cardProfile.node, card.id, cur.data.cardProfile.guid, cur.data.cardDetail.token, cur.data.notifiedView, cur.data.notifiedArticle, cur.data.articles); - - // update view - cur.data.notifiedArticle = card.data.notifiedArticle; - cur.data.notifiedChannel = card.data.notifiedChannel; - cur.data.notifiedView = card.data.notifiedView; - } - if (cur.data.notifiedArticle !== card.data.notifiedArticle) { - // update remote articles - await updateContactArticles(cur.data.cardProfile.node, card.id, cur.data.cardProfile.guid, cur.data.cardDetail.token, cur.data.notifiedView, cur.data.notifiedArticle, cur.data.articles); - cur.data.notifiedArticle = card.data.notifiedArticle; - } - if (cur.data.notifiedChannel !== card.data.notifiedChannel) { - // update remote channels - await updateContactChannels(cur.data.cardProfile.node, card.id, cur.data.cardProfile.guid, cur.data.cardDetail.token, cur.data.notifiedView, cur.data.notifiedChannel, cur.channels); - cur.data.notifiedChannel = card.data.notifiedChannel; - } - } - catch (err) { - // contact update failed - console.log(err); - cur.channels = new Map(); - cur.articles = new Map(); - cur.revision = 0; - cards.current.set(card.id, { ...cur, error: true }); - continue; - } - } - else { - cur.channels = new Map(); - cur.articles = new Map(); - } - cur.revision = card.revision; - cards.current.set(card.id, { ...cur }); + try { + const card = cards.current.get(card.id); + await syncCard(card); + cards.current.set(card.id, card); } - else { - cards.current.delete(card.id); + catch(err) { + console.log(err); } + syncing.current = false; + await sync(); } } - const updateContactChannels = async (node, cardId, guid, token, viewRevision, channelRevision, channelMap) => { - let delta = await getContactChannels(node, guid + "." + token, viewRevision, channelRevision); + const sync = async () => { + if (!syncing.current && (setRevision.current !== curRevision.current || force.current)) { + syncing.current = true; + force.current = false; + + try { + const token = access.current; + const revision = curRevision.current; + const delta = await getCards(token, setRevision.current); + for (let card of delta) { + if (card.data) { + let cur = cards.current.get(card.id); + if (cur == null) { + cur = { id: card.id, data: { articles: new Map() }, offsync: false, channels: new Map() } + } + if (cur.data.detailRevision !== card.data.detailRevision) { + if (card.data.cardDetail != null) { + cur.data.cardDetail = card.data.cardDetail; + } + else { + cur.data.cardDetail = await getCardDetail(access.current, card.id); + } + cur.data.detailRevision = card.data.detailRevision; + } + if (cur.data.profileRevision !== card.data.profileRevision) { + if (card.data.cardProfile != null) { + cur.data.cardProfile = card.data.cardProfile; + } + else { + cur.data.cardProfile = await getCardProfile(access.current, card.id); + } + cur.data.profileRevision = card.data.profileRevision; + } + + if (cur.data.cardDetail.status === 'connected' && !cur.offsync) { + cur.data.curNotifiedView = card.data.notifiedView; + cur.data.curNotifiedProfile = card.data.notifiedProfile; + cur.data.curNotifiedArticle = card.data.notifiedArticle; + cur.data.curNotifiedChannel = card.data.notifiedChannel; + try { + await syncCard(cur); + } + catch (err) { + console.log(err); + cur.offsync = true; + } + } + cards.current.set(cur.id, cur); + } + else { + cards.current.delete(cur.id); + } + } + updateState({ offsync: false, cards: cards.current }); + } + catch (err) { + console.log(err); + syncing.current = false; + updateState({ offsync: true }); + return; + } + + syncing.current = false; + await sync(); + } + }; + + const syncCard = async (token, card) => { + const { cardProfile, cardDetail } = card; + + // sync profile + if (card.data.setNotifiedProfile !== card.data.curNotifiedProfile) { + if (card.data.profileRevision != card.data.curNotifiedProfile) { + const token = `${cardProfile.guid}.${cardDetail.token}`; + const message = await getContactProfile(cardProfile.node, token); + await setCardProfile(token, card.id, message); + } + } + card.data.setNotifiedProfile = card.data.curNotifiedProfile; + + // sync channels & articles + if (card.data.setNotifiedArticle !== card.data.curNotifiedArticle || card.data.setNotifiedView !== card.data.curNotifiedView) { + await syncCardArticles(card); + } + if (card.data.setNotifiedChannel !== card.data.curNotifiedChannel || card.data.setNotifiedView !== card.data.curNotifiedView) { + await syncCardChannels(card); + } + card.data.setNotifiedArticle = card.data.curNotifiedArticle; + card.data.setNotifiedChannel = card.data.curNotifiedChannel; + card.data.setNotifiedView = card.data.curNotifiedView; + card.offsync = false; + } + + const syncCardArticles = async (card) => { + console.log("update contact articles"); + } + + const syncCardChannels = async (card) => { + const { cardProfile, cardDetail } = card; + const token = `${cardProfile.guid}.${cardDetail.token}`; + let delta; + if (card.data.setNotifiedView !== card.data.curNotifiedView) { + card.channels = new Map(); + delta = await getContactChannels(node, token); + } + else { + const { setNotifiedView, setNotifiedChannel } = card.data; + delta = await getContactChannels(node, notifiedView, notifiedChannel); + } for (let channel of delta) { if (channel.data) { - let cur = channelMap.get(channel.id); + let cur = card.channels.get(channel.id); if (cur == null) { - cur = { guid, cardId, id: channel.id, data: { } } + cur = { guid, cardId, id: channel.id, data: {} }; } if (cur.data.detailRevision !== channel.data.detailRevision) { if (channel.data.channelDetail != null) { cur.data.channelDetail = channel.data.channelDetail; } - else { - let detail = await getContactChannelDetail(node, guid + "." + token, channel.id); - cur.data.channelDetail = detail; + else { + cur.data.channelDetail = await getContactChannelDetail(node, token, channel.id); } cur.data.unsealedChannel = null; cur.data.detailRevision = channel.data.detailRevision; @@ -181,286 +203,37 @@ export function useCardContext() { cur.data.channelSummary = channel.data.channelSummary; } else { - let summary = await getContactChannelSummary(node, guid + "." + token, channel.id); - cur.data.channelSummary = summary; + cur.data.channelSummary = getContactChannelSummary(node, token, channel.id); } cur.data.unsealedSummary = null; cur.data.topicRevision = channel.data.topicRevision; } cur.revision = channel.revision; - channelMap.set(channel.id, { ...cur }); + card.channels.set(channel.id, cur); } - else { - channelMap.delete(channel.id); + else { + card.channels.delete(channel.id); } } } - const updateContactArticles = async (node, cardId, guid, token, viewRevision, articleRevision, articleMap) => { - console.log("update contact articles"); - } - - const setCards = async (rev) => { - if (next.current == null) { - if (rev == null) { - rev = revision.curren; - } - next.current = rev; - if (revision.current !== rev) { - try { - await updateCards(); - } - catch(err) { - console.log(err); - } - updateState({ init: true, cards: cards.current }); - revision.current = rev; - } - - while (resync.current.length) { - try { - await updateCard(resync.current.shift()); - updateState({ cards: cards.current }); - } - catch (err) { - console.log(err); - } - } - - let r = next.current; - next.current = null; - if (revision.current !== r) { - setCards(r); - } - } - else { - if (rev != null) { - next.current = rev; - } - } - } - - const getCardByGuid = (guid) => { - let card = null; - cards.current.forEach((value, key, map) => { - if(value?.data?.cardProfile?.guid === guid) { - card = value; - } - }); - return card; - } - const actions = { setToken: (token) => { + if (access.current || syncing.current) { + throw new Error("invalid session state"); + } access.current = token; + cards.current = new Map(); + curRevision.current = null; + setRevision.current = null; + setState({ offsync: false, cards: new Map() }); }, clearToken: () => { access.current = null; - cards.current = new Map(); - revision.current = null; - setState({ init: false, cards: new Map() }); - }, + }, setRevision: async (rev) => { - setCards(rev); - }, - getCardByGuid: getCardByGuid, - getCardProfileByGuid: (guid) => { - let card = getCardByGuid(guid); - if (card) { - let { name, handle } = card.data.cardProfile; - if (card.data.cardProfile.imageSet) { - return { name, handle, imageUrl: getCardImageUrl(access.current, card.id, card.data.profileRevision) }; - } - return { name, handle } - } - return {}; - }, - getImageUrl: (cardId) => { - let card = cards.current.get(cardId); - if (!card || !card.data.cardProfile.imageSet) { - return null; - } - return getCardImageUrl(access.current, cardId, card.data.profileRevision) - }, - getName: (cardId) => { - let card = cards.current.get(cardId); - if (!card) { - return null; - } - return card.data.cardProfile.name; - }, - unsealChannelSubject: (cardId, channelId, sealKey) => { - try { - const card = cards.current.get(cardId); - const channel = card.channels.get(channelId); - const { subjectEncrypted, subjectIv, seals } = JSON.parse(channel.data.channelDetail.data); - const unsealedKey = unsealKey(seals, sealKey); - if (unsealedKey) { - const iv = CryptoJS.enc.Hex.parse(subjectIv); - const key = CryptoJS.enc.Hex.parse(unsealedKey); - const enc = CryptoJS.enc.Base64.parse(subjectEncrypted); - let cipher = CryptoJS.lib.CipherParams.create({ ciphertext: enc, iv: iv }); - const dec = CryptoJS.AES.decrypt(cipher, key, { iv: iv }); - channel.data.unsealedChannel = JSON.parse(dec.toString(CryptoJS.enc.Utf8)); - card.channels.set(channel.id, { ...channel }); - cards.current.set(cardId, { ...card }); - updateState({ cards: cards.current }); - } - } - catch(err) { - console.log(err); - } - }, - isUnsealed: (cardId, channelId, sealKey) => { - try { - const card = cards.current.get(cardId); - const channel = card.channels.get(channelId); - const { seals } = JSON.parse(channel.data.channelDetail.data); - for (let i = 0; i < seals.length; i++) { - if (seals[i].publicKey === sealKey.public) { - return sealKey.private != null; - } - } - } - catch(err) { - console.log(err); - } - return false; - }, - unsealChannelSummary: (cardId, channelId, sealKey) => { - try { - const card = cards.current.get(cardId); - const channel = card.channels.get(channelId); - const { seals } = JSON.parse(channel.data.channelDetail.data); - const { messageEncrypted, messageIv } = JSON.parse(channel.data.channelSummary.lastTopic.data); - const unsealedKey = unsealKey(seals, sealKey); - if (unsealedKey) { - const iv = CryptoJS.enc.Hex.parse(messageIv); - const key = CryptoJS.enc.Hex.parse(unsealedKey); - const enc = CryptoJS.enc.Base64.parse(messageEncrypted); - const cipher = CryptoJS.lib.CipherParams.create({ ciphertext: enc, iv: iv }); - const dec = CryptoJS.AES.decrypt(cipher, key, { iv: iv }); - channel.data.unsealedSummary = JSON.parse(dec.toString(CryptoJS.enc.Utf8)); - card.channels.set(channel.id, { ...channel }); - cards.current.set(cardId, { ...card }); - updateState({ cards: cards.current }); - } - } - catch(err) { - console.log(err); - } - }, - removeChannel: async (cardId, channelId) => { - let { cardProfile, cardDetail } = cards.current.get(cardId).data; - let token = cardProfile.guid + '.' + cardDetail.token; - let node = cardProfile.node; - await removeContactChannel(node, token, channelId); - }, - removeChannelTopic: async (cardId, channelId, topicId) => { - let { cardProfile, cardDetail } = cards.current.get(cardId).data; - let token = cardProfile.guid + '.' + cardDetail.token; - let node = cardProfile.node; - await removeContactChannelTopic(node, token, channelId, topicId); - try { - resync.current.push(cardId); - await setCards(null); - } - catch (err) { - console.log(err); - } - }, - setChannelTopicSubject: async (cardId, channelId, topicId, data) => { - let { cardProfile, cardDetail } = cards.current.get(cardId).data; - let token = cardProfile.guid + '.' + cardDetail.token; - let node = cardProfile.node; - await setContactChannelTopicSubject(node, token, channelId, topicId, 'superbasictopic', data); - try { - resync.current.push(cardId); - await setCards(null); - } - catch (err) { - console.log(err); - } - }, - setSealedChannelTopicSubject: async (cardId, channelId, topicId, data, sealKey) => { - let { cardProfile, cardDetail } = cards.current.get(cardId).data; - let token = cardProfile.guid + '.' + cardDetail.token; - let node = cardProfile.node; - const iv = CryptoJS.lib.WordArray.random(128 / 8); - const key = CryptoJS.enc.Hex.parse(sealKey); - const encrypted = CryptoJS.AES.encrypt(JSON.stringify({ message: data }), key, { iv: iv }); - const messageEncrypted = encrypted.ciphertext.toString(CryptoJS.enc.Base64) - const messageIv = iv.toString(); - await setContactChannelTopicSubject(node, token, channelId, topicId, 'sealedtopic', { messageEncrypted, messageIv }); - try { - resync.current.push(cardId); - await setCards(null); - } - catch (err) { - console.log(err); - } - }, - addChannelTopic: async (cardId, channelId, message, files) => { - let { cardProfile, cardDetail } = cards.current.get(cardId).data; - let token = cardProfile.guid + '.' + cardDetail.token; - let node = cardProfile.node; - if (files?.length) { - const topicId = await addContactChannelTopic(node, token, channelId, null, null, null); - upload.actions.addContactTopic(node, token, cardId, channelId, topicId, files, async (assets) => { - message.assets = assets; - await setContactChannelTopicSubject(node, token, channelId, topicId, message); - }, async () => { - try { - await removeContactChannelTopic(node, token, channelId, topicId); - } - catch(err) { - console.log(err); - } - }); - } - else { - await addContactChannelTopic(node, token, channelId, 'superbasictopic', message, files); - } - try { - resync.current.push(cardId); - await setCards(null); - } - catch (err) { - console.log(err); - } - }, - addSealedChannelTopic: async (cardId, channelId, sealKey, message) => { - let { cardProfile, cardDetail } = cards.current.get(cardId).data; - let token = cardProfile.guid + '.' + cardDetail.token; - let node = cardProfile.node; - const iv = CryptoJS.lib.WordArray.random(128 / 8); - const key = CryptoJS.enc.Hex.parse(sealKey); - const encrypted = CryptoJS.AES.encrypt(JSON.stringify({ message }), key, { iv: iv }); - const messageEncrypted = encrypted.ciphertext.toString(CryptoJS.enc.Base64) - const messageIv = iv.toString(); - await addContactChannelTopic(node, token, channelId, 'sealedtopic', { messageEncrypted, messageIv }); - }, - getChannel: (cardId, channelId) => { - let card = cards.current.get(cardId); - let channel = card.channels.get(channelId); - return channel; - }, - getChannelRevision: (cardId, channelId) => { - let card = cards.current.get(cardId); - let channel = card.channels.get(channelId); - return channel?.revision; - }, - getChannelTopics: async (cardId, channelId, revision, count, begin, end) => { - let card = cards.current.get(cardId); - let node = card.data.cardProfile.node; - let token = card.data.cardProfile.guid + '.' + card.data.cardDetail.token; - return await getContactChannelTopics(node, token, channelId, revision, count, begin, end); - }, - getChannelTopic: async (cardId, channelId, topicId) => { - let card = cards.current.get(cardId); - let node = card.data.cardProfile.node; - let token = card.data.cardProfile.guid + '.' + card.data.cardDetail.token; - return await getContactChannelTopic(node, token, channelId, topicId); + curRevision.current = rev; + await sync(); }, addCard: async (message) => { return await addCard(access.current, message); @@ -490,16 +263,91 @@ export function useCardContext() { setCardCloseMessage: async (server, message) => { return await setCardCloseMessage(server, message); }, - getContactChannelTopicAssetUrl: (cardId, channelId, topicId, assetId) => { - let card = cards.current.get(cardId); - let node = card.data.cardProfile.node; - let token = card.data.cardProfile.guid + "." + card.data.cardDetail.token; + removeChannel: async (cardId, channelId) => { + let { cardProfile, cardDetail } = cards.current.get(cardId).data; + let token = cardProfile.guid + '.' + cardDetail.token; + let node = cardProfile.node; + await removeContactChannel(node, token, channelId); + }, + unsealChannelSubject: async (cardId, channelId, unsealed, revision) => { + const card = cards.current.get(cardId); + const channel = card.channels.get(channelId); + if (channel.revision === revision) { + channel.data.unsealedSubject = unsealed; + card.channels.set(channelId, channel); + cards.current.set(cardId, card); + updateState({ cards: cards.current }); + } + }, + unsealChannelSummary: async (channelId, unsealed, revision) => { + const card = cards.current.get(cardId); + const channel = card.channels.get(channelId); + if (channel.revision === revision) { + channel.data.unsealedSummary = unsealed; + card.channels.set(channelId, channel); + cards.current.set(cardId, card); + updateState({ cards: cards.current }); + } + }, + addTopic: async (cardId, channelId, type, message, files) => { + let { cardProfile, cardDetail } = cards.current.get(cardId).data; + let token = cardProfile.guid + '.' + cardDetail.token; + let node = cardProfile.node; + if (files?.length) { + const topicId = await addContactChannelTopic(node, token, channelId, null, null, null); + upload.actions.addContactTopic(node, token, cardId, channelId, topicId, files, async (assets) => { + const subject = message(assets); + await setContactChannelTopicSubject(node, token, channelId, topicId, type, subject); + }, async () => { + try { + await removeContactChannelTopic(node, token, channelId, topicId); + } + catch(err) { + console.log(err); + } + }); + } + else { + const subject = message([]); + await addContactChannelTopic(node, token, channelId, type, subject, files); + } + resyncCard(cardId); + }, + removeChannelTopic: async (cardId, channelId, topicId) => { + let { cardProfile, cardDetail } = cards.current.get(cardId).data; + let token = cardProfile.guid + '.' + cardDetail.token; + let node = cardProfile.node; + await removeContactChannelTopic(node, token, channelId, topicId); + resyncCard(cardId); + }, + setChannelTopicSubject: async (cardId, channelId, topicId, type, subject) => { + let { cardProfile, cardDetail } = cards.current.get(cardId).data; + let token = cardProfile.guid + '.' + cardDetail.token; + let node = cardProfile.node; + await setContactChannelTopicSubject(node, token, channelId, topicId, type, subject); + resyncCard(cardId); + }, + getChannelTopicAssetUrl: (cardId, channelId, topicId, assetId) => { + let { cardProfile, cardDetail } = cards.current.get(cardId).data; + let token = cardProfile.guid + '.' + cardDetail.token; + let node = cardProfile.node; return getContactChannelTopicAssetUrl(node, token, channelId, topicId, assetId); }, - resync: async (cardId) => { - resync.current.push(cardId); - await setCards(null); - } + getChannelTopics: async (channelId, revision, count, begin, end) => { + let { cardProfile, cardDetail } = cards.current.get(cardId).data; + let token = cardProfile.guid + '.' + cardDetail.token; + let node = cardProfile.node; + return await getContactChannelTopics(node, token, channelId, revision, count, begin, end); + }, + getChannelTopic: async (channelId, topicId) => { + let { cardProfile, cardDetail } = cards.current.get(cardId).data; + let token = cardProfile.guid + '.' + cardDetail.token; + let node = cardProfile.node; + return await getChannelTopic(node, token, channelId, topicId); + }, + resync: async () => { + await resync(); + }, } return { state, actions } diff --git a/net/web/src/context/useChannelContext.hook.js b/net/web/src/context/useChannelContext.hook.js index 857203ae..f689abfc 100644 --- a/net/web/src/context/useChannelContext.hook.js +++ b/net/web/src/context/useChannelContext.hook.js @@ -26,14 +26,26 @@ export function useChannelContext() { const curRevision = useRef(null); const channels = useRef(new Map()); const syncing = useRef(false); + const force = useRef(false); const updateState = (value) => { setState((s) => ({ ...s, ...value })) } + const resync = async () => { + try { + force.current = true; + await sync(); + } + catch (err) { + console.log(err); + } + }; + const sync = async () => { - if (!syncing.current && setRevision.current !== curRevision.current) { + if (!syncing.current && (setRevision.current !== curRevision.current || force.current)) { syncing.current = true; + force.current = false; try { const token = access.current; @@ -157,19 +169,15 @@ export function useChannelContext() { const subject = message([]); await addChannelTopic(access.current, channelId, type, subject); } - try { - await setChannels(null); - } - catch (err) { - console.log(err); - } - + await resync(); }, removeTopic: async (channelId, topicId) => { await removeChannelTopic(access.current, channelId, topicId); + await resync(); }, setTopicSubject: async (channelId, topicId, type, subject) => { await setChannelTopicSubject(access.current, channelId, topicId, type, subject); + await resync(); }, getChannelTopicAssetUrl: (channelId, topicId, assetId) => { return getChannelTopicAssetUrl(access.current, channelId, topicId, assetId); @@ -181,7 +189,7 @@ export function useChannelContext() { return await getChannelTopic(access.current, channelId, topicId); }, resync: async () => { - await sync(); + await resync(); }, }; diff --git a/net/web/test/Card.test.js b/net/web/test/Card.test.js new file mode 100644 index 00000000..5019747c --- /dev/null +++ b/net/web/test/Card.test.js @@ -0,0 +1,84 @@ +import React, { useState, useEffect, useContext } from 'react'; +import { prettyDOM } from '@testing-library/dom' +import {render, act, screen, waitFor, fireEvent} from '@testing-library/react' +import { CardContextProvider, CardContext } from 'context/CardContext'; +import * as fetchUtil from 'api/fetchUtil'; + +let cardContext = null; +function CardView() { + const [renderCount, setRenderCount] = useState(0); + const [cards, setCards] = useState([]); + const card = useContext(CardContext); + cardContext = card; + + useEffect(() => { + const rendered = [] + const entries = Array.from(card.state.cards.values()); + entries.forEach(entry => { + + rendered.push( +
+
); + }); + setCards(rendered); + setRenderCount(renderCount + 1); + }, [card.state]) + + return ( +
+ { cards } +
+ ); +} + +function CardTestApp() { + return ( + + + + ) +} + +const realFetchWithTimeout = fetchUtil.fetchWithTimeout; +const realFetchWithCustomTimeout = fetchUtil.fetchWithCustomTimeout; + +let statusCards; +let fetchCards; +beforeEach(() => { + + statusCards = 200; + fetchCards =[]; + const mockFetch = jest.fn().mockImplementation((url, options) => { + return Promise.resolve({ + url: 'getChannels', + status: statusChannels, + json: () => Promise.resolve(fetchChannels), + }); + }); + fetchUtil.fetchWithTimeout = mockFetch; + fetchUtil.fetchWithCustomTimeout = mockFetch; +}); + +afterEach(() => { + fetchUtil.fetchWithTimeout = realFetchWithTimeout; + fetchUtil.fetchWithCustomTimeout = realFetchWithCustomTimeout; +}); + +test('boilerplate', async () => { + + render(); + + await waitFor(async () => { + expect(cardContext).not.toBe(null); + }); + + await act( async () => { + cardContext.actions.setToken('abc123'); + }); + + await act( async () => { + cardContext.actions.clearToken(); + }); + +}); + diff --git a/net/web/test/Channel.test.js b/net/web/test/Channel.test.js index ed5aaf9f..4e7b23cb 100644 --- a/net/web/test/Channel.test.js +++ b/net/web/test/Channel.test.js @@ -15,7 +15,6 @@ function ChannelView() { const rendered = [] const entries = Array.from(channel.state.channels.values()); entries.forEach(entry => { -console.log(entry.data.unsealedSubject); rendered.push(