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.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.nostros.modules.WebsocketModule;
|
||||
import com.nostros.modules.RelayPoolModule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@ -21,7 +21,7 @@ public class NostrosPackage implements ReactPackage {
|
||||
ReactApplicationContext reactContext) {
|
||||
List<NativeModule> modules = new ArrayList<>();
|
||||
|
||||
modules.add(new WebsocketModule(reactContext));
|
||||
modules.add(new RelayPoolModule(reactContext));
|
||||
|
||||
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 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.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.nostros.modules.DatabaseModule;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class WebsocketModule extends ReactContextBaseJavaModule {
|
||||
public class Websocket {
|
||||
private WebSocket webSocket;
|
||||
private DatabaseModule database;
|
||||
private String userPubKey;
|
||||
private String url;
|
||||
|
||||
public WebsocketModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
database = new DatabaseModule(reactContext);
|
||||
public Websocket(String serverUrl, DatabaseModule databaseModule) {
|
||||
database = databaseModule;
|
||||
url = serverUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "WebsocketModule";
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void send(String message) {
|
||||
Log.d("Websocket", "SEND URL:" + url + " __ " + message);
|
||||
webSocket.sendText(message);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void connectWebsocket(Callback callBack) throws IOException {
|
||||
public void disconnect() {
|
||||
webSocket.disconnect();
|
||||
}
|
||||
|
||||
public void connect(String userPubKey) throws IOException {
|
||||
WebSocketFactory factory = new WebSocketFactory();
|
||||
webSocket = factory.createSocket("wss://relay.damus.io");
|
||||
webSocket = factory.createSocket(url);
|
||||
webSocket.addListener(new WebSocketAdapter() {
|
||||
@Override
|
||||
public void onConnected(WebSocket ws, Map<String, List<String>> headers) throws Exception
|
||||
{
|
||||
callBack.invoke("connected");
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
if (jsonArray.get(0).toString().equals("EVENT")) {
|
||||
database.saveEvent(jsonArray.getJSONObject(2), userPubKey);
|
||||
@ -77,9 +64,4 @@ public class WebsocketModule extends ReactContextBaseJavaModule {
|
||||
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;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
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.Relay;
|
||||
|
||||
import org.json.JSONException;
|
||||
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;
|
||||
|
||||
DatabaseModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
String dbPath = reactContext.getFilesDir().getAbsolutePath();
|
||||
database = SQLiteDatabase.openDatabase( dbPath + "/nostros.sqlite", null, SQLiteDatabase.OPEN_READWRITE);
|
||||
DatabaseModule(String absoluteFilesPath) {
|
||||
database = SQLiteDatabase.openDatabase( absoluteFilesPath + "/nostros.sqlite", null, SQLiteDatabase.OPEN_READWRITE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "DatabaseModule";
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void saveEvent(JSONObject data, String userPubKey) throws JSONException {
|
||||
Event event = new Event(data);
|
||||
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 {
|
||||
Button,
|
||||
Divider,
|
||||
Input,
|
||||
Layout,
|
||||
TopNavigation,
|
||||
TopNavigationAction,
|
||||
useTheme,
|
||||
} from '@ui-kitten/components'
|
||||
import { Button, Divider, Input, Layout, TopNavigation, useTheme } from '@ui-kitten/components'
|
||||
import React, { useContext, useEffect } from 'react'
|
||||
import { Clipboard, StyleSheet } from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
@ -49,9 +41,10 @@ export const ConfigPage: React.FC = () => {
|
||||
}
|
||||
|
||||
const renderBackAction = (): JSX.Element => (
|
||||
<TopNavigationAction
|
||||
icon={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||
<Button
|
||||
accessoryRight={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||
onPress={onPressBack}
|
||||
appearance='ghost'
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -12,7 +12,7 @@ import { populatePets } from '../../Functions/RelayFunctions/Users'
|
||||
|
||||
export const ContactsPage: React.FC = () => {
|
||||
const { database } = useContext(AppContext)
|
||||
const { relayPool, publicKey, privateKey } = useContext(RelayPoolContext)
|
||||
const { relayPool, publicKey, privateKey, lastEventId } = useContext(RelayPoolContext)
|
||||
const theme = useTheme()
|
||||
const [users, setUsers] = useState<User[]>()
|
||||
const [refreshing, setRefreshing] = useState(true)
|
||||
@ -22,6 +22,10 @@ export const ContactsPage: React.FC = () => {
|
||||
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
useEffect(() => {
|
||||
loadUsers()
|
||||
}, [lastEventId])
|
||||
|
||||
useEffect(() => {
|
||||
setUsers([])
|
||||
loadUsers()
|
||||
@ -36,12 +40,15 @@ export const ContactsPage: React.FC = () => {
|
||||
}
|
||||
|
||||
getUsers(database, filters).then((results) => {
|
||||
if (results) {
|
||||
if (results && results.length > 0) {
|
||||
setUsers(results)
|
||||
relayPool?.subscribe('main-channel', {
|
||||
kinds: [EventKind.meta],
|
||||
authors: results.map((user) => user.id),
|
||||
})
|
||||
const missingDataUsers = results.filter((user) => !user.picture).map((user) => user.id)
|
||||
if (missingDataUsers.length > 0) {
|
||||
relayPool?.subscribe('main-channel', {
|
||||
kinds: [EventKind.meta],
|
||||
authors: missingDataUsers,
|
||||
})
|
||||
}
|
||||
}
|
||||
setRefreshing(false)
|
||||
})
|
||||
|
@ -15,11 +15,11 @@ import NoteCard from '../NoteCard'
|
||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import { RelayFilters } from '../../lib/nostr/Relay'
|
||||
import { getReplyEventId } from '../../Functions/RelayFunctions/Events'
|
||||
import { getUsers, User } from '../../Functions/DatabaseFunctions/Users'
|
||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||
import Loading from '../Loading'
|
||||
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
|
||||
|
||||
export const HomePage: React.FC = () => {
|
||||
const { database, goToPage } = useContext(AppContext)
|
||||
@ -33,7 +33,7 @@ export const HomePage: React.FC = () => {
|
||||
|
||||
const calculateInitialNotes: () => Promise<void> = async () => {
|
||||
if (database && publicKey) {
|
||||
setTimeout(() => setRefreshing(false), 3000)
|
||||
setTimeout(() => setRefreshing(false), 2000)
|
||||
const users = await getUsers(database, { contacts: true, includeIds: [publicKey] })
|
||||
setAuthors(users)
|
||||
subscribeNotes(users)
|
||||
@ -43,21 +43,12 @@ export const HomePage: React.FC = () => {
|
||||
const subscribeNotes: (users: User[], past?: boolean) => void = (users, past) => {
|
||||
if (!database || !publicKey || users.length === 0) return
|
||||
|
||||
const limit = past ? pageSize : initialPageSize
|
||||
getNotes(database, { contacts: true, includeIds: [publicKey], limit }).then((results) => {
|
||||
const message: RelayFilters = {
|
||||
kinds: [EventKind.textNote, EventKind.recommendServer],
|
||||
authors: users.map((user) => user.id),
|
||||
limit: initialPageSize,
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
const message: RelayFilters = {
|
||||
kinds: [EventKind.textNote, EventKind.recommendServer],
|
||||
authors: users.map((user) => user.id),
|
||||
limit: pageSize,
|
||||
}
|
||||
relayPool?.subscribe('main-channel', message)
|
||||
}
|
||||
|
||||
const loadNotes: () => void = () => {
|
||||
@ -65,6 +56,15 @@ export const HomePage: React.FC = () => {
|
||||
getNotes(database, { contacts: true, includeIds: [publicKey], limit: pageSize }).then(
|
||||
(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],
|
||||
authors: [publicKey],
|
||||
})
|
||||
setTimeout(() => goToPage('home', true), 3000)
|
||||
setTimeout(() => goToPage('home', true), 5000)
|
||||
}
|
||||
}, [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 { Note } from '../../Functions/DatabaseFunctions/Notes'
|
||||
import { StyleSheet, TouchableOpacity } from 'react-native'
|
||||
@ -6,7 +6,6 @@ import Markdown from 'react-native-markdown-display'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { addRelay } from '../../Functions/DatabaseFunctions/Relays'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { showMessage } from 'react-native-flash-message'
|
||||
import { t } from 'i18next'
|
||||
@ -15,6 +14,7 @@ import moment from 'moment'
|
||||
import { populateRelay } from '../../Functions/RelayFunctions'
|
||||
import Avatar from '../Avatar'
|
||||
import { markdownIt, markdownStyle } from '../../Constants/AppConstants'
|
||||
import { searchRelays } from '../../Functions/DatabaseFunctions/Relays'
|
||||
|
||||
interface NoteCardProps {
|
||||
note: Note
|
||||
@ -22,11 +22,17 @@ interface NoteCardProps {
|
||||
|
||||
export const NoteCard: React.FC<NoteCardProps> = ({ note }) => {
|
||||
const theme = useTheme()
|
||||
const { relayPool, setRelayPool, publicKey } = useContext(RelayPoolContext)
|
||||
const { relayPool, publicKey } = useContext(RelayPoolContext)
|
||||
const { database, goToPage } = useContext(AppContext)
|
||||
const [relayAdded, setRelayAdded] = useState<boolean>(
|
||||
Object.keys(relayPool?.relays ?? {}).includes(note.content),
|
||||
)
|
||||
const [relayAdded, setRelayAdded] = useState<boolean>(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (database) {
|
||||
searchRelays(note.content, database).then((result) => {
|
||||
setRelayAdded(result.length > 0)
|
||||
})
|
||||
}
|
||||
}, [database])
|
||||
|
||||
const textNote: () => JSX.Element = () => {
|
||||
return (
|
||||
@ -73,16 +79,15 @@ export const NoteCard: React.FC<NoteCardProps> = ({ note }) => {
|
||||
|
||||
const addRelayItem: () => void = () => {
|
||||
if (relayPool && database && publicKey) {
|
||||
relayPool.add(note.content)
|
||||
setRelayPool(relayPool)
|
||||
addRelay({ url: note.content }, database)
|
||||
populateRelay(relayPool, database, publicKey)
|
||||
showMessage({
|
||||
message: t('alerts.relayAdded'),
|
||||
description: note.content,
|
||||
type: 'success',
|
||||
relayPool.add(note.content, () => {
|
||||
populateRelay(relayPool, database, publicKey)
|
||||
showMessage({
|
||||
message: t('alerts.relayAdded'),
|
||||
description: note.content,
|
||||
type: 'success',
|
||||
})
|
||||
setRelayAdded(true)
|
||||
})
|
||||
setRelayAdded(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,4 @@
|
||||
import {
|
||||
Card,
|
||||
Layout,
|
||||
Spinner,
|
||||
TopNavigation,
|
||||
TopNavigationAction,
|
||||
useTheme,
|
||||
} from '@ui-kitten/components'
|
||||
import { Button, Card, Layout, Spinner, TopNavigation, useTheme } from '@ui-kitten/components'
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
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 NoteCard from '../NoteCard'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import { RelayFilters } from '../../lib/nostr/Relay'
|
||||
import {
|
||||
Clipboard,
|
||||
RefreshControl,
|
||||
@ -25,6 +17,7 @@ import {
|
||||
} from 'react-native'
|
||||
import Loading from '../Loading'
|
||||
import { getDirectReplies, getReplyEventId } from '../../Functions/RelayFunctions/Events'
|
||||
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
|
||||
|
||||
export const NotePage: React.FC = () => {
|
||||
const { page, goBack, goToPage, database, getActualPage } = useContext(AppContext)
|
||||
@ -113,18 +106,20 @@ export const NotePage: React.FC = () => {
|
||||
|
||||
const renderBackAction = (): JSX.Element => {
|
||||
return (
|
||||
<TopNavigationAction
|
||||
icon={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||
<Button
|
||||
accessoryRight={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||
onPress={onPressBack}
|
||||
appearance='ghost'
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const renderNoteActions = (): JSX.Element => {
|
||||
return note && getReplyEventId(note) ? (
|
||||
<TopNavigationAction
|
||||
icon={<Icon name='arrow-up' size={16} color={theme['text-basic-color']} />}
|
||||
<Button
|
||||
accessoryRight={<Icon name='arrow-up' size={16} color={theme['text-basic-color']} />}
|
||||
onPress={onPressGoParent}
|
||||
appearance='ghost'
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
|
@ -1,12 +1,4 @@
|
||||
import {
|
||||
Card,
|
||||
Layout,
|
||||
Spinner,
|
||||
Text,
|
||||
TopNavigation,
|
||||
TopNavigationAction,
|
||||
useTheme,
|
||||
} from '@ui-kitten/components'
|
||||
import { Button, Card, Layout, Spinner, Text, TopNavigation, useTheme } from '@ui-kitten/components'
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
||||
import {
|
||||
Clipboard,
|
||||
@ -24,13 +16,13 @@ import NoteCard from '../NoteCard'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { getUser, User, updateUserContact } from '../../Functions/DatabaseFunctions/Users'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import { RelayFilters } from '../../lib/nostr/Relay'
|
||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||
import { populatePets } from '../../Functions/RelayFunctions/Users'
|
||||
import { getReplyEventId } from '../../Functions/RelayFunctions/Events'
|
||||
import Loading from '../Loading'
|
||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||
import Avatar from '../Avatar'
|
||||
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
|
||||
|
||||
export const ProfilePage: React.FC = () => {
|
||||
const { database, page, goToPage, goBack } = useContext(AppContext)
|
||||
@ -80,20 +72,14 @@ export const ProfilePage: React.FC = () => {
|
||||
|
||||
const subscribeNotes: (past?: boolean) => void = (past) => {
|
||||
if (!database) return
|
||||
const limit = past ? pageSize : initialPageSize
|
||||
getNotes(database, { filters: { pubkey: userId }, limit }).then((results) => {
|
||||
const message: RelayFilters = {
|
||||
kinds: [EventKind.textNote, EventKind.recommendServer],
|
||||
authors: [userId],
|
||||
limit: initialPageSize,
|
||||
}
|
||||
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)
|
||||
})
|
||||
|
||||
const message: RelayFilters = {
|
||||
kinds: [EventKind.textNote, EventKind.recommendServer],
|
||||
authors: [userId],
|
||||
limit: pageSize,
|
||||
}
|
||||
|
||||
relayPool?.subscribe('main-channel', message)
|
||||
}
|
||||
|
||||
const subscribeProfile: () => Promise<void> = async () => {
|
||||
@ -148,25 +134,32 @@ export const ProfilePage: React.FC = () => {
|
||||
const renderOptions: () => JSX.Element = () => {
|
||||
if (publicKey === userId) {
|
||||
return (
|
||||
<TopNavigationAction
|
||||
icon={<Icon name='cog' size={16} color={theme['text-basic-color']} solid />}
|
||||
<Button
|
||||
accessoryRight={<Icon name='cog' size={16} color={theme['text-basic-color']} solid />}
|
||||
onPress={() => goToPage('config')}
|
||||
appearance='ghost'
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
if (user) {
|
||||
if (isContact) {
|
||||
return (
|
||||
<TopNavigationAction
|
||||
icon={<Icon name='user-minus' size={16} color={theme['color-danger-500']} solid />}
|
||||
<Button
|
||||
accessoryRight={
|
||||
<Icon name='user-minus' size={16} color={theme['color-danger-500']} solid />
|
||||
}
|
||||
onPress={removeAuthor}
|
||||
appearance='ghost'
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<TopNavigationAction
|
||||
icon={<Icon name='user-plus' size={16} color={theme['color-success-500']} solid />}
|
||||
<Button
|
||||
accessoryRight={
|
||||
<Icon name='user-plus' size={16} color={theme['color-success-500']} solid />
|
||||
}
|
||||
onPress={addAuthor}
|
||||
appearance='ghost'
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -177,7 +170,6 @@ export const ProfilePage: React.FC = () => {
|
||||
}
|
||||
|
||||
const onPressBack: () => void = () => {
|
||||
relayPool?.removeOn('event', 'profile')
|
||||
relayPool?.unsubscribeAll()
|
||||
goBack()
|
||||
}
|
||||
@ -187,9 +179,10 @@ export const ProfilePage: React.FC = () => {
|
||||
return <></>
|
||||
} else {
|
||||
return (
|
||||
<TopNavigationAction
|
||||
icon={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||
<Button
|
||||
accessoryRight={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||
onPress={onPressBack}
|
||||
appearance='ghost'
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -273,8 +266,7 @@ export const ProfilePage: React.FC = () => {
|
||||
|
||||
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
|
||||
if (handleInfinityScroll(event)) {
|
||||
const newSize: number = notes?.length === pageSize ? pageSize + initialPageSize : pageSize
|
||||
setPageSize(newSize)
|
||||
setPageSize(pageSize + initialPageSize)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,26 +1,18 @@
|
||||
import {
|
||||
Button,
|
||||
Layout,
|
||||
TopNavigation,
|
||||
TopNavigationAction,
|
||||
useTheme,
|
||||
Text,
|
||||
} from '@ui-kitten/components'
|
||||
import { Button, Layout, TopNavigation, useTheme, Text } from '@ui-kitten/components'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { RefreshControl, ScrollView, StyleSheet } from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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 { populateRelay } from '../../Functions/RelayFunctions'
|
||||
import { showMessage } from 'react-native-flash-message'
|
||||
|
||||
export const RelaysPage: React.FC = () => {
|
||||
const theme = useTheme()
|
||||
const { goBack, database } = useContext(AppContext)
|
||||
const { relayPool, publicKey, setRelayPool } = useContext(RelayPoolContext)
|
||||
const { relayPool, publicKey } = useContext(RelayPoolContext)
|
||||
const { t } = useTranslation('common')
|
||||
const [relays, setRelays] = useState<Relay[]>([])
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
@ -43,40 +35,38 @@ export const RelaysPage: React.FC = () => {
|
||||
}
|
||||
|
||||
const renderBackAction = (): JSX.Element => (
|
||||
<TopNavigationAction
|
||||
icon={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||
<Button
|
||||
accessoryRight={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||
onPress={onPressBack}
|
||||
appearance='ghost'
|
||||
/>
|
||||
)
|
||||
|
||||
const addRelayItem: (url: string) => void = async (url) => {
|
||||
if (relayPool && database && publicKey && (relays?.length ?? 0) < 2) {
|
||||
setLoading(true)
|
||||
relayPool.add(url)
|
||||
setRelayPool(relayPool)
|
||||
await addRelay({ url }, database)
|
||||
populateRelay(relayPool, database, publicKey)
|
||||
showMessage({
|
||||
message: t('alerts.relayAdded'),
|
||||
description: url,
|
||||
type: 'success',
|
||||
relayPool.add(url, () => {
|
||||
showMessage({
|
||||
message: t('alerts.relayAdded'),
|
||||
description: url,
|
||||
type: 'success',
|
||||
})
|
||||
loadRelays()
|
||||
})
|
||||
loadRelays()
|
||||
}
|
||||
}
|
||||
|
||||
const removeRelayItem: (url: string) => void = async (url) => {
|
||||
if (relayPool && database && publicKey) {
|
||||
setLoading(true)
|
||||
relayPool.remove(url)
|
||||
setRelayPool(relayPool)
|
||||
await removeRelay({ url }, database)
|
||||
showMessage({
|
||||
message: t('alerts.relayRemoved'),
|
||||
description: url,
|
||||
type: 'danger',
|
||||
relayPool.remove(url, () => {
|
||||
showMessage({
|
||||
message: t('alerts.relayRemoved'),
|
||||
description: url,
|
||||
type: 'danger',
|
||||
})
|
||||
loadRelays()
|
||||
})
|
||||
loadRelays()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,4 @@
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Layout,
|
||||
Spinner,
|
||||
TopNavigation,
|
||||
TopNavigationAction,
|
||||
useTheme,
|
||||
} from '@ui-kitten/components'
|
||||
import { Button, Input, Layout, Spinner, TopNavigation, useTheme } from '@ui-kitten/components'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { StyleSheet } from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
@ -87,9 +79,10 @@ export const SendPage: React.FC = () => {
|
||||
}
|
||||
|
||||
const renderBackAction = (): JSX.Element => (
|
||||
<TopNavigationAction
|
||||
icon={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||
<Button
|
||||
accessoryRight={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||
onPress={onPressBack}
|
||||
appearance='ghost'
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -1,14 +1,8 @@
|
||||
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 { 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 { getPublickey } from '../lib/nostr/Bip'
|
||||
import { defaultRelays } from '../Constants/RelayConstants'
|
||||
import WebsocketModule from '../lib/nostr/Native/WebsocketModule'
|
||||
import moment from 'moment'
|
||||
|
||||
export interface RelayPoolContextProps {
|
||||
@ -50,31 +44,9 @@ export const RelayPoolContextProvider = ({
|
||||
const [lastPage, setLastPage] = useState<string>(page)
|
||||
|
||||
const loadRelayPool: () => void = async () => {
|
||||
if (database) {
|
||||
const relays = await getRelays(database)
|
||||
if (database && publicKey) {
|
||||
const initRelayPool = new RelayPool([], privateKey)
|
||||
if (relays && relays.length > 0) {
|
||||
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',
|
||||
})
|
||||
},
|
||||
)
|
||||
initRelayPool.connect(publicKey)
|
||||
setRelayPool(initRelayPool)
|
||||
setLoadingRelayPool(false)
|
||||
}
|
||||
@ -86,22 +58,17 @@ export const RelayPoolContextProvider = ({
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
WebsocketModule.connectWebsocket((message) => {
|
||||
console.log('WEBSOCKET', message)
|
||||
setClock()
|
||||
})
|
||||
setClock()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (relayPool && lastPage !== page) {
|
||||
relayPool.removeOn('event', lastPage)
|
||||
setLastPage(page)
|
||||
}
|
||||
}, [page])
|
||||
|
||||
useEffect(() => {
|
||||
if (publicKey && publicKey !== '') {
|
||||
WebsocketModule.setUserPubKey(publicKey)
|
||||
if (!loadingRelayPool && page !== 'landing') {
|
||||
goToPage('home', true)
|
||||
} else {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { QueryResult, QuickSQLiteConnection } from 'react-native-quick-sqlite'
|
||||
import { QuickSQLiteConnection } from 'react-native-quick-sqlite'
|
||||
import { getItems } from '..'
|
||||
|
||||
export interface Relay {
|
||||
@ -10,38 +10,6 @@ const databaseToEntity: (object: any) => Relay = (object) => {
|
||||
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: (
|
||||
relayUrl: string,
|
||||
db: QuickSQLiteConnection,
|
||||
|
@ -22,7 +22,7 @@ export const isDirectReply: (mainEvent: Event, reply: Event) => boolean = (mainE
|
||||
const taggedReplyEventsIds: string[] = getTaggedEventIds(reply)
|
||||
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) => {
|
||||
@ -31,5 +31,5 @@ export const getTaggedEventIds: (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 { 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 {
|
||||
open: { [id: string]: (relay: Relay) => void }
|
||||
event: { [id: string]: (relay: Relay, subId: string, event: Event) => void }
|
||||
esoe: { [id: string]: (relay: Relay, subId: string) => void }
|
||||
notice: { [id: string]: (relay: Relay, events: Event[]) => void }
|
||||
export interface RelayFilters {
|
||||
ids?: string[]
|
||||
authors?: string[]
|
||||
kinds?: number[]
|
||||
'#e'?: string[]
|
||||
'#p'?: string[]
|
||||
since?: number
|
||||
limit?: number
|
||||
until?: number
|
||||
}
|
||||
|
||||
export interface RelayMessage {
|
||||
data: string
|
||||
}
|
||||
|
||||
class RelayPool {
|
||||
constructor(relaysUrls: string[], privateKey?: string, options: RelayOptions = {}) {
|
||||
this.relays = {}
|
||||
constructor(relaysUrls: string[], privateKey?: string) {
|
||||
this.privateKey = privateKey
|
||||
this.options = options
|
||||
|
||||
this.onFunctions = {
|
||||
open: {},
|
||||
event: {},
|
||||
esoe: {},
|
||||
notice: {},
|
||||
}
|
||||
this.subscriptions = {}
|
||||
|
||||
relaysUrls.forEach((relayUrl) => {
|
||||
this.add(relayUrl)
|
||||
})
|
||||
|
||||
this.setupHandlers()
|
||||
}
|
||||
|
||||
private readonly privateKey?: string
|
||||
private readonly options: RelayOptions
|
||||
private readonly onFunctions: OnFunctions
|
||||
public relays: { [url: string]: Relay }
|
||||
private subscriptions: { [subId: string]: string[] }
|
||||
|
||||
private readonly setupHandlers: () => void = () => {
|
||||
Object.keys(this.relays).forEach((relayUrl: string) => {
|
||||
const relay: Relay = this.relays[relayUrl]
|
||||
|
||||
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),
|
||||
)
|
||||
}
|
||||
})
|
||||
private readonly send: (message: object) => void = async (message) => {
|
||||
const tosend = JSON.stringify(message)
|
||||
RelayPoolModule.send(tosend)
|
||||
}
|
||||
|
||||
public on: (
|
||||
method: 'open' | 'event' | 'esoe' | 'notice',
|
||||
id: string,
|
||||
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,
|
||||
public readonly connect: (publicKey: string, callback?: () => void) => void = async (
|
||||
publicKey,
|
||||
callback = () => {},
|
||||
) => {
|
||||
delete this.onFunctions[method][id]
|
||||
RelayPoolModule.connect(publicKey, callback)
|
||||
}
|
||||
|
||||
public readonly add: (relayUrl: string) => Promise<boolean> = async (relayUrl) => {
|
||||
if (this.relays[relayUrl]) return false
|
||||
|
||||
this.relays[relayUrl] = new Relay(relayUrl, this.options)
|
||||
this.setupHandlers()
|
||||
|
||||
return true
|
||||
public readonly add: (relayUrl: string, callback?: () => void) => void = async (
|
||||
relayUrl,
|
||||
callback = () => {},
|
||||
) => {
|
||||
RelayPoolModule.add(relayUrl, callback)
|
||||
}
|
||||
|
||||
public readonly close: () => void = async () => {
|
||||
Object.keys(this.relays).forEach((relayUrl: string) => {
|
||||
const relay: Relay = this.relays[relayUrl]
|
||||
relay.close()
|
||||
})
|
||||
}
|
||||
|
||||
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 remove: (relayUrl: string, callback?: () => void) => void = async (
|
||||
relayUrl,
|
||||
callback = () => {},
|
||||
) => {
|
||||
RelayPoolModule.remove(relayUrl, callback)
|
||||
}
|
||||
|
||||
public readonly sendEvent: (event: Event) => Promise<Event | null> = async (event) => {
|
||||
@ -107,10 +62,7 @@ class RelayPool {
|
||||
const signedEvent: Event = await signEvent(event, this.privateKey)
|
||||
|
||||
if (validateEvent(signedEvent)) {
|
||||
Object.keys(this.relays).forEach((relayUrl: string) => {
|
||||
const relay: Relay = this.relays[relayUrl]
|
||||
relay.sendEvent(signedEvent)
|
||||
})
|
||||
this.send(['EVENT', event])
|
||||
|
||||
return signedEvent
|
||||
} else {
|
||||
@ -126,20 +78,27 @@ class RelayPool {
|
||||
subId,
|
||||
filters,
|
||||
) => {
|
||||
Object.keys(this.relays).forEach((relayUrl: string) => {
|
||||
this.relays[relayUrl].subscribe(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!', 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) => {
|
||||
Object.keys(this.relays).forEach((relayUrl: string) => {
|
||||
this.relays[relayUrl].unsubscribe(subId)
|
||||
})
|
||||
this.send(['CLOSE', subId])
|
||||
delete this.subscriptions[subId]
|
||||
}
|
||||
|
||||
public readonly unsubscribeAll: () => void = async () => {
|
||||
Object.keys(this.relays).forEach((relayUrl: string) => {
|
||||
this.relays[relayUrl].unsubscribeAll()
|
||||
Object.keys(this.subscriptions).forEach((subId: string) => {
|
||||
this.unsubscribe(subId)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user