diff --git a/app/mobile/ios/Databag.xcodeproj/project.pbxproj b/app/mobile/ios/Databag.xcodeproj/project.pbxproj index e18215b7..d6343f17 100644 --- a/app/mobile/ios/Databag.xcodeproj/project.pbxproj +++ b/app/mobile/ios/Databag.xcodeproj/project.pbxproj @@ -567,7 +567,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -639,7 +639,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; diff --git a/app/mobile/package.json b/app/mobile/package.json index ae430929..fdf29b17 100644 --- a/app/mobile/package.json +++ b/app/mobile/package.json @@ -19,7 +19,8 @@ "@react-navigation/native-stack": "^6.9.10", "@react-navigation/stack": "^6.3.14", "axios": "^1.3.3", - "crypto-js": "^4.1.1", + "crypto-js": "^3.3.0", + "jsencrypt": "^3.3.1", "moment": "^2.29.4", "react": "18.2.0", "react-native": "0.71.3", diff --git a/app/mobile/src/context/sealUtil.js b/app/mobile/src/context/sealUtil.js index d40310ec..eb54af50 100644 --- a/app/mobile/src/context/sealUtil.js +++ b/app/mobile/src/context/sealUtil.js @@ -16,7 +16,7 @@ export function isUnsealed(seals, sealKey) { return false; } -export function getContentKey(seals, sealKey) { +export async function getContentKey(seals, sealKey) { for (let i = 0; i < seals?.length; i++) { if (seals[i].publicKey === sealKey.public) { const seal = seals[i]; diff --git a/app/mobile/src/session/profile/Profile.jsx b/app/mobile/src/session/profile/Profile.jsx index fad470ca..acf46d83 100644 --- a/app/mobile/src/session/profile/Profile.jsx +++ b/app/mobile/src/session/profile/Profile.jsx @@ -88,7 +88,7 @@ export function ProfileBody() { const saveSeal = async () => { try { await actions.saveSeal(); - actions.hideSealEdit(); + actions.hideEditSeal(); } catch (err) { console.log(err); @@ -102,7 +102,7 @@ export function ProfileBody() { const saveDetails = async () => { try { await actions.saveDetails(); - actions.hideDetailEdit(); + actions.hideEditDetails(); } catch (err) { console.log(err); @@ -116,7 +116,7 @@ export function ProfileBody() { const saveLogin = async () => { try { await actions.saveLogin(); - actions.hideLoginEdit(); + actions.hideEditLogin(); } catch (err) { console.log(err); @@ -179,17 +179,19 @@ export function ProfileBody() { - - setVisible(!state.searchable)} activeOpacity={1}> - Visible in Registry - - - - - setNotifications(!state.pushEnabled)} activeOpacity={1}> - Enable Notifications - - + + + setVisible(!state.searchable)} activeOpacity={1}> + Visible in Registry + + + + + setNotifications(!state.pushEnabled)} activeOpacity={1}> + Enable Notifications + + + @@ -197,7 +199,7 @@ export function ProfileBody() { Logout - + Change Login @@ -361,16 +363,16 @@ export function ProfileBody() { transparent={true} visible={state.editSeal} supportedOrientations={['portrait', 'landscape']} - onRequestClose={actions.hideSealEdit} + onRequestClose={actions.hideEditSeal} > Sealed Topics: - actions.setSealable(!state.sealable)} activeOpacity={1}> + actions.setSealEnable(!state.sealEnabled)} activeOpacity={1}> Enable Sealed Topics - + { state.sealMode === 'unlocking' && ( <> @@ -438,7 +440,7 @@ export function ProfileBody() { )} - saving can take a minute + saving can take a few minutes )} { state.sealMode === 'disabling' && ( @@ -457,23 +459,32 @@ export function ProfileBody() { )} - + Cancel { state.canSaveSeal && ( <> { state.sealMode !== 'unlocking' && state.sealMode !== 'unlocked' && ( + { state.saving && ( + + )} Save )} { state.sealMode === 'unlocked' && ( + { state.saving && ( + + )} Forget )} { state.sealMode === 'unlocking' && ( + { state.saving && ( + + )} Unlock )} @@ -483,11 +494,17 @@ export function ProfileBody() { <> { state.sealMode !== 'unlocking' && ( + { state.saving && ( + + )} Save )} { state.sealMode === 'unlocking' && ( + { state.saving && ( + + )} Unlock )} @@ -503,7 +520,7 @@ export function ProfileBody() { transparent={true} visible={state.editLogin} supportedOrientations={['portrait', 'landscape']} - onRequestClose={actions.hideLoginEdit} + onRequestClose={actions.hideEditLogin} > @@ -559,7 +576,7 @@ export function ProfileBody() { )} - + Cancel { (state.checked && state.available && state.editConfirm === state.editPassword && state.editPassword) && ( diff --git a/app/mobile/src/session/profile/Profile.styled.js b/app/mobile/src/session/profile/Profile.styled.js index 54950226..a21125f6 100644 --- a/app/mobile/src/session/profile/Profile.styled.js +++ b/app/mobile/src/session/profile/Profile.styled.js @@ -10,6 +10,10 @@ export const styles = StyleSheet.create({ button: { paddingRight: 16, }, + switch: { + false: Colors.grey, + true: Colors.background, + }, headerText: { fontSize: 18, overflow: 'hidden', @@ -31,7 +35,7 @@ export const styles = StyleSheet.create({ color: Colors.alert, }, logout: { - marginTop: 8, + marginTop: 16, display: 'flex', flexDirection: 'row', alignItems: 'center', @@ -41,9 +45,10 @@ export const styles = StyleSheet.create({ logoutText: { marginLeft: 8, color: Colors.primary, + fontSize: 16, }, delete: { - marginTop: 8, + marginTop: 16, display: 'flex', flexDirection: 'row', alignItems: 'center', @@ -53,6 +58,7 @@ export const styles = StyleSheet.create({ deleteText: { marginLeft: 8, color: Colors.alert, + fontSize: 16, }, modalWrapper: { display: 'flex', @@ -89,7 +95,7 @@ export const styles = StyleSheet.create({ borderRadius: 4, padding: 8, marginRight: 8, - width: 72, + width: 88, display: 'flex', alignItems: 'center', }, @@ -114,6 +120,10 @@ export const styles = StyleSheet.create({ removeText: { color: Colors.white, }, + input: { + fontSize: 14, + flexGrow: 1, + }, inputField: { width: '100%', borderWidth: 1, @@ -184,9 +194,11 @@ export const styles = StyleSheet.create({ padding: 8, borderRadius: 4, backgroundColor: Colors.primary, - width: 72, + width: 88, display: 'flex', + flexDirection: 'row', alignItems: 'center', + justifyContent: 'center', }, saveText: { color: Colors.white, @@ -202,10 +214,14 @@ export const styles = StyleSheet.create({ flexDirection: 'row', }, blockedLabel: { - marginTop: 24, + marginTop: 32, alignSelf: 'center', color: Colors.grey, }, + group: { + marginTop: 16, + marginBottom: 16, + }, enable: { display: 'flex', flexDirection: 'row', @@ -213,10 +229,11 @@ export const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', width: '100%', - marginTop: 8, + marginBottom: 4, }, enableText: { color: Colors.primary, + fontSize: 16, }, enableSwitch: { transform: [{ scaleX: .6 }, { scaleY: .6 }], @@ -238,5 +255,45 @@ export const styles = StyleSheet.create({ display: 'flex', alignItems: 'center', }, + sealable: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + paddingBottom: 16, + }, + disabled: { + borderWidth: 1, + borderColor: Colors.lightgrey, + padding: 8, + borderRadius: 4, + width: 88, + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + activity: { + paddingRight: 4, + }, + disabledText: { + color: Colors.disabled, + }, + sealUpdate: { + position: 'absolute', + top: 0, + height: 36, + left: 8, + width: '100%', + }, + notice: { + color: Colors.grey, + fontStyle: 'italic', + textAlign: 'center', + paddingBottom: 8, + }, + warn: { + paddingTop: 2, + paddingRight: 8, + }, }); diff --git a/app/mobile/src/session/profile/blockedMessages/useBlockedMessages.hook.js b/app/mobile/src/session/profile/blockedMessages/useBlockedMessages.hook.js index 69f3053d..dec98703 100644 --- a/app/mobile/src/session/profile/blockedMessages/useBlockedMessages.hook.js +++ b/app/mobile/src/session/profile/blockedMessages/useBlockedMessages.hook.js @@ -79,8 +79,8 @@ export function useBlockedMessages() { }); }); + const merged = []; for(let i = 0; i < channels.length; i++) { - const merged = []; let topics; const { cardId, channelId } = channels[i]; if (cardId) { diff --git a/app/mobile/src/session/profile/useProfile.hook.js b/app/mobile/src/session/profile/useProfile.hook.js index 3afcd3d1..b338ae77 100644 --- a/app/mobile/src/session/profile/useProfile.hook.js +++ b/app/mobile/src/session/profile/useProfile.hook.js @@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { ProfileContext } from 'context/ProfileContext'; import { AccountContext } from 'context/AccountContext'; import { AppContext } from 'context/AppContext'; +import { generateSeal, updateSeal, unlockSeal } from 'context/sealUtil'; export function useProfile() { @@ -25,6 +26,30 @@ export function useProfile() { blockedMessages: false, logginOut: false, disconnected: false, + pushEnabled: false, + searchable: false, + sealableFalse: false, + editPassword: null, + editConfirm: null, + showPassword: false, + showConfirm: false, + saving: false, + checked: true, + available: true, + + seal: null, + sealKey: null, + sealEnabled: false, + sealUnlocked: false, + sealMode: null, + sealUnlock: null, + sealPassword: null, + sealConfirm: null, + sealDelete: null, + canSaveSeal: false, + showSealUnlock: false, + showSealConfirm: false, + showSealPassword: false, }); const app = useContext(AppContext); @@ -32,10 +57,54 @@ export function useProfile() { const profile = useContext(ProfileContext); const navigate = useNavigate(); + const debounce = useRef(); + const updateState = (value) => { setState((s) => ({ ...s, ...value })); } + const unlock = async () => { + const sealKey = unlockSeal(state.seal, state.sealUnlock); + await account.actions.unlockAccountSeal(sealKey); + }; + + const forget = async () => { + await account.actions.unlockAccountSeal({}); + } + + const update = async () => { + const updated = updateSeal(state.seal, state.sealKey, state.sealPassword); + await account.actions.setAccountSeal(updated.seal, updated.sealKey); + } + + const enable = async () => { + const created = await generateSeal(state.sealPassword); + await account.actions.setAccountSeal(created.seal, created.sealKey); + } + + const disable = async () => { + await account.actions.setAccountSeal({}, {}); + } + + useEffect(() => { + if (state.sealMode === 'unlocked') { + return updateState({ canSaveSeal: true }); + } + if (state.sealMode === 'unlocking' && state.sealUnlock != null && state.sealUnlock !== '') { + return updateState({ canSaveSeal: true }); + } + if (state.sealMode === 'enabling' && state.sealPassword != null && state.sealPassword === state.sealConfirm) { + return updateState({ canSaveSeal: true }); + } + if (state.sealMode === 'disabling' && state.sealDelete === 'delete') { + return updateState({ canSaveSeal: true }); + } + if (state.sealMode === 'updating' && state.sealPassword != null && state.sealPassword === state.sealConfirm) { + return updateState({ canSaveSeal: true }); + } + updateState({ canSaveSeal: false }); + }, [state.sealMode, state.sealable, state.sealUnlock, state.sealPassword, state.sealConfirm, state.sealDelete]); + useEffect(() => { const { name, handle, node, location, description, image } = profile.state.identity; const imageSource = image ? profile.state.imageUrl : 'avatar'; @@ -44,8 +113,11 @@ export function useProfile() { }, [profile.state]); useEffect(() => { - const { sealable } = account.state.status || {}; - updateState({ sealable }); + const { searchable, pushEnabled, seal, sealable } = account.state.status; + const sealKey = account.state.sealKey; + const sealEnabled = seal?.publicKey != null; + const sealUnlocked = seal?.publicKey === sealKey?.public && sealKey?.private && sealKey?.public; + updateState({ searchable, sealable, pushEnabled, seal, sealKey, sealEnabled, sealUnlocked }); }, [account.state]); useEffect(() => { @@ -79,13 +151,24 @@ export function useProfile() { updateState({ editDetails: false }); }, showEditLogin: () => { - updateState({ editLogin: true }); + updateState({ editLogin: true, editPassword: null, editConfirm: null }); }, hideEditLogin: () => { updateState({ editLogin: false }); }, showEditSeal: () => { - updateState({ editSeal: true }); + let sealMode = null; + const sealable = state.sealEnabled; + if (state.sealEnabled && !state.sealUnlocked) { + sealMode = 'unlocking'; + } + else if (state.sealEnabled && state.sealUnlocked) { + sealMode = 'unlocked'; + } + else { + sealMode = 'disabled'; + } + updateState({ editSeal: true, sealMode, sealUnlock: null, sealPassword: null, sealConfirm: null, sealDelete: null }); }, hideEditSeal: () => { updateState({ editSeal: false }); @@ -117,6 +200,147 @@ export function useProfile() { setEditDescription: (editDescription) => { updateState({ editDescription }); }, + setSealUnlock: (sealUnlock) => { + updateState({ sealUnlock }); + }, + setSealPassword: (sealPassword) => { + updateState({ sealPassword }); + }, + setSealConfirm: (sealConfirm) => { + updateState({ sealConfirm }); + }, + setSealDelete: (sealDelete) => { + updateState({ sealDelete }); + }, + showSealUnlock: () => { + updateState({ showSealUnlock: true }); + }, + hideSealUnlock: () => { + updateState({ showSealUnlock: false }); + }, + showSealPassword: () => { + updateState({ showSealPassword: true }); + }, + hideSealPassword: () => { + updateState({ showSealPassword: false }); + }, + showSealConfirm: () => { + updateState({ showSealConfirm: true }); + }, + hideSealConfirm: () => { + updateState({ showSealConfirm: false }); + }, + updateSeal: () => { + updateState({ sealMode: 'updating' }); + }, + setSealEnable: (sealEnabled) => { + let sealMode = null; + if (sealEnabled !== state.sealEnabled) { + if (sealEnabled) { + sealMode = 'enabling'; + } + else { + sealMode = 'disabling'; + } + } + else { + if (state.sealEnabled && !state.sealUnlocked) { + sealMode = 'unlocking'; + } + else if (state.sealEnabled && state.sealUnlocked) { + sealMode = 'unlocked'; + } + else { + sealMode = 'disabled'; + } + } + updateState({ sealEnabled, sealMode }); + }, + saveSeal: async () => { + if (!state.saving) { + try { + updateState({ saving: true }); + + if (state.sealMode === 'enabling') { + await enable(); + } + else if (state.sealMode === 'disabling') { + await disable(); + } + else if (state.sealMode === 'unlocking') { + await unlock(); + } + else if (state.sealMode === 'unlocked') { + await forget(); + } + else if (state.sealMode === 'updating') { + await update(); + } + else { + console.log(state.sealMode); + } + + updateState({ saving: false }); + } + catch(err) { + console(err); + updateState({ saving: false }); + } + } + }, + setVisible: async (searchable) => { + updateState({ searchable }); + await account.actions.setSearchable(searchable); + }, + setNotifications: async (pushEnabled) => { + updateState({ pushEnabled }); + await account.actions.setNotifications(pushEnabled); + }, + setEditPassword: (editPassword) => { + updateState({ editPassword }); + }, + setEditConfirm: (editConfirm) => { + updateState({ editConfirm }); + }, + showPassword: () => { + updateState({ showPassword: true }); + }, + hidePassword: () => { + updateState({ showPassword: false }); + }, + showConfirm: () => { + updateState({ showConfirm: true }); + }, + hideConfirm: () => { + updateState({ showConfirm: false }); + }, + setEditHandle: (editHandle) => { + updateState({ editHandle, checked: false }); + + if (debounce.current != null) { + clearTimeout(debounce.current); + } + debounce.current = setTimeout(async () => { + try { + if (editHandle === state.handle) { + updateState({ available: true, checked: true }); + } + else { + const available = await profile.actions.getHandleStatus(editHandle); + updateState({ available, checked: true }); + } + } + catch (err) { + console.log(err); + } + }, 1000); + }, + saveDetails: async () => { + await profile.actions.setProfileData(state.editName, state.editLocation, state.editDescription); + }, + saveLogin: async () => { + await account.actions.setLogin(state.editHandle, state.editPassword); + }, }; return { state, actions }; diff --git a/app/mobile/yarn.lock b/app/mobile/yarn.lock index 9f56ffac..409f0f1b 100644 --- a/app/mobile/yarn.lock +++ b/app/mobile/yarn.lock @@ -2938,10 +2938,10 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -crypto-js@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz" - integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== +crypto-js@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.3.0.tgz#846dd1cce2f68aacfa156c8578f926a609b7976b" + integrity sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q== csstype@^3.0.2: version "3.1.1" @@ -4988,6 +4988,11 @@ jscodeshift@^0.13.1: temp "^0.8.4" write-file-atomic "^2.3.0" +jsencrypt@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/jsencrypt/-/jsencrypt-3.3.1.tgz#5d47e6c1079bb50e714a36d009ba09ea81fbfa18" + integrity sha512-dVvV54GdFuJgmEKn+oBiaifDMen4p6o6j/lJh0OVMcouME8sST0bJ7bldIgKBQk4za0zyGn0/pm4vOznR25mLw== + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz"