mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-18 12:11:35 +00:00
Notifications fully working
This commit is contained in:
parent
7388b4f6de
commit
26a2b7101c
@ -1,14 +1,15 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { MemoryRouter, BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||
import { Box, Slide, Typography, styled } from '@mui/material';
|
||||
import { MemoryRouter, BrowserRouter } from 'react-router-dom';
|
||||
import { Box, Typography, styled } from '@mui/material';
|
||||
import { type UseAppStoreType, AppContext, closeAll } from '../contexts/AppContext';
|
||||
|
||||
import { RobotPage, MakerPage, BookPage, OrderPage, SettingsPage, NavBar, MainDialogs } from './';
|
||||
import { NavBar, MainDialogs } from './';
|
||||
import RobotAvatar from '../components/RobotAvatar';
|
||||
import Notifications from '../components/Notifications';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { GarageContext, type UseGarageStoreType } from '../contexts/GarageContext';
|
||||
import Routes from './Routes';
|
||||
|
||||
const Router = window.NativeRobosats === undefined ? BrowserRouter : MemoryRouter;
|
||||
|
||||
@ -53,87 +54,7 @@ const Main: React.FC = () => {
|
||||
)}
|
||||
|
||||
<MainBox navbarHeight={navbarHeight}>
|
||||
<Routes>
|
||||
{['/robot/:token?', '/', ''].map((path, index) => {
|
||||
return (
|
||||
<Route
|
||||
path={path}
|
||||
element={
|
||||
<Slide
|
||||
direction={page === 'robot' ? slideDirection.in : slideDirection.out}
|
||||
in={page === 'robot'}
|
||||
appear={slideDirection.in !== undefined}
|
||||
>
|
||||
<div>
|
||||
<RobotPage />
|
||||
</div>
|
||||
</Slide>
|
||||
}
|
||||
key={index}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<Route
|
||||
path={'/offers'}
|
||||
element={
|
||||
<Slide
|
||||
direction={page === 'offers' ? slideDirection.in : slideDirection.out}
|
||||
in={page === 'offers'}
|
||||
appear={slideDirection.in !== undefined}
|
||||
>
|
||||
<div>
|
||||
<BookPage />
|
||||
</div>
|
||||
</Slide>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path='/create'
|
||||
element={
|
||||
<Slide
|
||||
direction={page === 'create' ? slideDirection.in : slideDirection.out}
|
||||
in={page === 'create'}
|
||||
appear={slideDirection.in !== undefined}
|
||||
>
|
||||
<div>
|
||||
<MakerPage />
|
||||
</div>
|
||||
</Slide>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path='/order/:shortAlias/:orderId'
|
||||
element={
|
||||
<Slide
|
||||
direction={page === 'order' ? slideDirection.in : slideDirection.out}
|
||||
in={page === 'order'}
|
||||
appear={slideDirection.in !== undefined}
|
||||
>
|
||||
<div>
|
||||
<OrderPage />
|
||||
</div>
|
||||
</Slide>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path='/settings'
|
||||
element={
|
||||
<Slide
|
||||
direction={page === 'settings' ? slideDirection.in : slideDirection.out}
|
||||
in={page === 'settings'}
|
||||
appear={slideDirection.in !== undefined}
|
||||
>
|
||||
<div>
|
||||
<SettingsPage />
|
||||
</div>
|
||||
</Slide>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
<Routes />
|
||||
</MainBox>
|
||||
<NavBar />
|
||||
<MainDialogs />
|
||||
|
113
frontend/src/basic/Routes.tsx
Normal file
113
frontend/src/basic/Routes.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { Routes as DomRoutes, Route, useNavigate } from 'react-router-dom';
|
||||
import { Box, Slide, Typography, styled } from '@mui/material';
|
||||
import { type UseAppStoreType, AppContext } from '../contexts/AppContext';
|
||||
|
||||
import { RobotPage, MakerPage, BookPage, OrderPage, SettingsPage } from '.';
|
||||
import { GarageContext, UseGarageStoreType } from '../contexts/GarageContext';
|
||||
|
||||
const Routes: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||
const { page, slideDirection } = useContext<UseAppStoreType>(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('navigateToPage', (event) => {
|
||||
const orderId = event?.detail?.order_id;
|
||||
const coordinator = event?.detail?.coordinator;
|
||||
if (orderId && coordinator) {
|
||||
const slot = garage.getSlotByOrder(coordinator, orderId);
|
||||
if (slot?.token) {
|
||||
garage.setCurrentSlot(slot?.token);
|
||||
navigate(`/order/${coordinator}/${orderId}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DomRoutes>
|
||||
{['/robot/:token?', '/', ''].map((path, index) => {
|
||||
return (
|
||||
<Route
|
||||
path={path}
|
||||
element={
|
||||
<Slide
|
||||
direction={page === 'robot' ? slideDirection.in : slideDirection.out}
|
||||
in={page === 'robot'}
|
||||
appear={slideDirection.in !== undefined}
|
||||
>
|
||||
<div>
|
||||
<RobotPage />
|
||||
</div>
|
||||
</Slide>
|
||||
}
|
||||
key={index}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<Route
|
||||
path={'/offers'}
|
||||
element={
|
||||
<Slide
|
||||
direction={page === 'offers' ? slideDirection.in : slideDirection.out}
|
||||
in={page === 'offers'}
|
||||
appear={slideDirection.in !== undefined}
|
||||
>
|
||||
<div>
|
||||
<BookPage />
|
||||
</div>
|
||||
</Slide>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path='/create'
|
||||
element={
|
||||
<Slide
|
||||
direction={page === 'create' ? slideDirection.in : slideDirection.out}
|
||||
in={page === 'create'}
|
||||
appear={slideDirection.in !== undefined}
|
||||
>
|
||||
<div>
|
||||
<MakerPage />
|
||||
</div>
|
||||
</Slide>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path='/order/:shortAlias/:orderId'
|
||||
element={
|
||||
<Slide
|
||||
direction={page === 'order' ? slideDirection.in : slideDirection.out}
|
||||
in={page === 'order'}
|
||||
appear={slideDirection.in !== undefined}
|
||||
>
|
||||
<div>
|
||||
<OrderPage />
|
||||
</div>
|
||||
</Slide>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path='/settings'
|
||||
element={
|
||||
<Slide
|
||||
direction={page === 'settings' ? slideDirection.in : slideDirection.out}
|
||||
in={page === 'settings'}
|
||||
appear={slideDirection.in !== undefined}
|
||||
>
|
||||
<div>
|
||||
<SettingsPage />
|
||||
</div>
|
||||
</Slide>
|
||||
}
|
||||
/>
|
||||
</DomRoutes>
|
||||
);
|
||||
};
|
||||
|
||||
export default Routes;
|
@ -8,6 +8,7 @@ import {
|
||||
defaultExchange,
|
||||
} from '.';
|
||||
import defaultFederation from '../../static/federation.json';
|
||||
import { systemClient } from '../services/System';
|
||||
import { getHost } from '../utils';
|
||||
import { coordinatorDefaultValues } from './Coordinator.model';
|
||||
import { updateExchangeInfo } from './Exchange.model';
|
||||
@ -90,9 +91,12 @@ export class Federation {
|
||||
};
|
||||
|
||||
updateUrl = async (origin: Origin, settings: Settings, hostUrl: string): Promise<void> => {
|
||||
const federationUrls = {};
|
||||
for (const coor of Object.values(this.coordinators)) {
|
||||
coor.updateUrl(origin, settings, hostUrl);
|
||||
federationUrls[coor.shortAlias] = coor.url;
|
||||
}
|
||||
systemClient.setCookie('federation', JSON.stringify(federationUrls));
|
||||
};
|
||||
|
||||
update = async (): Promise<void> => {
|
||||
|
@ -109,6 +109,18 @@ class Garage {
|
||||
this.triggerHook('onRobotUpdate');
|
||||
};
|
||||
|
||||
getSlotByOrder: (coordinator: string, orderID: number) => Slot | null = (
|
||||
coordinator,
|
||||
orderID,
|
||||
) => {
|
||||
return (
|
||||
Object.values(this.slots).find((slot) => {
|
||||
const robot = slot.getRobot(coordinator);
|
||||
return slot.activeShortAlias === coordinator && robot?.activeOrderId === orderID;
|
||||
}) ?? null
|
||||
);
|
||||
};
|
||||
|
||||
// Robots
|
||||
createRobot: (token: string, shortAliases: string[], attributes: Record<any, any>) => void = (
|
||||
token,
|
||||
|
8
frontend/src/services/Native/index.d.ts
vendored
8
frontend/src/services/Native/index.d.ts
vendored
@ -25,7 +25,13 @@ export interface NativeWebViewMessageHttp {
|
||||
export interface NativeWebViewMessageSystem {
|
||||
id?: number;
|
||||
category: 'system';
|
||||
type: 'init' | 'torStatus' | 'copyToClipboardString' | 'setCookie' | 'deleteCookie';
|
||||
type:
|
||||
| 'init'
|
||||
| 'torStatus'
|
||||
| 'copyToClipboardString'
|
||||
| 'setCookie'
|
||||
| 'deleteCookie'
|
||||
| 'navigateToPage';
|
||||
key?: string;
|
||||
detail?: string;
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { redirect } from 'react-router-dom';
|
||||
import {
|
||||
type NativeRobosatsPromise,
|
||||
type NativeWebViewMessage,
|
||||
@ -45,6 +46,8 @@ class NativeRobosats {
|
||||
if (message.key !== undefined) {
|
||||
this.cookies[message.key] = String(message.detail);
|
||||
}
|
||||
} else if (message.type === 'navigateToPage') {
|
||||
window.dispatchEvent(new CustomEvent('navigateToPage', { detail: message?.detail }));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -8,6 +8,7 @@ import { name as app_name, version as app_version } from './package.json';
|
||||
import TorModule from './native/TorModule';
|
||||
import RoboIdentitiesModule from './native/RoboIdentitiesModule';
|
||||
import NotificationsModule from './native/NotificationsModule';
|
||||
import SystemModule from './native/SystemModule';
|
||||
|
||||
const backgroundColors = {
|
||||
light: 'white',
|
||||
@ -24,6 +25,13 @@ const App = () => {
|
||||
|
||||
useEffect(() => {
|
||||
TorModule.start();
|
||||
DeviceEventEmitter.addListener('navigateToPage', (payload) => {
|
||||
injectMessage({
|
||||
category: 'system',
|
||||
type: 'navigateToPage',
|
||||
detail: payload,
|
||||
});
|
||||
});
|
||||
DeviceEventEmitter.addListener('TorStatus', (payload) => {
|
||||
if (payload.torStatus === 'OFF') TorModule.restart();
|
||||
injectMessage({
|
||||
@ -73,7 +81,9 @@ const App = () => {
|
||||
loadCookie('settings_mode');
|
||||
loadCookie('settings_light_qr');
|
||||
loadCookie('settings_network');
|
||||
loadCookie('settings_use_proxy');
|
||||
loadCookie('settings_use_proxy').then((useProxy) => {
|
||||
SystemModule.useProxy(useProxy ?? 'true');
|
||||
});
|
||||
loadCookie('garage_slots').then((slots) => {
|
||||
NotificationsModule.monitorOrders(slots ?? '{}');
|
||||
injectMessageResolve(responseId);
|
||||
@ -133,6 +143,13 @@ const App = () => {
|
||||
Clipboard.setString(data.detail);
|
||||
} else if (data.type === 'setCookie') {
|
||||
setCookie(data.key, data.detail);
|
||||
if (data.key === 'federation') {
|
||||
SystemModule.setFederation(data.detail ?? '{}');
|
||||
} else if (data.key === 'garage_slots') {
|
||||
NotificationsModule.monitorOrders(data.detail ?? '{}');
|
||||
} else if (data.key === 'settings_use_proxy') {
|
||||
SystemModule.useProxy(data.detail ?? 'true');
|
||||
}
|
||||
} else if (data.type === 'deleteCookie') {
|
||||
EncryptedStorage.removeItem(data.key);
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
package com.robosats;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
@ -15,6 +17,10 @@ import com.facebook.react.ReactActivity;
|
||||
import com.facebook.react.ReactActivityDelegate;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
import com.robosats.workers.NotificationWorker;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -25,11 +31,34 @@ public class MainActivity extends ReactActivity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle extras = getIntent().getExtras();
|
||||
if (extras != null) {
|
||||
String coordinator = extras.getString("coordinator");
|
||||
String token = extras.getString("token");
|
||||
int order_id = extras.getInt("order_id", 0);
|
||||
if (order_id > 0) {
|
||||
navigateToPage(coordinator, order_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.POST_NOTIFICATIONS}, REQUEST_CODE_POST_NOTIFICATIONS);
|
||||
} else {
|
||||
// Permission already granted, schedule your work
|
||||
schedulePeriodicTask();
|
||||
scheduleFirstNotificationTask();
|
||||
schedulePeriodicNotificationTask();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
if (intent != null) {
|
||||
String coordinator = intent.getStringExtra("coordinator");
|
||||
int order_id = intent.getIntExtra("order_id", 0);
|
||||
if (order_id > 0) {
|
||||
navigateToPage(coordinator, order_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +86,8 @@ public class MainActivity extends ReactActivity {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (requestCode == REQUEST_CODE_POST_NOTIFICATIONS) {
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
schedulePeriodicTask();
|
||||
scheduleFirstNotificationTask();
|
||||
schedulePeriodicNotificationTask();
|
||||
} else {
|
||||
// Permission denied, handle accordingly
|
||||
// Maybe show a message to the user explaining why the permission is necessary
|
||||
@ -65,15 +95,39 @@ public class MainActivity extends ReactActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void schedulePeriodicTask() {
|
||||
// // Trigger the WorkManager setup and enqueueing here
|
||||
// PeriodicWorkRequest periodicWorkRequest =
|
||||
// new PeriodicWorkRequest.Builder(NotificationWorker.class, 15, TimeUnit.MINUTES)
|
||||
// .build();
|
||||
//
|
||||
// WorkManager.getInstance(getApplicationContext())
|
||||
// .enqueueUniquePeriodicWork("RobosatsNotificationsWork",
|
||||
// ExistingPeriodicWorkPolicy.KEEP, periodicWorkRequest);
|
||||
private void navigateToPage(String coordinator, Integer order_id) {
|
||||
ReactContext reactContext = getReactInstanceManager().getCurrentReactContext();
|
||||
if (reactContext != null) {
|
||||
WritableMap payload = Arguments.createMap();
|
||||
payload.putString("coordinator", coordinator);
|
||||
payload.putInt("order_id", order_id);
|
||||
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||
.emit("navigateToPage", payload);
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleFirstNotificationTask() {
|
||||
OneTimeWorkRequest workRequest =
|
||||
new OneTimeWorkRequest.Builder(NotificationWorker.class)
|
||||
.setInitialDelay(20, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
WorkManager.getInstance(getApplicationContext())
|
||||
.enqueue(workRequest);
|
||||
}
|
||||
|
||||
private void schedulePeriodicNotificationTask() {
|
||||
// Trigger the WorkManager setup and enqueueing here
|
||||
PeriodicWorkRequest periodicWorkRequest =
|
||||
new PeriodicWorkRequest.Builder(NotificationWorker.class, 15, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
WorkManager.getInstance(getApplicationContext())
|
||||
.enqueueUniquePeriodicWork("RobosatsNotificationsWork",
|
||||
ExistingPeriodicWorkPolicy.KEEP, periodicWorkRequest);
|
||||
}
|
||||
|
||||
private void scheduleInitialTask() {
|
||||
OneTimeWorkRequest workRequest =
|
||||
new OneTimeWorkRequest.Builder(NotificationWorker.class)
|
||||
.setInitialDelay(25, TimeUnit.SECONDS)
|
||||
|
@ -6,6 +6,7 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.robosats.modules.NotificationsModule;
|
||||
import com.robosats.modules.RoboIdentitiesModule;
|
||||
import com.robosats.modules.SystemModule;
|
||||
import com.robosats.modules.TorModule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -23,6 +24,7 @@ public class RobosatsPackage implements ReactPackage {
|
||||
ReactApplicationContext reactContext) {
|
||||
List<NativeModule> modules = new ArrayList<>();
|
||||
|
||||
modules.add(new SystemModule(reactContext));
|
||||
modules.add(new TorModule(reactContext));
|
||||
modules.add(new NotificationsModule(reactContext));
|
||||
modules.add(new RoboIdentitiesModule(reactContext));
|
||||
|
@ -0,0 +1,41 @@
|
||||
package com.robosats.modules;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
|
||||
public class SystemModule extends ReactContextBaseJavaModule {
|
||||
public SystemModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "SystemModule";
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void useProxy(String use_proxy) {
|
||||
String PREFS_NAME = "System";
|
||||
String KEY_DATA = "UsePoxy";
|
||||
SharedPreferences sharedPreferences = getReactApplicationContext().getSharedPreferences(PREFS_NAME, ReactApplicationContext.MODE_PRIVATE);
|
||||
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(KEY_DATA, use_proxy);
|
||||
|
||||
editor.apply();
|
||||
}
|
||||
@ReactMethod
|
||||
public void setFederation(String use_proxy) {
|
||||
String PREFS_NAME = "System";
|
||||
String KEY_DATA = "Federation";
|
||||
SharedPreferences sharedPreferences = getReactApplicationContext().getSharedPreferences(PREFS_NAME, ReactApplicationContext.MODE_PRIVATE);
|
||||
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(KEY_DATA, use_proxy);
|
||||
|
||||
editor.apply();
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import kotlin.UninitializedPropertyAccessException;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.MediaType;
|
||||
@ -45,7 +46,7 @@ public class TorModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void sendRequest(String action, String url, String headers, String body, final Promise promise) throws JSONException {
|
||||
public void sendRequest(String action, String url, String headers, String body, final Promise promise) throws JSONException, UninitializedPropertyAccessException {
|
||||
OkHttpClient client = new OkHttpClient.Builder()
|
||||
.connectTimeout(60, TimeUnit.SECONDS) // Set connection timeout
|
||||
.readTimeout(30, TimeUnit.SECONDS) // Set read timeout
|
||||
@ -93,7 +94,7 @@ public class TorModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getTorStatus() {
|
||||
public void getTorStatus() throws UninitializedPropertyAccessException {
|
||||
String torState = TorKmpManager.INSTANCE.getTorKmpObject().getTorState().getState().name();
|
||||
WritableMap payload = Arguments.createMap();
|
||||
payload.putString("torStatus", torState);
|
||||
@ -103,7 +104,7 @@ public class TorModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void isConnected() {
|
||||
public void isConnected() throws UninitializedPropertyAccessException {
|
||||
String isConnected = String.valueOf(TorKmpManager.INSTANCE.getTorKmpObject().isConnected());
|
||||
WritableMap payload = Arguments.createMap();
|
||||
payload.putString("isConnected", isConnected);
|
||||
@ -113,7 +114,7 @@ public class TorModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void isStarting() {
|
||||
public void isStarting() throws UninitializedPropertyAccessException {
|
||||
String isStarting = String.valueOf(TorKmpManager.INSTANCE.getTorKmpObject().isStarting());
|
||||
WritableMap payload = Arguments.createMap();
|
||||
payload.putString("isStarting", isStarting);
|
||||
@ -123,7 +124,7 @@ public class TorModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void stop() {
|
||||
public void stop() throws UninitializedPropertyAccessException {
|
||||
TorKmpManager.INSTANCE.getTorKmpObject().getTorOperationManager().stopQuietly();
|
||||
WritableMap payload = Arguments.createMap();
|
||||
context
|
||||
@ -132,9 +133,7 @@ public class TorModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void start() {
|
||||
TorKmp torKmp = new TorKmp(context.getCurrentActivity().getApplication());
|
||||
TorKmpManager.INSTANCE.updateTorKmpObject(torKmp);
|
||||
public void start() throws InterruptedException, UninitializedPropertyAccessException {
|
||||
TorKmpManager.INSTANCE.getTorKmpObject().getTorOperationManager().startQuietly();
|
||||
WritableMap payload = Arguments.createMap();
|
||||
context
|
||||
@ -143,7 +142,7 @@ public class TorModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void restart() {
|
||||
public void restart() throws UninitializedPropertyAccessException {
|
||||
TorKmp torKmp = new TorKmp(context.getCurrentActivity().getApplication());
|
||||
TorKmpManager.INSTANCE.updateTorKmpObject(torKmp);
|
||||
TorKmpManager.INSTANCE.getTorKmpObject().getTorOperationManager().restartQuietly();
|
||||
@ -154,7 +153,7 @@ public class TorModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void newIdentity() {
|
||||
public void newIdentity() throws UninitializedPropertyAccessException {
|
||||
TorKmpManager.INSTANCE.getTorKmpObject().newIdentity(context.getCurrentActivity().getApplication());
|
||||
WritableMap payload = Arguments.createMap();
|
||||
context
|
||||
|
@ -27,6 +27,7 @@ import io.matthewnelson.kmp.tor.manager.R
|
||||
import kotlinx.coroutines.*
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
||||
class TorKmp(application : Application) {
|
||||
|
||||
@ -391,6 +392,7 @@ class TorKmp(application : Application) {
|
||||
object TorKmpManager {
|
||||
private lateinit var torKmp: TorKmp
|
||||
|
||||
@Throws(UninitializedPropertyAccessException::class)
|
||||
fun getTorKmpObject(): TorKmp {
|
||||
return torKmp
|
||||
}
|
||||
|
@ -3,8 +3,10 @@ package com.robosats.workers;
|
||||
import android.app.Application;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
@ -14,7 +16,9 @@ import androidx.work.Worker;
|
||||
import androidx.work.WorkerParameters;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.robosats.MainActivity;
|
||||
import com.robosats.R;
|
||||
import com.robosats.tor.TorKmp;
|
||||
import com.robosats.tor.TorKmpManager;
|
||||
|
||||
import org.json.JSONArray;
|
||||
@ -22,9 +26,13 @@ import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import kotlin.UninitializedPropertyAccessException;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.OkHttpClient;
|
||||
@ -33,8 +41,11 @@ import okhttp3.Response;
|
||||
|
||||
public class NotificationWorker extends Worker {
|
||||
private static final String CHANNEL_ID = "robosats_notifications";
|
||||
private static final String PREFS_NAME = "Notifications";
|
||||
private static final String KEY_DATA = "Slots";
|
||||
private static final String PREFS_NAME_NOTIFICATION = "Notifications";
|
||||
private static final String PREFS_NAME_SYSTEM = "System";
|
||||
private static final String KEY_DATA_SLOTS = "Slots";
|
||||
private static final String KEY_DATA_PROXY = "UsePoxy";
|
||||
private static final String KEY_DATA_FEDERATION = "Federation";
|
||||
|
||||
public NotificationWorker(Context context, WorkerParameters params) {
|
||||
super(context, params);
|
||||
@ -43,8 +54,10 @@ public class NotificationWorker extends Worker {
|
||||
@Override
|
||||
public Result doWork() {
|
||||
|
||||
SharedPreferences sharedPreferences = getApplicationContext().getSharedPreferences(PREFS_NAME, ReactApplicationContext.MODE_PRIVATE);
|
||||
String slotsJson = sharedPreferences.getString(KEY_DATA, null);
|
||||
SharedPreferences sharedPreferences =
|
||||
getApplicationContext()
|
||||
.getSharedPreferences(PREFS_NAME_NOTIFICATION, ReactApplicationContext.MODE_PRIVATE);
|
||||
String slotsJson = sharedPreferences.getString(KEY_DATA_SLOTS, null);
|
||||
|
||||
try {
|
||||
assert slotsJson != null;
|
||||
@ -54,34 +67,59 @@ public class NotificationWorker extends Worker {
|
||||
while (it.hasNext()) {
|
||||
String robotToken = it.next();
|
||||
JSONObject slot = (JSONObject) slots.get(robotToken);
|
||||
|
||||
JSONObject robots = slot.getJSONObject("robots");
|
||||
String activeShortAlias = slot.getString("activeShortAlias");
|
||||
JSONObject coordinatorRobot = robots.getJSONObject(activeShortAlias);
|
||||
String coordinator = "satstralia";
|
||||
fetchNotifications(coordinatorRobot, coordinator);
|
||||
JSONObject coordinatorRobot;
|
||||
String activeShortAlias;
|
||||
try {
|
||||
activeShortAlias = slot.getString("activeShortAlias");
|
||||
coordinatorRobot = robots.getJSONObject(activeShortAlias);
|
||||
fetchNotifications(coordinatorRobot, activeShortAlias);
|
||||
} catch (JSONException | InterruptedException e) {
|
||||
Log.d("JSON error", String.valueOf(e));
|
||||
}
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
Log.d("JSON error", String.valueOf(e));
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
private void fetchNotifications(JSONObject robot, String coordinator) throws JSONException {
|
||||
OkHttpClient client = new OkHttpClient.Builder()
|
||||
.connectTimeout(60, TimeUnit.SECONDS) // Set connection timeout
|
||||
.readTimeout(30, TimeUnit.SECONDS) // Set read timeout
|
||||
.proxy(TorKmpManager.INSTANCE.getTorKmpObject().getProxy())
|
||||
.build();
|
||||
Request.Builder requestBuilder = new Request.Builder().url("http://satstraoq35jffvkgpfoqld32nzw2siuvowanruindbfojowpwsjdgad.onion/api/notifications");
|
||||
private void fetchNotifications(JSONObject robot, String coordinator) throws JSONException, InterruptedException {
|
||||
String token = robot.getString("tokenSHA256");
|
||||
SharedPreferences sharedPreferences =
|
||||
getApplicationContext()
|
||||
.getSharedPreferences(PREFS_NAME_SYSTEM, ReactApplicationContext.MODE_PRIVATE);
|
||||
boolean useProxy = Objects.equals(sharedPreferences.getString(KEY_DATA_PROXY, null), "true");
|
||||
JSONObject federation = new JSONObject(sharedPreferences.getString(KEY_DATA_FEDERATION, "{}"));
|
||||
long unix_time_millis = sharedPreferences.getLong(token, 0);
|
||||
String url = federation.getString(coordinator) + "/api/notifications";
|
||||
// if (unix_time_millis > 0) {
|
||||
// String last_created_at = String
|
||||
// .valueOf(LocalDateTime.ofInstant(Instant.ofEpochMilli(unix_time_millis), ZoneId.of("UTC")));
|
||||
// url += "?created_at=" + last_created_at;
|
||||
// }
|
||||
|
||||
requestBuilder.addHeader("Authorization", "Token " + robot.getString("tokenSHA256"));
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
||||
.connectTimeout(60, TimeUnit.SECONDS) // Set connection timeout
|
||||
.readTimeout(30, TimeUnit.SECONDS); // Set read timeout
|
||||
|
||||
if (useProxy) {
|
||||
TorKmp tor = this.getTorKmp();
|
||||
builder.proxy(tor.getProxy());
|
||||
}
|
||||
|
||||
OkHttpClient client = builder.build();
|
||||
Request.Builder requestBuilder = new Request.Builder().url(url);
|
||||
|
||||
requestBuilder
|
||||
.addHeader("Authorization", "Token " + token);
|
||||
|
||||
requestBuilder.get();
|
||||
Request request = requestBuilder.build();
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
displayErrorNotification();
|
||||
Log.d("RobosatsError", e.toString());
|
||||
}
|
||||
|
||||
@ -98,15 +136,14 @@ public class NotificationWorker extends Worker {
|
||||
});
|
||||
try {
|
||||
JSONArray results = new JSONArray(body);
|
||||
for (int i = 0; i < results.length(); i++) {
|
||||
SharedPreferences sharedPreferences = getApplicationContext().getSharedPreferences(PREFS_NAME, ReactApplicationContext.MODE_PRIVATE);
|
||||
JSONObject notification = results.getJSONObject(i);
|
||||
if (results.length() > 0) {
|
||||
JSONObject notification = results.getJSONObject(0);
|
||||
Integer order_id = notification.getInt("order_id");
|
||||
|
||||
displayNotification(order_id, notification.getString("title"), coordinator);
|
||||
displayOrderNotification(order_id, notification.getString("title"), coordinator);
|
||||
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(coordinator + order_id, String.valueOf(notification.getInt("created_at")));
|
||||
editor.putLong(token, System.currentTimeMillis());
|
||||
editor.apply();
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
@ -117,23 +154,75 @@ public class NotificationWorker extends Worker {
|
||||
|
||||
}
|
||||
|
||||
private void displayNotification(Integer order_id, String message, String coordinator) {
|
||||
private void displayOrderNotification(Integer order_id, String message, String coordinator) {
|
||||
NotificationManager notificationManager = (NotificationManager)
|
||||
getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
|
||||
"Robosats",
|
||||
order_id.toString(),
|
||||
NotificationManager.IMPORTANCE_HIGH);
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
|
||||
Intent intent = new Intent(this.getApplicationContext(), MainActivity.class);
|
||||
intent.putExtra("coordinator", coordinator);
|
||||
intent.putExtra("order_id", order_id);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(this.getApplicationContext(), 0,
|
||||
intent, PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
NotificationCompat.Builder builder =
|
||||
new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
|
||||
.setContentTitle("Order #" + order_id)
|
||||
.setContentText(message)
|
||||
.setSmallIcon(R.mipmap.ic_launcher_round)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true);
|
||||
|
||||
notificationManager.notify(order_id, builder.build());
|
||||
}
|
||||
|
||||
private void displayErrorNotification() {
|
||||
NotificationManager notificationManager = (NotificationManager)
|
||||
getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
|
||||
"robosats_error",
|
||||
NotificationManager.IMPORTANCE_HIGH);
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
|
||||
NotificationCompat.Builder builder =
|
||||
new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
|
||||
.setContentTitle("Connection Error")
|
||||
.setContentText("There was an error while connecting to the Tor network.")
|
||||
.setSmallIcon(R.mipmap.ic_launcher_round)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setAutoCancel(true);
|
||||
|
||||
notificationManager.notify(0, builder.build());
|
||||
}
|
||||
|
||||
private TorKmp getTorKmp() throws InterruptedException {
|
||||
TorKmp torKmp;
|
||||
try {
|
||||
torKmp = TorKmpManager.INSTANCE.getTorKmpObject();
|
||||
} catch (UninitializedPropertyAccessException e) {
|
||||
torKmp = new TorKmp((Application) this.getApplicationContext());
|
||||
}
|
||||
|
||||
int retires = 0;
|
||||
while (!torKmp.isConnected() && retires < 15) {
|
||||
if (!torKmp.isStarting()) {
|
||||
torKmp.getTorOperationManager().startQuietly();
|
||||
}
|
||||
Thread.sleep(2000);
|
||||
retires += 1;
|
||||
}
|
||||
|
||||
if (!torKmp.isConnected()) {
|
||||
displayErrorNotification();
|
||||
}
|
||||
|
||||
return torKmp;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@ const { NotificationsModule } = NativeModules;
|
||||
|
||||
interface NotificationsModuleInterface {
|
||||
monitorOrders: (slotsJson: string) => void;
|
||||
useProxy: (useProxy: string) => void;
|
||||
setFederation: (federation: string) => void;
|
||||
}
|
||||
|
||||
export default NotificationsModule as NotificationsModuleInterface;
|
||||
|
9
mobile/native/SystemModule.ts
Normal file
9
mobile/native/SystemModule.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { NativeModules } from 'react-native';
|
||||
const { SystemModule } = NativeModules;
|
||||
|
||||
interface SystemModuleInterface {
|
||||
useProxy: (useProxy: string) => void;
|
||||
setFederation: (federation: string) => void;
|
||||
}
|
||||
|
||||
export default SystemModule as SystemModuleInterface;
|
Loading…
Reference in New Issue
Block a user