From e62452cbd313b9950f99cadc6b84fe0c741208d1 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Mon, 9 Jan 2023 09:43:02 +0100 Subject: [PATCH 1/2] Reload websocket after a long period in background --- App.tsx | 2 + .../java/com/nostros/classes/Websocket.java | 27 +++++++++--- frontend/Components/Button/index.tsx | 5 +-- frontend/Contexts/RelayPoolContext.tsx | 22 +++++++--- frontend/Locales/ru.json | 6 +-- frontend/Pages/ContactsPage/index.tsx | 4 +- frontend/Pages/ConversationPage/index.tsx | 42 +++++++++---------- frontend/Pages/HomePage/index.tsx | 2 +- frontend/Pages/LandingPage/Loader/index.tsx | 2 +- frontend/Pages/SendPage/index.tsx | 37 ++++++++-------- frontend/lib/nostr/Nip04/index.ts | 12 ++++-- 11 files changed, 99 insertions(+), 62 deletions(-) diff --git a/App.tsx b/App.tsx index 7383f8b2..d3c6ac7b 100644 --- a/App.tsx +++ b/App.tsx @@ -1,7 +1,9 @@ import App from './frontend' import { Buffer as SafeBuffer } from 'safe-buffer' +import { randomBytes } from '@noble/hashes/utils' import 'text-encoding-polyfill' global.Buffer = SafeBuffer +global.randomBytes = randomBytes export default App diff --git a/android/app/src/main/java/com/nostros/classes/Websocket.java b/android/app/src/main/java/com/nostros/classes/Websocket.java index fe8c5a57..c18b2a24 100644 --- a/android/app/src/main/java/com/nostros/classes/Websocket.java +++ b/android/app/src/main/java/com/nostros/classes/Websocket.java @@ -6,11 +6,8 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; -import com.neovisionaries.ws.client.HostnameUnverifiedException; -import com.neovisionaries.ws.client.OpeningHandshakeException; import com.neovisionaries.ws.client.WebSocket; import com.neovisionaries.ws.client.WebSocketAdapter; -import com.neovisionaries.ws.client.WebSocketException; import com.neovisionaries.ws.client.WebSocketFactory; import com.neovisionaries.ws.client.WebSocketFrame; import com.nostros.modules.DatabaseModule; @@ -19,12 +16,12 @@ import org.json.JSONObject; import java.io.IOException; -import java.util.ArrayList; public class Websocket { private WebSocket webSocket; private DatabaseModule database; private String url; + private String pubKey; private ReactApplicationContext context; public Websocket(String serverUrl, DatabaseModule databaseModule, ReactApplicationContext reactContext) { @@ -35,6 +32,13 @@ public Websocket(String serverUrl, DatabaseModule databaseModule, ReactApplicati public void send(String message) { Log.d("Websocket", "SEND URL:" + url + " __ " + message); + if (!webSocket.isOpen()) { + try { + this.connect(pubKey); + } catch (IOException e) { + e.printStackTrace(); + } + } webSocket.sendText(message); } @@ -44,6 +48,7 @@ public void disconnect() { public void connect(String userPubKey) throws IOException { WebSocketFactory factory = new WebSocketFactory(); + pubKey = userPubKey; webSocket = factory.createSocket(url); webSocket.setMissingCloseFrameAllowed(true); webSocket.setPingInterval(25 * 1000); @@ -53,10 +58,13 @@ public void connect(String userPubKey) throws IOException { public void onTextMessage(WebSocket websocket, String message) throws Exception { Log.d("Websocket", "RECEIVE URL:" + url + " __ " + message); JSONArray jsonArray = new JSONArray(message); - if (jsonArray.get(0).toString().equals("EVENT")) { + String messageType = jsonArray.get(0).toString(); + if (messageType.equals("EVENT")) { JSONObject data = jsonArray.getJSONObject(2); database.saveEvent(data, userPubKey); reactNativeEvent(data.getString("id")); + } else if (messageType.equals("OK")) { + reactNativeConfirmation(jsonArray.get(1).toString()); } } @Override @@ -75,4 +83,13 @@ public void reactNativeEvent(String eventId) { .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit("WebsocketEvent", payload); } + + public void reactNativeConfirmation(String eventId) { + Log.d("Websocket", "reactNativeConfirmation" + eventId); + WritableMap payload = Arguments.createMap(); + payload.putString("eventId", eventId); + context + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("WebsocketConfirmation", payload); + } } diff --git a/frontend/Components/Button/index.tsx b/frontend/Components/Button/index.tsx index 4ba6b323..bd586a05 100644 --- a/frontend/Components/Button/index.tsx +++ b/frontend/Components/Button/index.tsx @@ -15,8 +15,8 @@ const LoadingIndicator = (): ReactElement => ( /** * Extension of the UI-Kitten button, with more features. - * @param param0 - * @returns + * @param param0 + * @returns */ export const Button: React.FC = ({ disabled, @@ -25,7 +25,6 @@ export const Button: React.FC = ({ accessoryLeft, ...otherProps }) => { - console.log('Is loading', loading) return ( void lastEventId?: string - setLastEventId: (lastEventId: string) => void + lastConfirmationtId?: string } export interface WebsocketEvent { @@ -32,7 +32,6 @@ export const initialRelayPoolContext: RelayPoolContextProps = { setPublicKey: () => {}, setPrivateKey: () => {}, setRelayPool: () => {}, - setLastEventId: () => {}, } export const RelayPoolContextProvider = ({ @@ -48,17 +47,30 @@ export const RelayPoolContextProvider = ({ initialRelayPoolContext.loadingRelayPool, ) const [lastEventId, setLastEventId] = useState('') + const [lastConfirmationtId, setLastConfirmationId] = useState('') const [lastPage, setLastPage] = useState(page) - const changeHandler: (event: WebsocketEvent) => void = (event) => { + const changeEventIdHandler: (event: WebsocketEvent) => void = (event) => { setLastEventId(event.eventId) } + const changeConfirmationIdHandler: (event: WebsocketEvent) => void = (event) => { + console.log('changeConfirmationIdHandler', event) + setLastConfirmationId(event.eventId) + } - const debouncedEventIdHandler = useMemo(() => debounce(changeHandler, 1000), [setLastEventId]) + const debouncedEventIdHandler = useMemo( + () => debounce(changeEventIdHandler, 1000), + [setLastEventId], + ) + const debouncedConfirmationHandler = useMemo( + () => debounce(changeConfirmationIdHandler, 500), + [setLastConfirmationId], + ) const loadRelayPool: () => void = async () => { if (database && publicKey) { DeviceEventEmitter.addListener('WebsocketEvent', debouncedEventIdHandler) + DeviceEventEmitter.addListener('WebsocketConfirmation', debouncedConfirmationHandler) const initRelayPool = new RelayPool([], privateKey) initRelayPool.connect(publicKey, (eventId: string) => setLastEventId(eventId)) setRelayPool(initRelayPool) @@ -121,7 +133,7 @@ export const RelayPoolContextProvider = ({ privateKey, setPrivateKey, lastEventId, - setLastEventId, + lastConfirmationtId, }} > {children} diff --git a/frontend/Locales/ru.json b/frontend/Locales/ru.json index a4134a36..c63903a1 100644 --- a/frontend/Locales/ru.json +++ b/frontend/Locales/ru.json @@ -102,10 +102,10 @@ "profilePublished": "Профиль опубликован", "profilePublishError": "При публикации профиля произошла ошибка", "invoiceCopied": "Инвойс копирован", - "invoiceError": "Произошла ошибка при попытке получить инвойса", - "sendNoteSuccess": "Примечание отправлено", + "invoiceError": "Произошла ошибка при попытке получить инвойс", + "sendNoteSuccess": "Заметка отправлена", "sendNoteError": "При отправке заметки произошла ошибка", - "sendGetNotesError": "При получении заметок произошла внутренняя ошибка", + "sendGetNotesError": "При загрузке заметок произошла внутренняя ошибка", "contactAddError": "При добавлении контакта произошла ошибка", "privateMessageEncryptError": "При шифровании сообщения произошла ошибка", "privateMessageSendError": "При отправке сообщения произошла ошибка" diff --git a/frontend/Pages/ContactsPage/index.tsx b/frontend/Pages/ContactsPage/index.tsx index bf3b4219..f2c26345 100644 --- a/frontend/Pages/ContactsPage/index.tsx +++ b/frontend/Pages/ContactsPage/index.tsx @@ -19,6 +19,7 @@ import { getUsers, updateUserContact, User } from '../../Functions/DatabaseFunct import { Button, UserCard } from '../../Components' import { RelayPoolContext } from '../../Contexts/RelayPoolContext' import { populatePets } from '../../Functions/RelayFunctions/Users' +import { getNip19Key } from '../../lib/nostr/Nip19' export const ContactsPage: React.FC = () => { const { database, goBack } = useContext(AppContext) @@ -85,7 +86,8 @@ export const ContactsPage: React.FC = () => { const onPressAddContact: () => void = () => { if (contactInput && relayPool && database && publicKey) { setIsAddingContact(true) - updateUserContact(contactInput, database, true) + const hexKey = getNip19Key(contactInput) + updateUserContact(hexKey, database, true) .then(() => { populatePets(relayPool, database, publicKey) setShowAddContact(false) diff --git a/frontend/Pages/ConversationPage/index.tsx b/frontend/Pages/ConversationPage/index.tsx index 616cd106..ee848082 100644 --- a/frontend/Pages/ConversationPage/index.tsx +++ b/frontend/Pages/ConversationPage/index.tsx @@ -56,7 +56,7 @@ export const ConversationPage: React.FC = () => { if (user) setOtherUser(user) }) getDirectMessages(database, { conversationId, order: 'ASC' }).then((results) => { - if (results && results.length > 0) { + if (privateKey && results && results.length > 0) { setSendingMessages([]) setDirectMessages( results.map((message) => { @@ -101,29 +101,29 @@ export const ConversationPage: React.FC = () => { } setSendingMessages((prev) => [...prev, event as DirectMessage]) setInput('') - - const encryptedcontent = encrypt(privateKey, otherPubKey, input) - encrypt(privateKey, otherPubKey, input).then((content) => { - relayPool - ?.sendEvent({ - ...event, - content: encryptedcontent, - }) - .catch((err) => { - showMessage({ - message: t('alerts.privateMessageSendError'), - description: err.message, - type: 'danger', + + encrypt(privateKey, otherPubKey, input) + .then((encryptedcontent) => { + relayPool + ?.sendEvent({ + ...event, + content: encryptedcontent, + }) + .catch((err) => { + showMessage({ + message: t('alerts.privateMessageSendError'), + description: err.message, + type: 'danger', + }) }) + }) + .catch((err) => { + showMessage({ + message: t('alerts.privateMessageEncryptError'), + description: err.message, + type: 'danger', }) - }) - .catch((err) => { - showMessage({ - message: t('alerts.privateMessageEncryptError'), - description: err.message, - type: 'danger', }) - }) } } diff --git a/frontend/Pages/HomePage/index.tsx b/frontend/Pages/HomePage/index.tsx index da2aab0b..bdc98ae6 100644 --- a/frontend/Pages/HomePage/index.tsx +++ b/frontend/Pages/HomePage/index.tsx @@ -44,7 +44,7 @@ export const HomePage: React.FC = () => { } const subscribeNotes: (users: User[], past?: boolean) => void = async (users, past) => { - if (!database || !publicKey || users.length === 0) return + if (!database || !publicKey) return const lastNotes: Note[] = await getMainNotes(database, publicKey, initialPageSize) const lastNote: Note = lastNotes[lastNotes.length - 1] diff --git a/frontend/Pages/LandingPage/Loader/index.tsx b/frontend/Pages/LandingPage/Loader/index.tsx index 4e7ee783..8bdddf98 100644 --- a/frontend/Pages/LandingPage/Loader/index.tsx +++ b/frontend/Pages/LandingPage/Loader/index.tsx @@ -40,7 +40,7 @@ export const Loader: React.FC = () => { relayPool?.subscribe('main-channel', { kinds: [EventKind.meta, EventKind.textNote], authors, - since: moment().unix() - 86400, + since: moment().unix() - 86400 * 2, }) } }) diff --git a/frontend/Pages/SendPage/index.tsx b/frontend/Pages/SendPage/index.tsx index e1f3f7a3..9dca1cce 100644 --- a/frontend/Pages/SendPage/index.tsx +++ b/frontend/Pages/SendPage/index.tsx @@ -7,7 +7,7 @@ import { TopNavigation, useTheme, } from '@ui-kitten/components' -import React, { useContext, useRef, useState } from 'react' +import React, { useContext, useEffect, useRef, useState } from 'react' import { StyleSheet } from 'react-native' import { AppContext } from '../../Contexts/AppContext' import Icon from 'react-native-vector-icons/FontAwesome5' @@ -25,7 +25,7 @@ import { Avatar, Button } from '../../Components' export const SendPage: React.FC = () => { const theme = useTheme() const { goBack, page, database } = useContext(AppContext) - const { relayPool, publicKey } = useContext(RelayPoolContext) + const { relayPool, publicKey, lastConfirmationtId } = useContext(RelayPoolContext) const { t } = useTranslation('common') // state const [content, setContent] = useState('') @@ -39,6 +39,17 @@ export const SendPage: React.FC = () => { const breadcrump = page.split('%') const eventId = breadcrump[breadcrump.length - 1].split('#')[1] + useEffect(() => { + if (isSending) { + showMessage({ + message: t('alerts.sendNoteSuccess'), + type: 'success', + }) + setIsSending(false) // restore sending status + goBack() + } + }, [lastConfirmationtId]) + const styles = StyleSheet.create({ container: { flex: 1, @@ -106,23 +117,13 @@ export const SendPage: React.FC = () => { pubkey: publicKey, tags, } - relayPool - ?.sendEvent(event) - .then(() => { - showMessage({ - message: t('alerts.sendNoteSuccess'), - type: 'success', - }) - setIsSending(false) // restore sending status - goBack() - }) - .catch((err) => { - showMessage({ - message: t('alerts.sendNoteError'), - description: err.message, - type: 'danger', - }) + relayPool?.sendEvent(event).catch((err) => { + showMessage({ + message: t('alerts.sendNoteError'), + description: err.message, + type: 'danger', }) + }) }) .catch((err) => { // error with getNotes diff --git a/frontend/lib/nostr/Nip04/index.ts b/frontend/lib/nostr/Nip04/index.ts index 2a5f8606..deac466d 100644 --- a/frontend/lib/nostr/Nip04/index.ts +++ b/frontend/lib/nostr/Nip04/index.ts @@ -1,14 +1,18 @@ import { Buffer } from 'buffer' -import { randomBytes } from '@noble/hashes/utils' +import { generateSecureRandom } from 'react-native-securerandom' import * as secp256k1 from '@noble/secp256k1' // @ts-expect-error import aes from 'browserify-cipher' -export function encrypt(privkey: string, pubkey: string, text: string): string { +export const encrypt: (privkey: string, pubkey: string, text: string) => Promise = async ( + privkey, + pubkey, + text, +) => { const key = secp256k1.getSharedSecret(privkey, '02' + pubkey) - const normalizedKey = getNormalizedX(key) + const normalizedKey = Buffer.from(key.slice(1, 33)).toString('hex') - const iv = Uint8Array.from(randomBytes(16)) + const iv = await generateSecureRandom(16) const cipher = aes.createCipheriv('aes-256-cbc', Buffer.from(normalizedKey, 'hex'), iv) let encryptedMessage = cipher.update(text, 'utf8', 'base64') encryptedMessage += cipher.final('base64') From e62eb1cf8b1f09bee3600a691cb38e3d1c67c230 Mon Sep 17 00:00:00 2001 From: KoalaSat Date: Mon, 9 Jan 2023 09:43:42 +0100 Subject: [PATCH 2/2] Update README --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index c1d5055d..ef43d5e4 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,6 @@ Local setup: https://github.com/KoalaSat/nostros/blob/main/SETUP.md # Some Features to Work On -### Bugs - -- [ ] Websocket connections closed when the app goes to background for too long - ### Home - [ ] Public Room