mirror of
https://github.com/KoalaSat/nostros.git
synced 2024-09-29 06:30:47 +00:00
Android Performance Imrpovement
This commit is contained in:
parent
151d215fd3
commit
e28437084a
@ -4,7 +4,7 @@ import com.facebook.react.ReactPackage;
|
|||||||
import com.facebook.react.bridge.NativeModule;
|
import com.facebook.react.bridge.NativeModule;
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
import com.facebook.react.uimanager.ViewManager;
|
import com.facebook.react.uimanager.ViewManager;
|
||||||
import com.nostros.modules.WebsocketModule;
|
import com.nostros.modules.RelayPoolModule;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -21,7 +21,7 @@ public class NostrosPackage implements ReactPackage {
|
|||||||
ReactApplicationContext reactContext) {
|
ReactApplicationContext reactContext) {
|
||||||
List<NativeModule> modules = new ArrayList<>();
|
List<NativeModule> modules = new ArrayList<>();
|
||||||
|
|
||||||
modules.add(new WebsocketModule(reactContext));
|
modules.add(new RelayPoolModule(reactContext));
|
||||||
|
|
||||||
return modules;
|
return modules;
|
||||||
}
|
}
|
||||||
|
45
android/app/src/main/java/com/nostros/classes/Relay.java
Normal file
45
android/app/src/main/java/com/nostros/classes/Relay.java
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package com.nostros.classes;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.nostros.modules.DatabaseModule;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class Relay {
|
||||||
|
private Websocket webSocket;
|
||||||
|
public String url;
|
||||||
|
|
||||||
|
public Relay(String serverUrl, DatabaseModule database) throws IOException {
|
||||||
|
webSocket = new Websocket(serverUrl, database);
|
||||||
|
url = serverUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String message) {
|
||||||
|
webSocket.send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnect() {
|
||||||
|
webSocket.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connect(String userPubKey) throws IOException {
|
||||||
|
webSocket.connect(userPubKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(SQLiteDatabase database) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put("url", url);
|
||||||
|
database.replace("nostros_relays", null, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void destroy(SQLiteDatabase database) {
|
||||||
|
String whereClause = "url = ?";
|
||||||
|
String[] whereArgs = new String[] {
|
||||||
|
url
|
||||||
|
};
|
||||||
|
database.delete ("nostros_relays", whereClause, whereArgs);
|
||||||
|
}
|
||||||
|
}
|
@ -1,58 +1,45 @@
|
|||||||
package com.nostros.modules;
|
package com.nostros.classes;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.facebook.react.bridge.Callback;
|
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
|
||||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
||||||
import com.facebook.react.bridge.ReactMethod;
|
|
||||||
import com.neovisionaries.ws.client.HostnameUnverifiedException;
|
import com.neovisionaries.ws.client.HostnameUnverifiedException;
|
||||||
import com.neovisionaries.ws.client.OpeningHandshakeException;
|
import com.neovisionaries.ws.client.OpeningHandshakeException;
|
||||||
import com.neovisionaries.ws.client.WebSocket;
|
import com.neovisionaries.ws.client.WebSocket;
|
||||||
import com.neovisionaries.ws.client.WebSocketAdapter;
|
import com.neovisionaries.ws.client.WebSocketAdapter;
|
||||||
import com.neovisionaries.ws.client.WebSocketException;
|
import com.neovisionaries.ws.client.WebSocketException;
|
||||||
import com.neovisionaries.ws.client.WebSocketFactory;
|
import com.neovisionaries.ws.client.WebSocketFactory;
|
||||||
|
import com.nostros.modules.DatabaseModule;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class WebsocketModule extends ReactContextBaseJavaModule {
|
public class Websocket {
|
||||||
private WebSocket webSocket;
|
private WebSocket webSocket;
|
||||||
private DatabaseModule database;
|
private DatabaseModule database;
|
||||||
private String userPubKey;
|
private String url;
|
||||||
|
|
||||||
public WebsocketModule(ReactApplicationContext reactContext) {
|
public Websocket(String serverUrl, DatabaseModule databaseModule) {
|
||||||
super(reactContext);
|
database = databaseModule;
|
||||||
database = new DatabaseModule(reactContext);
|
url = serverUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "WebsocketModule";
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void send(String message) {
|
public void send(String message) {
|
||||||
|
Log.d("Websocket", "SEND URL:" + url + " __ " + message);
|
||||||
webSocket.sendText(message);
|
webSocket.sendText(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
public void disconnect() {
|
||||||
public void connectWebsocket(Callback callBack) throws IOException {
|
webSocket.disconnect();
|
||||||
WebSocketFactory factory = new WebSocketFactory();
|
|
||||||
webSocket = factory.createSocket("wss://relay.damus.io");
|
|
||||||
webSocket.addListener(new WebSocketAdapter() {
|
|
||||||
@Override
|
|
||||||
public void onConnected(WebSocket ws, Map<String, List<String>> headers) throws Exception
|
|
||||||
{
|
|
||||||
callBack.invoke("connected");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void connect(String userPubKey) throws IOException {
|
||||||
|
WebSocketFactory factory = new WebSocketFactory();
|
||||||
|
webSocket = factory.createSocket(url);
|
||||||
|
webSocket.addListener(new WebSocketAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void onTextMessage(WebSocket websocket, String message) throws Exception {
|
public void onTextMessage(WebSocket websocket, String message) throws Exception {
|
||||||
Log.d("Websocket", message);
|
Log.d("Websocket", "RECEIVE URL:" + url + " __ " + message);
|
||||||
JSONArray jsonArray = new JSONArray(message);
|
JSONArray jsonArray = new JSONArray(message);
|
||||||
if (jsonArray.get(0).toString().equals("EVENT")) {
|
if (jsonArray.get(0).toString().equals("EVENT")) {
|
||||||
database.saveEvent(jsonArray.getJSONObject(2), userPubKey);
|
database.saveEvent(jsonArray.getJSONObject(2), userPubKey);
|
||||||
@ -77,9 +64,4 @@ public class WebsocketModule extends ReactContextBaseJavaModule {
|
|||||||
Log.d("WebSocket", "Failed to establish a WebSocket connection.");
|
Log.d("WebSocket", "Failed to establish a WebSocket connection.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void setUserPubKey(String userPubKey) {
|
|
||||||
this.userPubKey = userPubKey;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,33 +1,57 @@
|
|||||||
package com.nostros.modules;
|
package com.nostros.modules;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
|
||||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
||||||
import com.facebook.react.bridge.ReactMethod;
|
|
||||||
import com.nostros.classes.Event;
|
import com.nostros.classes.Event;
|
||||||
|
import com.nostros.classes.Relay;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
public class DatabaseModule extends ReactContextBaseJavaModule {
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DatabaseModule {
|
||||||
private SQLiteDatabase database;
|
private SQLiteDatabase database;
|
||||||
|
|
||||||
DatabaseModule(ReactApplicationContext reactContext) {
|
DatabaseModule(String absoluteFilesPath) {
|
||||||
super(reactContext);
|
database = SQLiteDatabase.openDatabase( absoluteFilesPath + "/nostros.sqlite", null, SQLiteDatabase.OPEN_READWRITE);
|
||||||
String dbPath = reactContext.getFilesDir().getAbsolutePath();
|
|
||||||
database = SQLiteDatabase.openDatabase( dbPath + "/nostros.sqlite", null, SQLiteDatabase.OPEN_READWRITE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "DatabaseModule";
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void saveEvent(JSONObject data, String userPubKey) throws JSONException {
|
public void saveEvent(JSONObject data, String userPubKey) throws JSONException {
|
||||||
Event event = new Event(data);
|
Event event = new Event(data);
|
||||||
event.save(database, userPubKey);
|
event.save(database, userPubKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void saveRelay(Relay relay) {
|
||||||
|
relay.save(database);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void destroyRelay(Relay relay) {
|
||||||
|
relay.destroy(database);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Relay> getRelays() {
|
||||||
|
List<Relay> relayList = new ArrayList<>();
|
||||||
|
String query = "SELECT url FROM nostros_relays;";
|
||||||
|
@SuppressLint("Recycle") Cursor cursor = database.rawQuery(query, new String[] {});
|
||||||
|
if (cursor.getCount() > 0) {
|
||||||
|
Log.d("WebSocket", String.valueOf(cursor.getCount()));
|
||||||
|
for (int i = 1; i < cursor.getCount(); i++) {
|
||||||
|
Log.d("WebSocket", String.valueOf(i));
|
||||||
|
try {
|
||||||
|
String relayUrl = cursor.getString(i);
|
||||||
|
Relay relay = new Relay(relayUrl, this);
|
||||||
|
relayList.add(relay);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d("WebSocket", e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return relayList;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
package com.nostros.modules;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Callback;
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
import com.nostros.classes.Relay;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RelayPoolModule extends ReactContextBaseJavaModule {
|
||||||
|
protected List<Relay> relays;
|
||||||
|
private String userPubKey;
|
||||||
|
private DatabaseModule database;
|
||||||
|
|
||||||
|
public RelayPoolModule(ReactApplicationContext reactContext) {
|
||||||
|
database = new DatabaseModule(reactContext.getFilesDir().getAbsolutePath());
|
||||||
|
|
||||||
|
List<Relay> relayList = database.getRelays();
|
||||||
|
if (relayList.isEmpty()) {
|
||||||
|
try {
|
||||||
|
relayList.add(new Relay("wss://relay.damus.io", database));
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d("WebSocket", e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relays = relayList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "RelayPoolModule";
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void add(String url, Callback callback) {
|
||||||
|
try {
|
||||||
|
Relay relay = new Relay(url, database);
|
||||||
|
relay.connect(userPubKey);
|
||||||
|
relays.add(relay);
|
||||||
|
database.saveRelay(relay);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d("WebSocket", e.toString());
|
||||||
|
}
|
||||||
|
callback.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void remove(String url, Callback callback) {
|
||||||
|
for (Relay relay : relays) {
|
||||||
|
if (relay.url.equals(url)) {
|
||||||
|
relay.disconnect();
|
||||||
|
relays.remove(relay);
|
||||||
|
database.destroyRelay(relay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void connect(String pubKey, Callback callback) {
|
||||||
|
userPubKey = pubKey;
|
||||||
|
for (Relay relay : relays) {
|
||||||
|
try {
|
||||||
|
relay.connect(pubKey);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.d("WebSocket", e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void send(String message) {
|
||||||
|
for (Relay relay : relays) {
|
||||||
|
relay.send(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,4 @@
|
|||||||
import {
|
import { Button, Divider, Input, Layout, TopNavigation, useTheme } from '@ui-kitten/components'
|
||||||
Button,
|
|
||||||
Divider,
|
|
||||||
Input,
|
|
||||||
Layout,
|
|
||||||
TopNavigation,
|
|
||||||
TopNavigationAction,
|
|
||||||
useTheme,
|
|
||||||
} from '@ui-kitten/components'
|
|
||||||
import React, { useContext, useEffect } from 'react'
|
import React, { useContext, useEffect } from 'react'
|
||||||
import { Clipboard, StyleSheet } from 'react-native'
|
import { Clipboard, StyleSheet } from 'react-native'
|
||||||
import { AppContext } from '../../Contexts/AppContext'
|
import { AppContext } from '../../Contexts/AppContext'
|
||||||
@ -49,9 +41,10 @@ export const ConfigPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderBackAction = (): JSX.Element => (
|
const renderBackAction = (): JSX.Element => (
|
||||||
<TopNavigationAction
|
<Button
|
||||||
icon={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
accessoryRight={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||||
onPress={onPressBack}
|
onPress={onPressBack}
|
||||||
|
appearance='ghost'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import { populatePets } from '../../Functions/RelayFunctions/Users'
|
|||||||
|
|
||||||
export const ContactsPage: React.FC = () => {
|
export const ContactsPage: React.FC = () => {
|
||||||
const { database } = useContext(AppContext)
|
const { database } = useContext(AppContext)
|
||||||
const { relayPool, publicKey, privateKey } = useContext(RelayPoolContext)
|
const { relayPool, publicKey, privateKey, lastEventId } = useContext(RelayPoolContext)
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const [users, setUsers] = useState<User[]>()
|
const [users, setUsers] = useState<User[]>()
|
||||||
const [refreshing, setRefreshing] = useState(true)
|
const [refreshing, setRefreshing] = useState(true)
|
||||||
@ -22,6 +22,10 @@ export const ContactsPage: React.FC = () => {
|
|||||||
|
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadUsers()
|
||||||
|
}, [lastEventId])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setUsers([])
|
setUsers([])
|
||||||
loadUsers()
|
loadUsers()
|
||||||
@ -36,13 +40,16 @@ export const ContactsPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getUsers(database, filters).then((results) => {
|
getUsers(database, filters).then((results) => {
|
||||||
if (results) {
|
if (results && results.length > 0) {
|
||||||
setUsers(results)
|
setUsers(results)
|
||||||
|
const missingDataUsers = results.filter((user) => !user.picture).map((user) => user.id)
|
||||||
|
if (missingDataUsers.length > 0) {
|
||||||
relayPool?.subscribe('main-channel', {
|
relayPool?.subscribe('main-channel', {
|
||||||
kinds: [EventKind.meta],
|
kinds: [EventKind.meta],
|
||||||
authors: results.map((user) => user.id),
|
authors: missingDataUsers,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
setRefreshing(false)
|
setRefreshing(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,11 @@ import NoteCard from '../NoteCard'
|
|||||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||||
import { EventKind } from '../../lib/nostr/Events'
|
import { EventKind } from '../../lib/nostr/Events'
|
||||||
import { RelayFilters } from '../../lib/nostr/Relay'
|
|
||||||
import { getReplyEventId } from '../../Functions/RelayFunctions/Events'
|
import { getReplyEventId } from '../../Functions/RelayFunctions/Events'
|
||||||
import { getUsers, User } from '../../Functions/DatabaseFunctions/Users'
|
import { getUsers, User } from '../../Functions/DatabaseFunctions/Users'
|
||||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||||
import Loading from '../Loading'
|
import Loading from '../Loading'
|
||||||
|
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
|
||||||
|
|
||||||
export const HomePage: React.FC = () => {
|
export const HomePage: React.FC = () => {
|
||||||
const { database, goToPage } = useContext(AppContext)
|
const { database, goToPage } = useContext(AppContext)
|
||||||
@ -33,7 +33,7 @@ export const HomePage: React.FC = () => {
|
|||||||
|
|
||||||
const calculateInitialNotes: () => Promise<void> = async () => {
|
const calculateInitialNotes: () => Promise<void> = async () => {
|
||||||
if (database && publicKey) {
|
if (database && publicKey) {
|
||||||
setTimeout(() => setRefreshing(false), 3000)
|
setTimeout(() => setRefreshing(false), 2000)
|
||||||
const users = await getUsers(database, { contacts: true, includeIds: [publicKey] })
|
const users = await getUsers(database, { contacts: true, includeIds: [publicKey] })
|
||||||
setAuthors(users)
|
setAuthors(users)
|
||||||
subscribeNotes(users)
|
subscribeNotes(users)
|
||||||
@ -43,21 +43,12 @@ export const HomePage: React.FC = () => {
|
|||||||
const subscribeNotes: (users: User[], past?: boolean) => void = (users, past) => {
|
const subscribeNotes: (users: User[], past?: boolean) => void = (users, past) => {
|
||||||
if (!database || !publicKey || users.length === 0) return
|
if (!database || !publicKey || users.length === 0) return
|
||||||
|
|
||||||
const limit = past ? pageSize : initialPageSize
|
|
||||||
getNotes(database, { contacts: true, includeIds: [publicKey], limit }).then((results) => {
|
|
||||||
const message: RelayFilters = {
|
const message: RelayFilters = {
|
||||||
kinds: [EventKind.textNote, EventKind.recommendServer],
|
kinds: [EventKind.textNote, EventKind.recommendServer],
|
||||||
authors: users.map((user) => user.id),
|
authors: users.map((user) => user.id),
|
||||||
limit: initialPageSize,
|
limit: pageSize,
|
||||||
}
|
|
||||||
|
|
||||||
if (past) {
|
|
||||||
message.until = results[results.length - 1]?.created_at
|
|
||||||
} else if (results.length >= pageSize) {
|
|
||||||
message.since = results[0]?.created_at
|
|
||||||
}
|
}
|
||||||
relayPool?.subscribe('main-channel', message)
|
relayPool?.subscribe('main-channel', message)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadNotes: () => void = () => {
|
const loadNotes: () => void = () => {
|
||||||
@ -65,6 +56,15 @@ export const HomePage: React.FC = () => {
|
|||||||
getNotes(database, { contacts: true, includeIds: [publicKey], limit: pageSize }).then(
|
getNotes(database, { contacts: true, includeIds: [publicKey], limit: pageSize }).then(
|
||||||
(notes) => {
|
(notes) => {
|
||||||
setNotes(notes)
|
setNotes(notes)
|
||||||
|
const missingDataNotes = notes
|
||||||
|
.filter((note) => !note.picture || note.picture === '')
|
||||||
|
.map((note) => note.pubkey)
|
||||||
|
if (missingDataNotes.length > 0) {
|
||||||
|
relayPool?.subscribe('main-channel', {
|
||||||
|
kinds: [EventKind.meta],
|
||||||
|
authors: missingDataNotes,
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ export const Logger: React.FC = () => {
|
|||||||
kinds: [EventKind.petNames, EventKind.meta],
|
kinds: [EventKind.petNames, EventKind.meta],
|
||||||
authors: [publicKey],
|
authors: [publicKey],
|
||||||
})
|
})
|
||||||
setTimeout(() => goToPage('home', true), 3000)
|
setTimeout(() => goToPage('home', true), 5000)
|
||||||
}
|
}
|
||||||
}, [loadingRelayPool, publicKey, loadingDb])
|
}, [loadingRelayPool, publicKey, loadingDb])
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useState } from 'react'
|
import React, { useContext, useEffect, useState } from 'react'
|
||||||
import { Button, Layout, Text, useTheme } from '@ui-kitten/components'
|
import { Button, Layout, Text, useTheme } from '@ui-kitten/components'
|
||||||
import { Note } from '../../Functions/DatabaseFunctions/Notes'
|
import { Note } from '../../Functions/DatabaseFunctions/Notes'
|
||||||
import { StyleSheet, TouchableOpacity } from 'react-native'
|
import { StyleSheet, TouchableOpacity } from 'react-native'
|
||||||
@ -6,7 +6,6 @@ import Markdown from 'react-native-markdown-display'
|
|||||||
import { EventKind } from '../../lib/nostr/Events'
|
import { EventKind } from '../../lib/nostr/Events'
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||||
import { addRelay } from '../../Functions/DatabaseFunctions/Relays'
|
|
||||||
import { AppContext } from '../../Contexts/AppContext'
|
import { AppContext } from '../../Contexts/AppContext'
|
||||||
import { showMessage } from 'react-native-flash-message'
|
import { showMessage } from 'react-native-flash-message'
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
@ -15,6 +14,7 @@ import moment from 'moment'
|
|||||||
import { populateRelay } from '../../Functions/RelayFunctions'
|
import { populateRelay } from '../../Functions/RelayFunctions'
|
||||||
import Avatar from '../Avatar'
|
import Avatar from '../Avatar'
|
||||||
import { markdownIt, markdownStyle } from '../../Constants/AppConstants'
|
import { markdownIt, markdownStyle } from '../../Constants/AppConstants'
|
||||||
|
import { searchRelays } from '../../Functions/DatabaseFunctions/Relays'
|
||||||
|
|
||||||
interface NoteCardProps {
|
interface NoteCardProps {
|
||||||
note: Note
|
note: Note
|
||||||
@ -22,11 +22,17 @@ interface NoteCardProps {
|
|||||||
|
|
||||||
export const NoteCard: React.FC<NoteCardProps> = ({ note }) => {
|
export const NoteCard: React.FC<NoteCardProps> = ({ note }) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const { relayPool, setRelayPool, publicKey } = useContext(RelayPoolContext)
|
const { relayPool, publicKey } = useContext(RelayPoolContext)
|
||||||
const { database, goToPage } = useContext(AppContext)
|
const { database, goToPage } = useContext(AppContext)
|
||||||
const [relayAdded, setRelayAdded] = useState<boolean>(
|
const [relayAdded, setRelayAdded] = useState<boolean>(false)
|
||||||
Object.keys(relayPool?.relays ?? {}).includes(note.content),
|
|
||||||
)
|
useEffect(() => {
|
||||||
|
if (database) {
|
||||||
|
searchRelays(note.content, database).then((result) => {
|
||||||
|
setRelayAdded(result.length > 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [database])
|
||||||
|
|
||||||
const textNote: () => JSX.Element = () => {
|
const textNote: () => JSX.Element = () => {
|
||||||
return (
|
return (
|
||||||
@ -73,9 +79,7 @@ export const NoteCard: React.FC<NoteCardProps> = ({ note }) => {
|
|||||||
|
|
||||||
const addRelayItem: () => void = () => {
|
const addRelayItem: () => void = () => {
|
||||||
if (relayPool && database && publicKey) {
|
if (relayPool && database && publicKey) {
|
||||||
relayPool.add(note.content)
|
relayPool.add(note.content, () => {
|
||||||
setRelayPool(relayPool)
|
|
||||||
addRelay({ url: note.content }, database)
|
|
||||||
populateRelay(relayPool, database, publicKey)
|
populateRelay(relayPool, database, publicKey)
|
||||||
showMessage({
|
showMessage({
|
||||||
message: t('alerts.relayAdded'),
|
message: t('alerts.relayAdded'),
|
||||||
@ -83,6 +87,7 @@ export const NoteCard: React.FC<NoteCardProps> = ({ note }) => {
|
|||||||
type: 'success',
|
type: 'success',
|
||||||
})
|
})
|
||||||
setRelayAdded(true)
|
setRelayAdded(true)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
import {
|
import { Button, Card, Layout, Spinner, TopNavigation, useTheme } from '@ui-kitten/components'
|
||||||
Card,
|
|
||||||
Layout,
|
|
||||||
Spinner,
|
|
||||||
TopNavigation,
|
|
||||||
TopNavigationAction,
|
|
||||||
useTheme,
|
|
||||||
} from '@ui-kitten/components'
|
|
||||||
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
||||||
import { AppContext } from '../../Contexts/AppContext'
|
import { AppContext } from '../../Contexts/AppContext'
|
||||||
import { getNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
|
import { getNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
|
||||||
@ -13,7 +6,6 @@ import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
|||||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||||
import NoteCard from '../NoteCard'
|
import NoteCard from '../NoteCard'
|
||||||
import { EventKind } from '../../lib/nostr/Events'
|
import { EventKind } from '../../lib/nostr/Events'
|
||||||
import { RelayFilters } from '../../lib/nostr/Relay'
|
|
||||||
import {
|
import {
|
||||||
Clipboard,
|
Clipboard,
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
@ -25,6 +17,7 @@ import {
|
|||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import Loading from '../Loading'
|
import Loading from '../Loading'
|
||||||
import { getDirectReplies, getReplyEventId } from '../../Functions/RelayFunctions/Events'
|
import { getDirectReplies, getReplyEventId } from '../../Functions/RelayFunctions/Events'
|
||||||
|
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
|
||||||
|
|
||||||
export const NotePage: React.FC = () => {
|
export const NotePage: React.FC = () => {
|
||||||
const { page, goBack, goToPage, database, getActualPage } = useContext(AppContext)
|
const { page, goBack, goToPage, database, getActualPage } = useContext(AppContext)
|
||||||
@ -113,18 +106,20 @@ export const NotePage: React.FC = () => {
|
|||||||
|
|
||||||
const renderBackAction = (): JSX.Element => {
|
const renderBackAction = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<TopNavigationAction
|
<Button
|
||||||
icon={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
accessoryRight={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||||
onPress={onPressBack}
|
onPress={onPressBack}
|
||||||
|
appearance='ghost'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderNoteActions = (): JSX.Element => {
|
const renderNoteActions = (): JSX.Element => {
|
||||||
return note && getReplyEventId(note) ? (
|
return note && getReplyEventId(note) ? (
|
||||||
<TopNavigationAction
|
<Button
|
||||||
icon={<Icon name='arrow-up' size={16} color={theme['text-basic-color']} />}
|
accessoryRight={<Icon name='arrow-up' size={16} color={theme['text-basic-color']} />}
|
||||||
onPress={onPressGoParent}
|
onPress={onPressGoParent}
|
||||||
|
appearance='ghost'
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
@ -1,12 +1,4 @@
|
|||||||
import {
|
import { Button, Card, Layout, Spinner, Text, TopNavigation, useTheme } from '@ui-kitten/components'
|
||||||
Card,
|
|
||||||
Layout,
|
|
||||||
Spinner,
|
|
||||||
Text,
|
|
||||||
TopNavigation,
|
|
||||||
TopNavigationAction,
|
|
||||||
useTheme,
|
|
||||||
} from '@ui-kitten/components'
|
|
||||||
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
Clipboard,
|
Clipboard,
|
||||||
@ -24,13 +16,13 @@ import NoteCard from '../NoteCard'
|
|||||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||||
import { getUser, User, updateUserContact } from '../../Functions/DatabaseFunctions/Users'
|
import { getUser, User, updateUserContact } from '../../Functions/DatabaseFunctions/Users'
|
||||||
import { EventKind } from '../../lib/nostr/Events'
|
import { EventKind } from '../../lib/nostr/Events'
|
||||||
import { RelayFilters } from '../../lib/nostr/Relay'
|
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||||
import { populatePets } from '../../Functions/RelayFunctions/Users'
|
import { populatePets } from '../../Functions/RelayFunctions/Users'
|
||||||
import { getReplyEventId } from '../../Functions/RelayFunctions/Events'
|
import { getReplyEventId } from '../../Functions/RelayFunctions/Events'
|
||||||
import Loading from '../Loading'
|
import Loading from '../Loading'
|
||||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||||
import Avatar from '../Avatar'
|
import Avatar from '../Avatar'
|
||||||
|
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
|
||||||
|
|
||||||
export const ProfilePage: React.FC = () => {
|
export const ProfilePage: React.FC = () => {
|
||||||
const { database, page, goToPage, goBack } = useContext(AppContext)
|
const { database, page, goToPage, goBack } = useContext(AppContext)
|
||||||
@ -80,20 +72,14 @@ export const ProfilePage: React.FC = () => {
|
|||||||
|
|
||||||
const subscribeNotes: (past?: boolean) => void = (past) => {
|
const subscribeNotes: (past?: boolean) => void = (past) => {
|
||||||
if (!database) return
|
if (!database) return
|
||||||
const limit = past ? pageSize : initialPageSize
|
|
||||||
getNotes(database, { filters: { pubkey: userId }, limit }).then((results) => {
|
|
||||||
const message: RelayFilters = {
|
const message: RelayFilters = {
|
||||||
kinds: [EventKind.textNote, EventKind.recommendServer],
|
kinds: [EventKind.textNote, EventKind.recommendServer],
|
||||||
authors: [userId],
|
authors: [userId],
|
||||||
limit: initialPageSize,
|
limit: pageSize,
|
||||||
}
|
|
||||||
if (past) {
|
|
||||||
message.until = results[results.length - 1]?.created_at
|
|
||||||
} else if (results.length >= pageSize) {
|
|
||||||
message.since = results[0]?.created_at
|
|
||||||
}
|
}
|
||||||
|
|
||||||
relayPool?.subscribe('main-channel', message)
|
relayPool?.subscribe('main-channel', message)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const subscribeProfile: () => Promise<void> = async () => {
|
const subscribeProfile: () => Promise<void> = async () => {
|
||||||
@ -148,25 +134,32 @@ export const ProfilePage: React.FC = () => {
|
|||||||
const renderOptions: () => JSX.Element = () => {
|
const renderOptions: () => JSX.Element = () => {
|
||||||
if (publicKey === userId) {
|
if (publicKey === userId) {
|
||||||
return (
|
return (
|
||||||
<TopNavigationAction
|
<Button
|
||||||
icon={<Icon name='cog' size={16} color={theme['text-basic-color']} solid />}
|
accessoryRight={<Icon name='cog' size={16} color={theme['text-basic-color']} solid />}
|
||||||
onPress={() => goToPage('config')}
|
onPress={() => goToPage('config')}
|
||||||
|
appearance='ghost'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
if (user) {
|
if (user) {
|
||||||
if (isContact) {
|
if (isContact) {
|
||||||
return (
|
return (
|
||||||
<TopNavigationAction
|
<Button
|
||||||
icon={<Icon name='user-minus' size={16} color={theme['color-danger-500']} solid />}
|
accessoryRight={
|
||||||
|
<Icon name='user-minus' size={16} color={theme['color-danger-500']} solid />
|
||||||
|
}
|
||||||
onPress={removeAuthor}
|
onPress={removeAuthor}
|
||||||
|
appearance='ghost'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<TopNavigationAction
|
<Button
|
||||||
icon={<Icon name='user-plus' size={16} color={theme['color-success-500']} solid />}
|
accessoryRight={
|
||||||
|
<Icon name='user-plus' size={16} color={theme['color-success-500']} solid />
|
||||||
|
}
|
||||||
onPress={addAuthor}
|
onPress={addAuthor}
|
||||||
|
appearance='ghost'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -177,7 +170,6 @@ export const ProfilePage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onPressBack: () => void = () => {
|
const onPressBack: () => void = () => {
|
||||||
relayPool?.removeOn('event', 'profile')
|
|
||||||
relayPool?.unsubscribeAll()
|
relayPool?.unsubscribeAll()
|
||||||
goBack()
|
goBack()
|
||||||
}
|
}
|
||||||
@ -187,9 +179,10 @@ export const ProfilePage: React.FC = () => {
|
|||||||
return <></>
|
return <></>
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<TopNavigationAction
|
<Button
|
||||||
icon={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
accessoryRight={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||||
onPress={onPressBack}
|
onPress={onPressBack}
|
||||||
|
appearance='ghost'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -273,8 +266,7 @@ export const ProfilePage: React.FC = () => {
|
|||||||
|
|
||||||
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
|
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
|
||||||
if (handleInfinityScroll(event)) {
|
if (handleInfinityScroll(event)) {
|
||||||
const newSize: number = notes?.length === pageSize ? pageSize + initialPageSize : pageSize
|
setPageSize(pageSize + initialPageSize)
|
||||||
setPageSize(newSize)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,26 +1,18 @@
|
|||||||
import {
|
import { Button, Layout, TopNavigation, useTheme, Text } from '@ui-kitten/components'
|
||||||
Button,
|
|
||||||
Layout,
|
|
||||||
TopNavigation,
|
|
||||||
TopNavigationAction,
|
|
||||||
useTheme,
|
|
||||||
Text,
|
|
||||||
} from '@ui-kitten/components'
|
|
||||||
import React, { useContext, useEffect, useState } from 'react'
|
import React, { useContext, useEffect, useState } from 'react'
|
||||||
import { RefreshControl, ScrollView, StyleSheet } from 'react-native'
|
import { RefreshControl, ScrollView, StyleSheet } from 'react-native'
|
||||||
import { AppContext } from '../../Contexts/AppContext'
|
import { AppContext } from '../../Contexts/AppContext'
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||||
import { getRelays, Relay, addRelay, removeRelay } from '../../Functions/DatabaseFunctions/Relays'
|
import { getRelays, Relay } from '../../Functions/DatabaseFunctions/Relays'
|
||||||
import { defaultRelays } from '../../Constants/RelayConstants'
|
import { defaultRelays } from '../../Constants/RelayConstants'
|
||||||
import { populateRelay } from '../../Functions/RelayFunctions'
|
|
||||||
import { showMessage } from 'react-native-flash-message'
|
import { showMessage } from 'react-native-flash-message'
|
||||||
|
|
||||||
export const RelaysPage: React.FC = () => {
|
export const RelaysPage: React.FC = () => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const { goBack, database } = useContext(AppContext)
|
const { goBack, database } = useContext(AppContext)
|
||||||
const { relayPool, publicKey, setRelayPool } = useContext(RelayPoolContext)
|
const { relayPool, publicKey } = useContext(RelayPoolContext)
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
const [relays, setRelays] = useState<Relay[]>([])
|
const [relays, setRelays] = useState<Relay[]>([])
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
@ -43,40 +35,38 @@ export const RelaysPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderBackAction = (): JSX.Element => (
|
const renderBackAction = (): JSX.Element => (
|
||||||
<TopNavigationAction
|
<Button
|
||||||
icon={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
accessoryRight={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||||
onPress={onPressBack}
|
onPress={onPressBack}
|
||||||
|
appearance='ghost'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
const addRelayItem: (url: string) => void = async (url) => {
|
const addRelayItem: (url: string) => void = async (url) => {
|
||||||
if (relayPool && database && publicKey && (relays?.length ?? 0) < 2) {
|
if (relayPool && database && publicKey && (relays?.length ?? 0) < 2) {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
relayPool.add(url)
|
relayPool.add(url, () => {
|
||||||
setRelayPool(relayPool)
|
|
||||||
await addRelay({ url }, database)
|
|
||||||
populateRelay(relayPool, database, publicKey)
|
|
||||||
showMessage({
|
showMessage({
|
||||||
message: t('alerts.relayAdded'),
|
message: t('alerts.relayAdded'),
|
||||||
description: url,
|
description: url,
|
||||||
type: 'success',
|
type: 'success',
|
||||||
})
|
})
|
||||||
loadRelays()
|
loadRelays()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeRelayItem: (url: string) => void = async (url) => {
|
const removeRelayItem: (url: string) => void = async (url) => {
|
||||||
if (relayPool && database && publicKey) {
|
if (relayPool && database && publicKey) {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
relayPool.remove(url)
|
relayPool.remove(url, () => {
|
||||||
setRelayPool(relayPool)
|
|
||||||
await removeRelay({ url }, database)
|
|
||||||
showMessage({
|
showMessage({
|
||||||
message: t('alerts.relayRemoved'),
|
message: t('alerts.relayRemoved'),
|
||||||
description: url,
|
description: url,
|
||||||
type: 'danger',
|
type: 'danger',
|
||||||
})
|
})
|
||||||
loadRelays()
|
loadRelays()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,4 @@
|
|||||||
import {
|
import { Button, Input, Layout, Spinner, TopNavigation, useTheme } from '@ui-kitten/components'
|
||||||
Button,
|
|
||||||
Input,
|
|
||||||
Layout,
|
|
||||||
Spinner,
|
|
||||||
TopNavigation,
|
|
||||||
TopNavigationAction,
|
|
||||||
useTheme,
|
|
||||||
} from '@ui-kitten/components'
|
|
||||||
import React, { useContext, useEffect, useState } from 'react'
|
import React, { useContext, useEffect, useState } from 'react'
|
||||||
import { StyleSheet } from 'react-native'
|
import { StyleSheet } from 'react-native'
|
||||||
import { AppContext } from '../../Contexts/AppContext'
|
import { AppContext } from '../../Contexts/AppContext'
|
||||||
@ -87,9 +79,10 @@ export const SendPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderBackAction = (): JSX.Element => (
|
const renderBackAction = (): JSX.Element => (
|
||||||
<TopNavigationAction
|
<Button
|
||||||
icon={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
accessoryRight={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||||
onPress={onPressBack}
|
onPress={onPressBack}
|
||||||
|
appearance='ghost'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,14 +1,8 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react'
|
import React, { useContext, useEffect, useState } from 'react'
|
||||||
import Relay from '../lib/nostr/Relay'
|
|
||||||
import { Event } from '../lib/nostr/Events'
|
|
||||||
import RelayPool from '../lib/nostr/RelayPool/intex'
|
import RelayPool from '../lib/nostr/RelayPool/intex'
|
||||||
import { AppContext } from './AppContext'
|
import { AppContext } from './AppContext'
|
||||||
import { getRelays, addRelay } from '../Functions/DatabaseFunctions/Relays'
|
|
||||||
import { showMessage } from 'react-native-flash-message'
|
|
||||||
import SInfo from 'react-native-sensitive-info'
|
import SInfo from 'react-native-sensitive-info'
|
||||||
import { getPublickey } from '../lib/nostr/Bip'
|
import { getPublickey } from '../lib/nostr/Bip'
|
||||||
import { defaultRelays } from '../Constants/RelayConstants'
|
|
||||||
import WebsocketModule from '../lib/nostr/Native/WebsocketModule'
|
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
|
||||||
export interface RelayPoolContextProps {
|
export interface RelayPoolContextProps {
|
||||||
@ -50,31 +44,9 @@ export const RelayPoolContextProvider = ({
|
|||||||
const [lastPage, setLastPage] = useState<string>(page)
|
const [lastPage, setLastPage] = useState<string>(page)
|
||||||
|
|
||||||
const loadRelayPool: () => void = async () => {
|
const loadRelayPool: () => void = async () => {
|
||||||
if (database) {
|
if (database && publicKey) {
|
||||||
const relays = await getRelays(database)
|
|
||||||
const initRelayPool = new RelayPool([], privateKey)
|
const initRelayPool = new RelayPool([], privateKey)
|
||||||
if (relays && relays.length > 0) {
|
initRelayPool.connect(publicKey)
|
||||||
relays.forEach((relay) => {
|
|
||||||
initRelayPool.add(relay.url)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// pickRandomItems(defaultRelays, 1).forEach((relayUrl) => {
|
|
||||||
initRelayPool.add(defaultRelays[4])
|
|
||||||
addRelay({ url: defaultRelays[4] }, database)
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
|
|
||||||
initRelayPool?.on(
|
|
||||||
'notice',
|
|
||||||
'RelayPoolContextProvider',
|
|
||||||
async (relay: Relay, _subId?: string, event?: Event) => {
|
|
||||||
showMessage({
|
|
||||||
message: relay.url,
|
|
||||||
description: event?.content ?? '',
|
|
||||||
type: 'info',
|
|
||||||
})
|
|
||||||
},
|
|
||||||
)
|
|
||||||
setRelayPool(initRelayPool)
|
setRelayPool(initRelayPool)
|
||||||
setLoadingRelayPool(false)
|
setLoadingRelayPool(false)
|
||||||
}
|
}
|
||||||
@ -86,22 +58,17 @@ export const RelayPoolContextProvider = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
WebsocketModule.connectWebsocket((message) => {
|
|
||||||
console.log('WEBSOCKET', message)
|
|
||||||
setClock()
|
setClock()
|
||||||
})
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (relayPool && lastPage !== page) {
|
if (relayPool && lastPage !== page) {
|
||||||
relayPool.removeOn('event', lastPage)
|
|
||||||
setLastPage(page)
|
setLastPage(page)
|
||||||
}
|
}
|
||||||
}, [page])
|
}, [page])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (publicKey && publicKey !== '') {
|
if (publicKey && publicKey !== '') {
|
||||||
WebsocketModule.setUserPubKey(publicKey)
|
|
||||||
if (!loadingRelayPool && page !== 'landing') {
|
if (!loadingRelayPool && page !== 'landing') {
|
||||||
goToPage('home', true)
|
goToPage('home', true)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { QueryResult, QuickSQLiteConnection } from 'react-native-quick-sqlite'
|
import { QuickSQLiteConnection } from 'react-native-quick-sqlite'
|
||||||
import { getItems } from '..'
|
import { getItems } from '..'
|
||||||
|
|
||||||
export interface Relay {
|
export interface Relay {
|
||||||
@ -10,38 +10,6 @@ const databaseToEntity: (object: any) => Relay = (object) => {
|
|||||||
return object as Relay
|
return object as Relay
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addRelay: (
|
|
||||||
relay: Relay,
|
|
||||||
db: QuickSQLiteConnection,
|
|
||||||
) => Promise<QueryResult | undefined> = async (relay, db) => {
|
|
||||||
if (relay.url) {
|
|
||||||
const relays: Relay[] = await searchRelays(relay.url, db)
|
|
||||||
if (relays.length === 0) {
|
|
||||||
const query = `
|
|
||||||
INSERT OR IGNORE INTO nostros_relays
|
|
||||||
(url)
|
|
||||||
VALUES
|
|
||||||
(?);
|
|
||||||
`
|
|
||||||
const queryValues = [relay.url.split("'").join("''")]
|
|
||||||
|
|
||||||
return db.execute(query, queryValues)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const removeRelay: (
|
|
||||||
relay: Relay,
|
|
||||||
db: QuickSQLiteConnection,
|
|
||||||
) => Promise<QueryResult | undefined> = async (relay, db) => {
|
|
||||||
if (relay.url) {
|
|
||||||
const query = `
|
|
||||||
DELETE FROM nostros_relays WHERE url=?;
|
|
||||||
`
|
|
||||||
return db.execute(query, [relay.url])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const searchRelays: (
|
export const searchRelays: (
|
||||||
relayUrl: string,
|
relayUrl: string,
|
||||||
db: QuickSQLiteConnection,
|
db: QuickSQLiteConnection,
|
||||||
|
@ -22,7 +22,7 @@ export const isDirectReply: (mainEvent: Event, reply: Event) => boolean = (mainE
|
|||||||
const taggedReplyEventsIds: string[] = getTaggedEventIds(reply)
|
const taggedReplyEventsIds: string[] = getTaggedEventIds(reply)
|
||||||
const difference = taggedReplyEventsIds.filter((item) => !taggedMainEventsIds.includes(item))
|
const difference = taggedReplyEventsIds.filter((item) => !taggedMainEventsIds.includes(item))
|
||||||
|
|
||||||
return difference.length === 1 && difference[0] === mainEvent.id
|
return difference.length === 1 && difference[0] === mainEvent?.id
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getTaggedEventIds: (event: Event) => string[] = (event) => {
|
export const getTaggedEventIds: (event: Event) => string[] = (event) => {
|
||||||
@ -31,5 +31,5 @@ export const getTaggedEventIds: (event: Event) => string[] = (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getETags: (event: Event) => string[][] = (event) => {
|
export const getETags: (event: Event) => string[][] = (event) => {
|
||||||
return event.tags.filter((tag) => tag[0] === 'e')
|
return event?.tags.filter((tag) => tag[0] === 'e') || []
|
||||||
}
|
}
|
||||||
|
11
frontend/lib/Native/WebsocketModule/index.ts
Normal file
11
frontend/lib/Native/WebsocketModule/index.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { NativeModules } from 'react-native'
|
||||||
|
const { RelayPoolModule } = NativeModules
|
||||||
|
|
||||||
|
interface RelayPoolInterface {
|
||||||
|
send: (message: string) => void
|
||||||
|
connect: (pubKey: string, callback: () => void) => void
|
||||||
|
add: (url: string, callback: () => void) => void
|
||||||
|
remove: (url: string, callback: () => void) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RelayPoolModule as RelayPoolInterface
|
@ -1,10 +0,0 @@
|
|||||||
import { NativeModules } from 'react-native'
|
|
||||||
const { WebsocketModule } = NativeModules
|
|
||||||
|
|
||||||
interface WebsocketInterface {
|
|
||||||
connectWebsocket: (callback: (message: string) => void) => void
|
|
||||||
send: (message: string) => void
|
|
||||||
setUserPubKey: (pubKey: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WebsocketModule as WebsocketInterface
|
|
@ -1,131 +0,0 @@
|
|||||||
import { Event } from '../Events'
|
|
||||||
import { v5 as uuidv5 } from 'uuid'
|
|
||||||
import WebsocketModule from '../Native/WebsocketModule'
|
|
||||||
|
|
||||||
export interface RelayFilters {
|
|
||||||
ids?: string[]
|
|
||||||
authors?: string[]
|
|
||||||
kinds?: number[]
|
|
||||||
'#e'?: string[]
|
|
||||||
'#p'?: string[]
|
|
||||||
since?: number
|
|
||||||
limit?: number
|
|
||||||
until?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RelayMessage {
|
|
||||||
data: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RelayOptions {
|
|
||||||
reconnect?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
class Relay {
|
|
||||||
constructor(relayUrl: string, options: RelayOptions = { reconnect: true }) {
|
|
||||||
this.url = relayUrl
|
|
||||||
this.options = options
|
|
||||||
this.manualClose = false
|
|
||||||
this.socket = new WebSocket(this.url)
|
|
||||||
this.subscriptions = {}
|
|
||||||
|
|
||||||
this.onOpen = () => {}
|
|
||||||
this.onEvent = () => {}
|
|
||||||
this.onEsoe = () => {}
|
|
||||||
this.onNotice = () => {}
|
|
||||||
|
|
||||||
this.initWebsocket()
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly options: RelayOptions
|
|
||||||
private socket: WebSocket
|
|
||||||
private manualClose: boolean
|
|
||||||
private subscriptions: { [subId: string]: string[] }
|
|
||||||
|
|
||||||
private readonly initWebsocket: () => void = async () => {
|
|
||||||
this.socket.onmessage = (message) => {
|
|
||||||
this.handleNostrMessage(message as RelayMessage)
|
|
||||||
}
|
|
||||||
this.socket.onclose = this.onClose
|
|
||||||
this.socket.onerror = this.onError
|
|
||||||
this.socket.onopen = () => this.onOpen(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly onClose: () => void = async () => {
|
|
||||||
if (!this.manualClose && this.options.reconnect) this.initWebsocket()
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly onError: () => void = async () => {
|
|
||||||
if (this.options.reconnect) this.initWebsocket()
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly handleNostrMessage: (message: RelayMessage) => void = async (message) => {
|
|
||||||
const data: any[] = JSON.parse(message.data)
|
|
||||||
|
|
||||||
if (data.length >= 2) {
|
|
||||||
const id: string = data[1]
|
|
||||||
if (data[0] === 'EVENT') {
|
|
||||||
if (data.length < 3) return
|
|
||||||
const message: Event = data[2]
|
|
||||||
return this.onEvent(this, id, message)
|
|
||||||
} else if (data[0] === 'EOSE') {
|
|
||||||
return this.onEsoe(this, id)
|
|
||||||
} else if (data[0] === 'NOTICE') {
|
|
||||||
return this.onNotice(this, [...data.slice(1)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly send: (message: object) => void = async (message) => {
|
|
||||||
const tosend = JSON.stringify(message)
|
|
||||||
console.log('SEND =====>', tosend)
|
|
||||||
WebsocketModule.send(tosend)
|
|
||||||
}
|
|
||||||
|
|
||||||
public url: string
|
|
||||||
public onOpen: (relay: Relay) => void
|
|
||||||
public onEvent: (relay: Relay, subId: string, event: Event) => void
|
|
||||||
public onEsoe: (relay: Relay, subId: string) => void
|
|
||||||
public onNotice: (relay: Relay, events: Event[]) => void
|
|
||||||
|
|
||||||
public readonly close: () => void = async () => {
|
|
||||||
if (this.socket) {
|
|
||||||
this.manualClose = true
|
|
||||||
this.socket.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly sendEvent: (event: Event) => void = async (event) => {
|
|
||||||
this.send(['EVENT', event])
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly subscribe: (subId: string, filters?: RelayFilters) => void = async (
|
|
||||||
subId,
|
|
||||||
filters = {},
|
|
||||||
) => {
|
|
||||||
const uuid = uuidv5(
|
|
||||||
`${subId}${JSON.stringify(filters)}`,
|
|
||||||
'57003344-b2cb-4b6f-a579-fae9e82c370a',
|
|
||||||
)
|
|
||||||
if (this.subscriptions[subId]?.includes(uuid)) {
|
|
||||||
console.log('Subscription already done!')
|
|
||||||
} else {
|
|
||||||
this.send(['REQ', subId, filters])
|
|
||||||
const newSubscriptions = [...(this.subscriptions[subId] ?? []), uuid]
|
|
||||||
this.subscriptions[subId] = newSubscriptions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly unsubscribe: (subId: string) => void = async (subId) => {
|
|
||||||
this.send(['CLOSE', subId])
|
|
||||||
delete this.subscriptions[subId]
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly unsubscribeAll: () => void = async () => {
|
|
||||||
Object.keys(this.subscriptions).forEach((subId: string) => {
|
|
||||||
this.unsubscribe(subId)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Relay
|
|
@ -1,105 +1,60 @@
|
|||||||
// import { spawnThread } from 'react-native-multithreading'
|
// import { spawnThread } from 'react-native-multithreading'
|
||||||
import { signEvent, validateEvent, Event } from '../Events'
|
import { signEvent, validateEvent, Event } from '../Events'
|
||||||
import Relay, { RelayFilters, RelayOptions } from '../Relay'
|
import { v5 as uuidv5 } from 'uuid'
|
||||||
|
import RelayPoolModule from '../../Native/WebsocketModule'
|
||||||
|
|
||||||
export interface OnFunctions {
|
export interface RelayFilters {
|
||||||
open: { [id: string]: (relay: Relay) => void }
|
ids?: string[]
|
||||||
event: { [id: string]: (relay: Relay, subId: string, event: Event) => void }
|
authors?: string[]
|
||||||
esoe: { [id: string]: (relay: Relay, subId: string) => void }
|
kinds?: number[]
|
||||||
notice: { [id: string]: (relay: Relay, events: Event[]) => void }
|
'#e'?: string[]
|
||||||
|
'#p'?: string[]
|
||||||
|
since?: number
|
||||||
|
limit?: number
|
||||||
|
until?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RelayMessage {
|
||||||
|
data: string
|
||||||
}
|
}
|
||||||
|
|
||||||
class RelayPool {
|
class RelayPool {
|
||||||
constructor(relaysUrls: string[], privateKey?: string, options: RelayOptions = {}) {
|
constructor(relaysUrls: string[], privateKey?: string) {
|
||||||
this.relays = {}
|
|
||||||
this.privateKey = privateKey
|
this.privateKey = privateKey
|
||||||
this.options = options
|
this.subscriptions = {}
|
||||||
|
|
||||||
this.onFunctions = {
|
|
||||||
open: {},
|
|
||||||
event: {},
|
|
||||||
esoe: {},
|
|
||||||
notice: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
relaysUrls.forEach((relayUrl) => {
|
relaysUrls.forEach((relayUrl) => {
|
||||||
this.add(relayUrl)
|
this.add(relayUrl)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.setupHandlers()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly privateKey?: string
|
private readonly privateKey?: string
|
||||||
private readonly options: RelayOptions
|
private subscriptions: { [subId: string]: string[] }
|
||||||
private readonly onFunctions: OnFunctions
|
|
||||||
public relays: { [url: string]: Relay }
|
|
||||||
|
|
||||||
private readonly setupHandlers: () => void = () => {
|
private readonly send: (message: object) => void = async (message) => {
|
||||||
Object.keys(this.relays).forEach((relayUrl: string) => {
|
const tosend = JSON.stringify(message)
|
||||||
const relay: Relay = this.relays[relayUrl]
|
RelayPoolModule.send(tosend)
|
||||||
|
|
||||||
relay.onOpen = (openRelay) => {
|
|
||||||
Object.keys(this.onFunctions.open).forEach((id) => this.onFunctions.open[id](openRelay))
|
|
||||||
}
|
|
||||||
relay.onEvent = (eventRelay, subId, event) => {
|
|
||||||
Object.keys(this.onFunctions.event).forEach((id) => {
|
|
||||||
this.onFunctions.event[id](eventRelay, subId, event)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
relay.onEsoe = (eventRelay, subId) => {
|
|
||||||
Object.keys(this.onFunctions.esoe).forEach((id) =>
|
|
||||||
this.onFunctions.esoe[id](eventRelay, subId),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
relay.onNotice = (eventRelay, events) => {
|
|
||||||
Object.keys(this.onFunctions.notice).forEach((id) =>
|
|
||||||
this.onFunctions.notice[id](eventRelay, events),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public on: (
|
public readonly connect: (publicKey: string, callback?: () => void) => void = async (
|
||||||
method: 'open' | 'event' | 'esoe' | 'notice',
|
publicKey,
|
||||||
id: string,
|
callback = () => {},
|
||||||
fn: (relay: Relay, subId?: string, event?: Event) => void,
|
|
||||||
) => void = async (method, id, fn) => {
|
|
||||||
this.onFunctions[method][id] = fn
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeOn: (method: 'open' | 'event' | 'esoe' | 'notice', id: string) => void = async (
|
|
||||||
method,
|
|
||||||
id,
|
|
||||||
) => {
|
) => {
|
||||||
delete this.onFunctions[method][id]
|
RelayPoolModule.connect(publicKey, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly add: (relayUrl: string) => Promise<boolean> = async (relayUrl) => {
|
public readonly add: (relayUrl: string, callback?: () => void) => void = async (
|
||||||
if (this.relays[relayUrl]) return false
|
relayUrl,
|
||||||
|
callback = () => {},
|
||||||
this.relays[relayUrl] = new Relay(relayUrl, this.options)
|
) => {
|
||||||
this.setupHandlers()
|
RelayPoolModule.add(relayUrl, callback)
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly close: () => void = async () => {
|
public readonly remove: (relayUrl: string, callback?: () => void) => void = async (
|
||||||
Object.keys(this.relays).forEach((relayUrl: string) => {
|
relayUrl,
|
||||||
const relay: Relay = this.relays[relayUrl]
|
callback = () => {},
|
||||||
relay.close()
|
) => {
|
||||||
})
|
RelayPoolModule.remove(relayUrl, callback)
|
||||||
}
|
|
||||||
|
|
||||||
public readonly remove: (relayUrl: string) => Promise<boolean> = async (relayUrl) => {
|
|
||||||
const relay: Relay | undefined = this.relays[relayUrl]
|
|
||||||
|
|
||||||
if (relay) {
|
|
||||||
relay.close()
|
|
||||||
delete this.relays[relayUrl]
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly sendEvent: (event: Event) => Promise<Event | null> = async (event) => {
|
public readonly sendEvent: (event: Event) => Promise<Event | null> = async (event) => {
|
||||||
@ -107,10 +62,7 @@ class RelayPool {
|
|||||||
const signedEvent: Event = await signEvent(event, this.privateKey)
|
const signedEvent: Event = await signEvent(event, this.privateKey)
|
||||||
|
|
||||||
if (validateEvent(signedEvent)) {
|
if (validateEvent(signedEvent)) {
|
||||||
Object.keys(this.relays).forEach((relayUrl: string) => {
|
this.send(['EVENT', event])
|
||||||
const relay: Relay = this.relays[relayUrl]
|
|
||||||
relay.sendEvent(signedEvent)
|
|
||||||
})
|
|
||||||
|
|
||||||
return signedEvent
|
return signedEvent
|
||||||
} else {
|
} else {
|
||||||
@ -126,20 +78,27 @@ class RelayPool {
|
|||||||
subId,
|
subId,
|
||||||
filters,
|
filters,
|
||||||
) => {
|
) => {
|
||||||
Object.keys(this.relays).forEach((relayUrl: string) => {
|
const uuid = uuidv5(
|
||||||
this.relays[relayUrl].subscribe(subId, filters)
|
`${subId}${JSON.stringify(filters)}`,
|
||||||
})
|
'57003344-b2cb-4b6f-a579-fae9e82c370a',
|
||||||
|
)
|
||||||
|
if (this.subscriptions[subId]?.includes(uuid)) {
|
||||||
|
console.log('Subscription already done!', filters)
|
||||||
|
} else {
|
||||||
|
this.send(['REQ', subId, filters])
|
||||||
|
const newSubscriptions = [...(this.subscriptions[subId] ?? []), uuid]
|
||||||
|
this.subscriptions[subId] = newSubscriptions
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly unsubscribe: (subId: string) => void = async (subId) => {
|
public readonly unsubscribe: (subId: string) => void = async (subId) => {
|
||||||
Object.keys(this.relays).forEach((relayUrl: string) => {
|
this.send(['CLOSE', subId])
|
||||||
this.relays[relayUrl].unsubscribe(subId)
|
delete this.subscriptions[subId]
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly unsubscribeAll: () => void = async () => {
|
public readonly unsubscribeAll: () => void = async () => {
|
||||||
Object.keys(this.relays).forEach((relayUrl: string) => {
|
Object.keys(this.subscriptions).forEach((subId: string) => {
|
||||||
this.relays[relayUrl].unsubscribeAll()
|
this.unsubscribe(subId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user