support updating subject on sealed channels in webapp

This commit is contained in:
Roland Osborne 2022-12-14 12:18:48 -08:00
parent fd086e2333
commit 40dc395dff
5 changed files with 90 additions and 23 deletions

View File

@ -1,8 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function setChannelSubject(token, channelId, subject ) {
let data = { subject };
let params = { dataType: 'superbasic', data: JSON.stringify(data) };
export async function setChannelSubject(token, channelId, dataType, data ) {
let params = { dataType, data: JSON.stringify(data) };
let channel = await fetchWithTimeout(`/content/channels/${channelId}/subject?agent=${token}`, { method: 'PUT', body: JSON.stringify(params)} );
checkResponse(channel);
return await channel.json();

View File

@ -48,7 +48,7 @@ export function useChannelContext() {
else {
let detail = await getChannelDetail(access.current, channel.id);
cur.data.channelDetail = detail;
cur.data.unsealedSubject = null;
cur.data.unsealedChannel = null;
}
cur.data.detailRevision = channel.data.detailRevision;
}
@ -151,7 +151,27 @@ export function useChannelContext() {
});
},
setChannelSubject: async (channelId, subject) => {
return await setChannelSubject(access.current, channelId, subject);
return await setChannelSubject(access.current, channelId, 'superbasic', { subject });
},
setChannelSealedSubject: async (channelId, subject, sealKey) => {
const channel = channels.current.get(channelId);
let { seals, subjectEncrypted, subjectIv } = JSON.parse(channel.data.channelDetail.data);
seals.forEach(seal => {
if (seal.publicKey === sealKey.public) {
let crypto = new JSEncrypt();
crypto.setPrivateKey(sealKey.private);
const unsealedKey = crypto.decrypt(seal.sealedKey);
const key = CryptoJS.enc.Hex.parse(unsealedKey);
const iv = CryptoJS.lib.WordArray.random(128 / 8);
const encrypted = CryptoJS.AES.encrypt(JSON.stringify({ subject }), key, { iv: iv });
subjectEncrypted = encrypted.ciphertext.toString(CryptoJS.enc.Base64)
subjectIv = iv.toString();
}
});
const data = { subjectEncrypted, subjectIv, seals };
return await setChannelSubject(access.current, channelId, 'sealed', data);
},
setChannelCard: async (channelId, cardId) => {
return await setChannelCard(access.current, channelId, cardId);

View File

@ -7,6 +7,7 @@ import { EditOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { CardSelect } from '../cardSelect/CardSelect';
import { EditSubject } from './editSubject/EditSubject';
import { EditMembers } from './editMembers/EditMembers';
import { UnlockOutlined, LockFilled } from '@ant-design/icons';
export function Details({ cardId, channelId, closeDetails, closeConversation, openContact }) {
@ -117,15 +118,31 @@ export function Details({ cardId, channelId, closeDetails, closeConversation, op
</div>
<div class="stats">
{ state.host && (
<div class="subject edit" onClick={actions.setEditSubject}>
<Space>
<div>{ state.subject }</div>
<EditOutlined />
</Space>
<div class="subject" onClick={actions.setEditSubject}>
{ state.locked && !state.unlocked && (
<LockFilled style={{ paddingRight: 4 }} />
)}
{ state.locked && state.unlocked && (
<UnlockOutlined style={{ paddingRight: 4 }} />
)}
<span>{ state.subject }</span>
{ state.editable && (
<span class="edit" onClick={actions.setEditSubject}>
<EditOutlined style={{ paddingLeft: 4 }}/>
</span>
)}
</div>
)}
{ !state.host && (
<div class="subject">{ state.subject }</div>
<div class="subject">
{ state.locked && !state.unlocked && (
<LockFilled style={{ paddingRight: 4 }} />
)}
{ state.locked && state.unlocked && (
<UnlockOutlined style={{ paddingRight: 4 }} />
)}
<span>{ state.subject }</span>
</div>
)}
{ state.host && (
<div class="host">host</div>
@ -139,7 +156,7 @@ export function Details({ cardId, channelId, closeDetails, closeConversation, op
{ state.host && (
<div class="button" onClick={deleteChannel}>Delete Topic</div>
)}
{ state.host && (
{ state.host && !state.locked && (
<div class="button" onClick={actions.setEditMembers}>Edit Membership</div>
)}
{ !state.host && (

View File

@ -90,9 +90,6 @@ export const DetailsWrapper = styled.div`
padding-left: 16px;
.edit {
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
}

View File

@ -1,6 +1,7 @@
import { useContext, useState, useEffect } from 'react';
import { CardContext } from 'context/CardContext';
import { ChannelContext } from 'context/ChannelContext';
import { AccountContext } from 'context/AccountContext';
import { ViewportContext } from 'context/ViewportContext';
export function useDetails(cardId, channelId) {
@ -20,9 +21,13 @@ export function useDetails(cardId, channelId) {
subjectUpdate: null,
unknown: 0,
display: null,
locked: false,
unlocked: false,
editable: false,
});
const card = useContext(CardContext);
const account = useContext(AccountContext);
const channel = useContext(ChannelContext);
const viewport = useContext(ViewportContext);
@ -35,7 +40,7 @@ export function useDetails(cardId, channelId) {
}, [viewport]);
useEffect(() => {
let img, subject, subjectUpdate, host, started, contacts
let img, subject, subjectUpdate, host, started, contacts, locked, unlocked, editable;
let chan;
if (cardId) {
const cardChan = card.state.cards.get(cardId);
@ -60,11 +65,35 @@ export function useDetails(cardId, channelId) {
img = 'team';
subject = 'Conversation'
}
if (chan.data.channelDetail.dataType === 'sealed') {
editable = false;
try {
const seals = JSON.parse(chan.data.channelDetail.data).seals;
seals.forEach(seal => {
if (account.state.sealKey.public === seal.publicKey) {
editable = true;
}
});
}
catch (err) {
console.log(err);
}
locked = true;
unlocked = chan.data.unsealedChannel != null;
if (chan.data.unsealedChannel?.subject) {
subject = chan.data.unsealedChannel.subject;
subjectUpdate = subject;
}
}
else {
locked = false;
editable = (chan.cardId == null);
const parsed = JSON.parse(chan.data.channelDetail.data);
if (parsed.subject) {
subject = parsed.subject;
subjectUpdate = subject;
}
}
const date = new Date(chan.data.channelDetail.created * 1000);
const now = new Date();
if(now.getTime() - date.getTime() < 86400000) {
@ -96,8 +125,8 @@ export function useDetails(cardId, channelId) {
}
});
updateState({ img, subject, host, started, contacts, members, unknown, subjectUpdate });
}, [cardId, channelId, card, channel]);
updateState({ img, subject, host, started, contacts, members, unknown, subjectUpdate, locked, unlocked, editable });
}, [cardId, channelId, card, channel, account]);
const actions = {
setEditSubject: () => {
@ -113,7 +142,12 @@ export function useDetails(cardId, channelId) {
if (!state.busy) {
try {
updateState({ busy: true });
if (state.locked) {
channel.actions.setChannelSealedSubject(channelId, state.subjectUpdate, account.state.sealKey);
}
else {
channel.actions.setChannelSubject(channelId, state.subjectUpdate);
}
updateState({ busy: false });
}
catch(err) {