implemented add new topic modal

This commit is contained in:
balzack 2024-11-12 20:55:19 -08:00
parent ae9f737fa3
commit 43daaabcb1
13 changed files with 141 additions and 85 deletions

View File

@ -483,7 +483,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.30;
MARKETING_VERSION = 1.31;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@ -515,7 +515,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.30;
MARKETING_VERSION = 1.31;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",

View File

@ -17,8 +17,7 @@
"@react-navigation/native": "^6.1.18",
"@react-navigation/native-stack": "^6.11.0",
"@react-navigation/stack": "^6.4.1",
"@types/crypto-js": "^4.2.2",
"crypto-js": "^4.2.0",
"crypto-js": "^3.3.0",
"databag-client-sdk": "^0.0.22",
"jsencrypt": "^3.3.2",
"react": "18.2.0",

View File

@ -2,13 +2,12 @@ import {Crypto} from 'databag-client-sdk';
import CryptoJS from 'crypto-js';
import {JSEncrypt} from 'jsencrypt';
import {RSA} from 'react-native-rsa-native';
import {generateSecureRandom} from 'react-native-securerandom';
export class NativeCrypto implements Crypto {
// generate salt for pbk function
public async pbkdfSalt(): Promise<{saltHex: string}> {
const salt = await generateSecureRandom(16);
const saltHex = this.uint8ToHexStr(salt);
const salt = CryptoJS.lib.WordArray.random(128 / 8);
const saltHex = salt.toString();
return {saltHex};
}
@ -26,15 +25,15 @@ export class NativeCrypto implements Crypto {
// generate random aes key
public async aesKey(): Promise<{aesKeyHex: string}> {
const aesKey = await generateSecureRandom(32);
const aesKeyHex = this.uint8ToHexStr(aesKey);
const aesKey = CryptoJS.lib.WordArray.random(256 / 8);
const aesKeyHex = aesKey.toString();
return {aesKeyHex};
}
// generate iv to use to aes function
public async aesIv(): Promise<{ivHex: string}> {
const iv = await generateSecureRandom(16);
const ivHex = this.uint8ToHexStr(iv);
const iv = CryptoJS.lib.WordArray.random(128 / 8);
const ivHex = iv.toString();
return {ivHex};
}

View File

@ -42,6 +42,7 @@ export const styles = StyleSheet.create({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'transparent',
},
split: {
display: 'flex',

View File

@ -29,6 +29,7 @@ export const styles = StyleSheet.create({
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
flexShrink: 1,
paddingLeft: 8,
paddingRight: 8,
},

View File

@ -27,11 +27,13 @@ export function Card({
<SafeAreaView style={styles.card}>
<Image style={styles.image} resizeMode={'contain'} source={{uri: imageUrl}} />
<View style={styles.details}>
{name && <Text style={styles.nameSet}>{name}</Text>}
{!name && <Text style={styles.nameUnset}>{placeholder}</Text>}
<Text style={styles.handle}>{node ? `${handle}/${node}` : handle}</Text>
{name && <Text numberOfLines={1} style={styles.nameSet}>{name}</Text>}
{!name && <Text numberOfLines={1} style={styles.nameUnset}>{placeholder}</Text>}
<Text numberOfLines={1} style={styles.handle}>{node ? `${handle}/${node}` : handle}</Text>
</View>
<View>
{actions}
</View>
{actions}
</SafeAreaView>
</Pressable>
);

View File

@ -5,6 +5,8 @@ export const en = {
sealed: 'Sealed',
notes: 'Notes',
server: 'Server',
token: 'Token',
delayMessage: 'Key generation can take several minutes.',
code: 'en',
settings: 'Settings',
@ -256,6 +258,8 @@ export const fr = {
sealed: 'Scellé',
notes: 'Notes',
server: 'Serveur',
token: 'Code',
delayMessage: 'La génération de clé peut prendre plusieurs minutes.',
code: 'fr',
settings: 'Paramètres',
@ -508,7 +512,9 @@ export const sp = {
sealed: 'Sellado',
notes: 'Notas',
server: 'Server',
token: 'Código',
delayMessage: 'La generación de claves puede tardar varios minutos.',
code: 'sp',
settings: 'Configuración',
contacts: 'Contactos',
@ -759,7 +765,9 @@ export const pt = {
sealed: 'Selado',
notes: 'Notas',
server: 'Servidor',
token: 'Code',
delayMessage: 'A geração da chave pode levar vários minutos.',
code: 'pt',
settings: 'Configurações',
contacts: 'Contatos',
@ -1010,6 +1018,8 @@ export const de = {
sealed: 'Versiegelt',
notes: 'Notizen',
server: 'Servierer',
token: 'Token',
delayMessage: 'Die Schlüsselgenerierung kann mehrere Minuten dauern.',
code: 'de',
settings: 'Einstellungen',
@ -1261,6 +1271,8 @@ export const ru = {
sealed: 'Запечатано',
notes: 'Заметки',
server: 'Сервер',
token: 'Токен',
delayMessage: 'Генерация ключа может занять несколько минут.',
code: 'ru',
settings: 'Настройки',

View File

@ -7,6 +7,7 @@ import {Channel} from '../channel/Channel';
import {Focus} from 'databag-client-sdk';
import {BlurView} from '@react-native-community/blur';
import {Card} from '../card/Card';
import {Confirm} from '../confirm/Confirm';
export function Content({select}: {select: (focus: Focus) => void}) {
const [add, setAdd] = useState(false);
@ -14,10 +15,32 @@ export function Content({select}: {select: (focus: Focus) => void}) {
const [sealed, setSealed] = useState(false);
const {state, actions} = useContent();
const theme = useTheme();
const members = useRef(new Set<string>());
const [subject, setSubject] = useState('');
const [members, setMembers] = useState([]);
const [alert, setAlert] = useState(false);
const [alertParams] = useState({
title: state.strings.operationFailed,
prompt: state.strings.tryAgain,
confirm: {
label: state.strings.ok,
action: () => setAlert(false),
},
});
const addTopic = async () => {
console.log('add topic');
setAdding(true);
try {
await actions.addTopic(sealed, subject, members);
setAdd(false);
setSubject('');
setMembers([]);
setSealed(false);
} catch (err) {
console.log(err);
setAdd(false);
setAlert(true);
}
setAdding(false);
};
const cards = sealed ? state.sealable : state.connected;
@ -119,49 +142,45 @@ export function Content({select}: {select: (focus: Focus) => void}) {
underlineStyle={styles.inputUnderline}
placeholder={state.strings.subjectOptional}
left={<TextInput.Icon style={styles.icon} icon="label-outline" />}
value={state.topic}
onChangeText={value => actions.setTopic(value)}
value={subject}
onChangeText={value => setSubject(value)}
/>
<Divider style={styles.modalDivider} />
</Surface>
<View style={styles.membersContainer}>
<Divider style={styles.modalDivider} />
<Surface elevation={0} mode="flat" style={styles.members}>
<FlatList
style={styles.cards}
data={cards}
initialNumToRender={32}
renderItem={({item}) => {
const enable = (<Switch style={styles.sealSwitch} value={members.current.has(item.guid)} onValueChange={flag => {
if (flag) {
members.current.add(item.guid);
} else {
members.current.delete(item.guid);
}
}} />)
return (
<Card
containerStyle={{
...styles.card,
borderColor: theme.colors.outlineVariant,
}}
imageUrl={item.imageUrl}
name={item.name}
handle={item.handle}
node={item.node}
placeholder={state.strings.name}
actions={[enable]}
/>
);
}}
keyExtractor={card => card.cardId}
/>
</Surface>
<Divider style={styles.modalDivider} />
<Divider style={styles.modalDivider} />
<Surface elevation={0} mode="flat" style={styles.members}>
<FlatList
style={styles.cards}
data={cards}
initialNumToRender={32}
renderItem={({item}) => {
const enable = (<Switch key="enable" style={styles.sealSwitch} value={Boolean(members.find(cardId => cardId === item.cardId))} onValueChange={flag => {
if (flag) {
setMembers([ item.cardId, ...members ]);
} else {
setMembers(members.filter(cardId => cardId != item.cardId));
}
}} />)
return (
<Card
containerStyle={{
...styles.card,
borderColor: theme.colors.outlineVariant,
}}
imageUrl={item.imageUrl}
name={item.name}
handle={item.handle}
node={item.node}
placeholder={state.strings.name}
actions={[enable]}
/>
);
}}
keyExtractor={card => card.cardId}
/>
</Surface>
<Divider style={styles.modalDivider} />
</View>
<View style={styles.addControls}>
<View style={styles.sealable}>
@ -183,6 +202,7 @@ export function Content({select}: {select: (focus: Focus) => void}) {
</View>
</View>
</Modal>
<Confirm show={alert} params={alertParams} />
</View>
);
}

View File

@ -56,7 +56,7 @@ export function useContent() {
useEffect(() => {
const channels = state.sorted.map(channel => {
const {cardId, channelId, unread, sealed, members, data, lastTopic} = channel;
const {cardId, channelId, unread, sealed, members, dataType, data, lastTopic} = channel;
const contacts = [] as (Card | undefined)[];
if (cardId) {
const card = state.cards.find(contact => contact.cardId === cardId);
@ -110,7 +110,7 @@ export function useContent() {
};
const getMessage = () => {
if (!lastTopic) {
if (!lastTopic || !lastTopic.status) {
return '';
}
if (lastTopic.dataType === 'superbasictopic') {
@ -219,9 +219,6 @@ export function useContent() {
}, []);
const actions = {
addChannel: () => {
console.log('add channel');
},
setFilter: (filter: string) => {
updateState({filter});
},
@ -231,6 +228,15 @@ export function useContent() {
getFocus: (cardId: string | null, channelId: string) => {
return app.state.session.setFocus(cardId, channelId);
},
addTopic: async (sealed: boolean, subject: string, contacts: string[]) => {
const content = app.state.session.getContent();
await new Promise(r => setTimeout(r, 2000));
if (sealed) {
await content.addChannel(true, 'sealed', {subject}, contacts);
} else {
await content.addChannel(false, 'superbasic', {subject}, contacts);
}
},
};
return {state, actions};

View File

@ -747,7 +747,7 @@ export function Settings({showLogout}: {showLogout: boolean}) {
)}
{!state.config.sealSet && (
<>
<Text style={styles.modalDescription}>{state.strings.sealUnset}</Text>
<Text style={styles.modalDescription}>{state.strings.sealCreate}</Text>
<Text style={styles.modalDescription}>{state.strings.delayMessage}</Text>
<TextInput
style={styles.input}

View File

@ -2833,13 +2833,6 @@ __metadata:
languageName: node
linkType: hard
"@types/crypto-js@npm:^4.2.2":
version: 4.2.2
resolution: "@types/crypto-js@npm:4.2.2"
checksum: 727daa0d2db35f0abefbab865c23213b6ee6a270e27e177939bbe4b70d1e84c2202d9fac4ea84859c4b4d49a4ee50f948f601327a39b69ec013288018ba07ca5
languageName: node
linkType: hard
"@types/graceful-fs@npm:^4.1.3":
version: 4.1.9
resolution: "@types/graceful-fs@npm:4.1.9"
@ -3192,11 +3185,10 @@ __metadata:
"@react-navigation/native": ^6.1.18
"@react-navigation/native-stack": ^6.11.0
"@react-navigation/stack": ^6.4.1
"@types/crypto-js": ^4.2.2
"@types/react": ^18.2.6
"@types/react-test-renderer": ^18.0.0
babel-jest: ^29.6.3
crypto-js: ^4.2.0
crypto-js: ^3.3.0
databag-client-sdk: ^0.0.22
eslint: ^8.19.0
eslint-plugin-prettier: 5.0.0
@ -4229,10 +4221,10 @@ __metadata:
languageName: node
linkType: hard
"crypto-js@npm:^4.2.0":
version: 4.2.0
resolution: "crypto-js@npm:4.2.0"
checksum: f051666dbc077c8324777f44fbd3aaea2986f198fe85092535130d17026c7c2ccf2d23ee5b29b36f7a4a07312db2fae23c9094b644cc35f7858b1b4fcaf27774
"crypto-js@npm:^3.3.0":
version: 3.3.0
resolution: "crypto-js@npm:3.3.0"
checksum: 193923143a4784b2f974366068d96fe8280168fd3fef2bfea9551a5c3e32096f5a8fa49ff4eeb5bd0b9716d325618d38cfbe6125e359a4ef488fbca93e600824
languageName: node
linkType: hard

View File

@ -1,7 +1,12 @@
import {NativeModules, Platform} from 'react-native';
export const en = {
unknown: 'Unknown',
sealed: 'Sealed',
notes: 'Notes',
server: 'Server',
token: 'Token',
delayMessage: 'Key generation can take several minutes.',
code: 'en',
settings: 'Settings',
@ -246,12 +251,16 @@ export const en = {
disable: 'Disable',
confirmDisable: 'Disabling Multi-Factor Authentication',
disablePrompt: 'Are you sure you want to disable multi-factor authentication',
}
};
export const fr = {
unknown: 'Inconnu',
sealed: 'Scellé',
notes: 'Notes',
server: 'Serveur',
token: 'Code',
delayMessage: 'La génération de clé peut prendre plusieurs minutes.',
code: 'fr',
settings: 'Paramètres',
contacts: 'Contacts',
@ -496,12 +505,15 @@ export const fr = {
disable: 'Désactiver',
confirmDisable: "Désactivation de l'authentification multi-facteurs",
disablePrompt: "Êtes-vous sûr de vouloir désactiver l'authentification multi-facteurs",
}
};
export const sp = {
unknown: 'Desconocido',
sealed: 'Sellado',
notes: 'Notas',
server: 'Server',
token: 'Código',
delayMessage: 'La generación de claves puede tardar varios minutos.',
code: 'sp',
settings: 'Configuración',
@ -746,13 +758,16 @@ export const sp = {
disable: 'Desactivar',
confirmDisable: 'Desactivación de la autenticación de dos factores',
disablePrompt: '¿Estás seguro de que quieres desactivar la autenticación de dos factores?',
}
};
export const pt = {
unknown: 'Desconhecido',
sealed: 'Selado',
notes: 'Notas',
server: 'Servidor',
token: 'Code',
delayMessage: 'A geração da chave pode levar vários minutos.',
code: 'pt',
settings: 'Configurações',
contacts: 'Contatos',
@ -996,12 +1011,15 @@ export const pt = {
disable: 'Desativar',
confirmDisable: 'Desativando Autenticação de Dois Fatores',
disablePrompt: 'Tem certeza de que deseja desativar a autenticação de dois fatores?',
}
};
export const de = {
unknown: 'Unbekannt',
sealed: 'Versiegelt',
notes: 'Notizen',
server: 'Servierer',
token: 'Token',
delayMessage: 'Die Schlüsselgenerierung kann mehrere Minuten dauern.',
code: 'de',
settings: 'Einstellungen',
@ -1246,12 +1264,15 @@ export const de = {
disable: 'Deaktivieren',
confirmDisable: 'Deaktivierung der Zwei-Faktor-Authentifizierung',
disablePrompt: 'Sind Sie sicher, dass Sie die Zwei-Faktor-Authentifizierung deaktivieren möchten?',
}
};
export const ru = {
unknown: 'Неизвестно',
sealed: 'Запечатано',
notes: 'Заметки',
server: 'Сервер',
token: 'Токен',
delayMessage: 'Генерация ключа может занять несколько минут.',
code: 'ru',
settings: 'Настройки',
@ -1496,4 +1517,5 @@ export const ru = {
disable: 'Отключить',
confirmDisable: 'Отключение двухфакторной аутентификации',
disablePrompt: 'Вы уверены, что хотите отключить двухфакторную аутентификацию?',
}
};

View File

@ -247,10 +247,11 @@ export class StreamModule {
throw new Error('seal not set');
}
const sealKey = await crypto.rsaEncrypt(aesKeyHex, seal.publicKey);
const hostSeal = { publicKey: seal.publicKey, sealedKey: sealKey.encryptedDataB64 };
const { ivHex } = await crypto.aesIv();
const subjectData = JSON.stringify(subject);
const { encryptedDataB64 } = await crypto.aesEncrypt(subjectData, ivHex, aesKeyHex);
const sealedSubject = { subjectEncrypted: encryptedDataB64, subjectIv: ivHex, seals: [...seals, sealKey] };
const sealedSubject = { subjectEncrypted: encryptedDataB64, subjectIv: ivHex, seals: [...seals, hostSeal] };
return await addChannel(node, secure, token, type, sealedSubject, cardIds);
}
@ -497,6 +498,7 @@ export class StreamModule {
if (!item.channelKey) {
item.channelKey = await this.getChannelKey(seals);
}
if (item.channelKey) {
const { data } = await this.crypto.aesDecrypt(subjectEncrypted, subjectIv, item.channelKey);
item.unsealedDetail = data;