diff --git a/frontend/src/basic/Main.tsx b/frontend/src/basic/Main.tsx
index b87d564a..344300d2 100644
--- a/frontend/src/basic/Main.tsx
+++ b/frontend/src/basic/Main.tsx
@@ -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 = () => {
)}
-
- {['/robot/:token?', '/', ''].map((path, index) => {
- return (
-
-
-
-
-
- }
- key={index}
- />
- );
- })}
-
-
-
-
-
-
- }
- />
-
-
-
-
-
-
- }
- />
-
-
-
-
-
-
- }
- />
-
-
-
-
-
-
- }
- />
-
+
diff --git a/frontend/src/basic/Routes.tsx b/frontend/src/basic/Routes.tsx
new file mode 100644
index 00000000..f0ab7d20
--- /dev/null
+++ b/frontend/src/basic/Routes.tsx
@@ -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(GarageContext);
+ const { page, slideDirection } = useContext(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 (
+
+ {['/robot/:token?', '/', ''].map((path, index) => {
+ return (
+
+
+
+
+
+ }
+ key={index}
+ />
+ );
+ })}
+
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+
+ }
+ />
+
+ );
+};
+
+export default Routes;
diff --git a/frontend/src/models/Federation.model.ts b/frontend/src/models/Federation.model.ts
index 46dfd5ff..b850d1e5 100644
--- a/frontend/src/models/Federation.model.ts
+++ b/frontend/src/models/Federation.model.ts
@@ -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 => {
+ 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 => {
diff --git a/frontend/src/models/Garage.model.ts b/frontend/src/models/Garage.model.ts
index a1f347a1..48a28a30 100644
--- a/frontend/src/models/Garage.model.ts
+++ b/frontend/src/models/Garage.model.ts
@@ -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) => void = (
token,
diff --git a/frontend/src/services/Native/index.d.ts b/frontend/src/services/Native/index.d.ts
index 680c8427..1ba9b275 100644
--- a/frontend/src/services/Native/index.d.ts
+++ b/frontend/src/services/Native/index.d.ts
@@ -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;
}
diff --git a/frontend/src/services/Native/index.ts b/frontend/src/services/Native/index.ts
index ad2d49a3..77e9f5d8 100644
--- a/frontend/src/services/Native/index.ts
+++ b/frontend/src/services/Native/index.ts
@@ -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 }));
}
};
diff --git a/mobile/App.tsx b/mobile/App.tsx
index 218e6b9f..59dca69d 100644
--- a/mobile/App.tsx
+++ b/mobile/App.tsx
@@ -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);
}
diff --git a/mobile/android/app/src/main/java/com/robosats/MainActivity.java b/mobile/android/app/src/main/java/com/robosats/MainActivity.java
index afc644a8..82a4688c 100644
--- a/mobile/android/app/src/main/java/com/robosats/MainActivity.java
+++ b/mobile/android/app/src/main/java/com/robosats/MainActivity.java
@@ -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,14 +31,37 @@ 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);
+ }
+ }
+ }
+
/**
* Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component.
@@ -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)
diff --git a/mobile/android/app/src/main/java/com/robosats/RobosatsPackage.java b/mobile/android/app/src/main/java/com/robosats/RobosatsPackage.java
index e78e0d31..d3f5ca7e 100644
--- a/mobile/android/app/src/main/java/com/robosats/RobosatsPackage.java
+++ b/mobile/android/app/src/main/java/com/robosats/RobosatsPackage.java
@@ -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 modules = new ArrayList<>();
+ modules.add(new SystemModule(reactContext));
modules.add(new TorModule(reactContext));
modules.add(new NotificationsModule(reactContext));
modules.add(new RoboIdentitiesModule(reactContext));
diff --git a/mobile/android/app/src/main/java/com/robosats/modules/SystemModule.java b/mobile/android/app/src/main/java/com/robosats/modules/SystemModule.java
new file mode 100644
index 00000000..f6206368
--- /dev/null
+++ b/mobile/android/app/src/main/java/com/robosats/modules/SystemModule.java
@@ -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();
+ }
+}
diff --git a/mobile/android/app/src/main/java/com/robosats/modules/TorModule.java b/mobile/android/app/src/main/java/com/robosats/modules/TorModule.java
index c94aa656..8e145554 100644
--- a/mobile/android/app/src/main/java/com/robosats/modules/TorModule.java
+++ b/mobile/android/app/src/main/java/com/robosats/modules/TorModule.java
@@ -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
diff --git a/mobile/android/app/src/main/java/com/robosats/tor/TorKmpManager.kt b/mobile/android/app/src/main/java/com/robosats/tor/TorKmpManager.kt
index cc5115a6..9accff0c 100644
--- a/mobile/android/app/src/main/java/com/robosats/tor/TorKmpManager.kt
+++ b/mobile/android/app/src/main/java/com/robosats/tor/TorKmpManager.kt
@@ -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
}
diff --git a/mobile/android/app/src/main/java/com/robosats/workers/NotificationWorker.java b/mobile/android/app/src/main/java/com/robosats/workers/NotificationWorker.java
index a81d02f4..db0dc3db 100644
--- a/mobile/android/app/src/main/java/com/robosats/workers/NotificationWorker.java
+++ b/mobile/android/app/src/main/java/com/robosats/workers/NotificationWorker.java
@@ -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;
+ }
}
diff --git a/mobile/native/NotificationsModule.ts b/mobile/native/NotificationsModule.ts
index a06d38a2..05751a6c 100644
--- a/mobile/native/NotificationsModule.ts
+++ b/mobile/native/NotificationsModule.ts
@@ -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;
diff --git a/mobile/native/SystemModule.ts b/mobile/native/SystemModule.ts
new file mode 100644
index 00000000..aba2a392
--- /dev/null
+++ b/mobile/native/SystemModule.ts
@@ -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;