diff --git a/app/mobile/src/context/useCardContext.hook.js b/app/mobile/src/context/useCardContext.hook.js index be53d19c..fb64e867 100644 --- a/app/mobile/src/context/useCardContext.hook.js +++ b/app/mobile/src/context/useCardContext.hook.js @@ -33,6 +33,9 @@ import { removeContactChannelTopic } from 'api/removeContactChannelTopic'; import { getContactChannelNotifications } from 'api/getContactChannelNotifications'; import { setContactChannelNotifications } from 'api/setContactChannelNotifications'; +import CryptoJS from 'crypto-js'; +import { JSEncrypt } from 'jsencrypt' + export function useCardContext() { const [state, setState] = useState({ cards: new Map(), @@ -595,6 +598,36 @@ export function useCardContext() { const { detail, profile } = getCardEntry(cardId); return await setContactChannelNotifications(profile.node, `${profile.guid}.${detail.token}`, channelId, notify); }, + unsealChannelSubject: async (cardId, channelId, revision, sealKey) => { + try { + const { guid } = session.current; + const card = cards.current.get(cardId); + const channel = card.channels.get(channelId); + const { subjectEncrypted, subjectIv, seals } = JSON.parse(channel.detail.data); + seals.forEach(seal => { + if (seal.publicKey === sealKey.public) { + let crypto = new JSEncrypt(); + crypto.setPrivateKey(sealKey.private); + const unsealedKey = crypto.decrypt(seal.sealedKey); + 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 }); + if (revision === channel.detailRevision) { + channel.unsealedDetail = JSON.parse(dec.toString(CryptoJS.enc.Utf8)); + } + } + }); + await store.actions.setCardChannelItemUnsealedDetail(guid, cardId, channelId, revision, channel.unsealedDetail); + card.channels.set(channelId, { ...channel }); + cards.current.set(cardId, { ...card }); + updateState({ cards: cards.current }); + } + catch(err) { + console.log(err); + } + }, resync: (cardId) => { resync.current.push(cardId); sync(); diff --git a/app/mobile/src/context/useChannelContext.hook.js b/app/mobile/src/context/useChannelContext.hook.js index 697d1623..83f59df4 100644 --- a/app/mobile/src/context/useChannelContext.hook.js +++ b/app/mobile/src/context/useChannelContext.hook.js @@ -334,6 +334,34 @@ export function useChannelContext() { const { server, appToken } = session.current; return await setChannelNotifications(server, appToken, channelId, notify); }, + unsealChannelSubject: async (channelId, revision, sealKey) => { + try { + const { guid } = session.current; + const channel = channels.current.get(channelId); + const { subjectEncrypted, subjectIv, seals } = JSON.parse(channel.detail.data); + seals.forEach(seal => { + if (seal.publicKey === sealKey.public) { + let crypto = new JSEncrypt(); + crypto.setPrivateKey(sealKey.private); + const unsealedKey = crypto.decrypt(seal.sealedKey); + 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 }); + if (revision === channel.detailRevision) { + channel.unsealedDetail = JSON.parse(dec.toString(CryptoJS.enc.Utf8)); + } + } + }); + await store.actions.setChannelItemUnsealedDetail(guid, channelId, revision, channel.unsealedDetail); + channels.current.set(channelId, { ...channel }); + updateState({ channels: channels.current }); + } + catch(err) { + console.log(err); + } + }, } return { state, actions } diff --git a/app/mobile/src/context/useStoreContext.hook.js b/app/mobile/src/context/useStoreContext.hook.js index 9a5b9825..15cdf30f 100644 --- a/app/mobile/src/context/useStoreContext.hook.js +++ b/app/mobile/src/context/useStoreContext.hook.js @@ -1,7 +1,7 @@ import { useEffect, useState, useRef, useContext } from 'react'; import SQLite from "react-native-sqlite-storage"; -const DATABAG_DB = 'databag_v064.db'; +const DATABAG_DB = 'databag_v068.db'; export function useStoreContext() { const [state, setState] = useState({}); @@ -221,6 +221,9 @@ export function useStoreContext() { setChannelItemDetail: async (guid, channelId, revision, detail) => { await db.current.executeSql(`UPDATE channel_${guid} set detail_revision=?, detail=?, unsealed_detail=null where channel_id=?`, [revision, encodeObject(detail), channelId]); }, + setChannelItemUnsealedDetail: async (guid, channelId, revision, unsealed) => { + await db.current.executeSql(`UPDATE channel_${guid} set unsealed_detail=? where detail_revision=? AND channel_id=?`, [encodeObject(unsealed), revision, channelId]); + }, setChannelItemSummary: async (guid, channelId, revision, summary) => { await db.current.executeSql(`UPDATE channel_${guid} set topic_revision=?, summary=?, unsealed_summary=null where channel_id=?`, [revision, encodeObject(summary), channelId]); }, @@ -236,7 +239,7 @@ export function useStoreContext() { }; }, getChannelItems: async (guid) => { - const values = await getAppValues(db.current, `SELECT channel_id, read_revision, revision, sync_revision, blocked, detail_revision, topic_revision, detail, summary FROM channel_${guid}`, []); + const values = await getAppValues(db.current, `SELECT channel_id, read_revision, revision, sync_revision, blocked, detail_revision, topic_revision, detail, unsealed_detail, summary, unsealed_summary FROM channel_${guid}`, []); return values.map(channel => ({ channelId: channel.channel_id, revision: channel.revision, @@ -254,7 +257,7 @@ export function useStoreContext() { getChannelTopicItems: async (guid, channelId) => { - const values = await getAppValues(db.current, `SELECT topic_id, revision, blocked, detail_revision, detail FROM channel_topic_${guid} WHERE channel_id=?`, [channelId]); + const values = await getAppValues(db.current, `SELECT topic_id, revision, blocked, detail_revision, detail, unsealed_detail FROM channel_topic_${guid} WHERE channel_id=?`, [channelId]); return values.map(topic => ({ topicId: topic.topic_id, revision: topic.revision, @@ -305,6 +308,9 @@ export function useStoreContext() { setCardChannelItemDetail: async (guid, cardId, channelId, revision, detail) => { await db.current.executeSql(`UPDATE card_channel_${guid} set detail_revision=?, detail=?, unsealed_detail=null where card_id=? and channel_id=?`, [revision, encodeObject(detail), cardId, channelId]); }, + setCardChannelItemUnsealedDetail: async (guid, cardId, channelId, revision, unsealed) => { + await db.current.executeSql(`UPDATE card_channel_${guid} set unsealed_detail=? where detail_revision=? AND card_id=? AND channel_id=?`, [encodeObject(unsealed), revision, cardId, channelId]); + }, setCardChannelItemSummary: async (guid, cardId, channelId, revision, summary) => { await db.current.executeSql(`UPDATE card_channel_${guid} set topic_revision=?, summary=?, unsealed_summary=null where card_id=? and channel_id=?`, [revision, encodeObject(summary), cardId, channelId]); }, @@ -320,7 +326,7 @@ export function useStoreContext() { }; }, getCardChannelItems: async (guid) => { - const values = await getAppValues(db.current, `SELECT card_id, channel_id, read_revision, sync_revision, revision, blocked, detail_revision, topic_revision, detail, summary FROM card_channel_${guid}`, []); + const values = await getAppValues(db.current, `SELECT card_id, channel_id, read_revision, sync_revision, revision, blocked, detail_revision, topic_revision, detail, unsealed_detail, summary, unsealed_summary FROM card_channel_${guid}`, []); return values.map(channel => ({ cardId: channel.card_id, channelId: channel.channel_id, @@ -341,7 +347,7 @@ export function useStoreContext() { }, getCardChannelTopicItems: async (guid, cardId, channelId) => { - const values = await getAppValues(db.current, `SELECT topic_id, revision, blocked, detail_revision, detail FROM card_channel_topic_${guid} WHERE card_id=? AND channel_id=?`, [cardId, channelId]); + const values = await getAppValues(db.current, `SELECT topic_id, revision, blocked, detail_revision, detail, unsealed_detail FROM card_channel_topic_${guid} WHERE card_id=? AND channel_id=?`, [cardId, channelId]); return values.map(topic => ({ topicId: topic.topic_id, revision: topic.revision, diff --git a/app/mobile/src/session/channels/channelItem/ChannelItem.jsx b/app/mobile/src/session/channels/channelItem/ChannelItem.jsx index 3dff621b..5b1742d3 100644 --- a/app/mobile/src/session/channels/channelItem/ChannelItem.jsx +++ b/app/mobile/src/session/channels/channelItem/ChannelItem.jsx @@ -3,6 +3,8 @@ import { TouchableOpacity } from 'react-native-gesture-handler'; import { Logo } from 'utils/Logo'; import { styles } from './ChannelItem.styled'; import { useChannelItem } from './useChannelItem.hook'; +import Colors from 'constants/Colors'; +import Ionicons from '@expo/vector-icons/MaterialCommunityIcons'; export function ChannelItem({ item, openConversation }) { @@ -12,7 +14,15 @@ export function ChannelItem({ item, openConversation }) { openConversation(item.cardId, item.channelId, item.revision)}> - { item.subject } + + { item.locked && !item.unlocked && ( + + )} + { item.locked && item.unlocked && ( + + )} + { item.subject } + { item.message } { item.updated && ( diff --git a/app/mobile/src/session/channels/channelItem/ChannelItem.styled.js b/app/mobile/src/session/channels/channelItem/ChannelItem.styled.js index 6ae51e84..5d629748 100644 --- a/app/mobile/src/session/channels/channelItem/ChannelItem.styled.js +++ b/app/mobile/src/session/channels/channelItem/ChannelItem.styled.js @@ -22,6 +22,13 @@ export const styles = StyleSheet.create({ flexShrink: 1, }, subject: { + display: 'flex', + flexDirection: 'row', + }, + subjectIcon: { + paddingRight: 4, + }, + subjectText: { color: Colors.text, fontSize: 14, }, diff --git a/app/mobile/src/session/channels/useChannels.hook.js b/app/mobile/src/session/channels/useChannels.hook.js index cb05e72b..13cfe81f 100644 --- a/app/mobile/src/session/channels/useChannels.hook.js +++ b/app/mobile/src/session/channels/useChannels.hook.js @@ -133,8 +133,32 @@ export function useChannels() { logo = 'appstore'; } + let locked = false; + let unlocked = false; let subject = null; - if (item?.detail?.data) { + if (item?.detail?.dataType === 'sealed') { + locked = true; + if (state.sealable) { + try { + if (item.unsealedDetail == null) { + if (item.cardId) { + card.actions.unsealChannelSubject(item.cardId, item.channelId, item.detailRevision, account.state.sealKey); + } + else { + channel.actions.unsealChannelSubject(item.channelId, item.detailRevision, account.state.sealKey); + } + } + else { + unlocked = true; + subject = item.unsealedDetail.subject; + } + } + catch (err) { + console.log(err) + } + } + } + else { try { subject = JSON.parse(item?.detail?.data).subject; } @@ -170,7 +194,7 @@ export function useChannels() { } } - return { cardId: item.cardId, channelId: item.channelId, contacts, logo, subject, message, updated, revision: item.revision, timestamp: created, blocked: item.blocked === 1 }; + return { cardId: item.cardId, channelId: item.channelId, contacts, logo, subject, locked, unlocked, message, updated, revision: item.revision, timestamp: created, blocked: item.blocked === 1 }; } useEffect(() => { @@ -214,7 +238,7 @@ export function useChannels() { }); updateState({ channels: sorted }); - }, [channel, card, state.filter]); + }, [channel, card, state.filter, state.sealable]); const actions = { setSealed: (sealed) => {