Skip to content

Commit

Permalink
chore: improve self hosted runner (Detox) (#2453)
Browse files Browse the repository at this point in the history
* chore: start own metro

* chore: comment out restore connection saga usage

* chore: repeat websocket connection tries on android

* chore: update pods

* chore: increase action timeout

* fix: always stop metro

* chore: limit intervals

* chore: enlarge intervals

* fix: use callback on react app init

* chore: remove redundand sagas

* fix: move app_ready event

* test: mock native modules
  • Loading branch information
siepra committed Apr 24, 2024
1 parent f587ccc commit 34cd60f
Show file tree
Hide file tree
Showing 18 changed files with 94 additions and 130 deletions.
16 changes: 14 additions & 2 deletions .github/workflows/e2e-android-self.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:

jobs:
detox-android-self-hosted:
timeout-minutes: 10
timeout-minutes: 25
runs-on: [self-hosted, macOS, ARM64, android]

steps:
Expand All @@ -32,6 +32,14 @@ jobs:
ndk.path=/Users/quiet/Library/Android/sdk/ndk/25.1.8937393
EOF
- name: Install pm2
run: npm install pm2@latest -g

- name: Start metro
run: |
cd packages/mobile
pm2 --name METRO start npm -- start
- name: Build Detox
run: |
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home
Expand All @@ -41,4 +49,8 @@ jobs:
- name: Run basic tests
run: |
cd packages/mobile
detox test starter -c android.emu.debug.ci
detox test starter -c android.emu.debug.ci
- name: Stop metro
if: always()
run: pm2 stop METRO
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ import androidx.core.app.NotificationCompat
import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
import com.google.gson.Gson
import com.quietmobile.BuildConfig
import com.quietmobile.Communication.CommunicationModule
import com.quietmobile.MainApplication
import com.quietmobile.Notification.NotificationHandler
import com.quietmobile.R
import com.quietmobile.Scheme.WebsocketConnectionPayload
import com.quietmobile.Utils.Const
import com.quietmobile.Utils.Const.WEBSOCKET_CONNECTION_DELAY
import com.quietmobile.Utils.Utils
import com.quietmobile.Utils.isAppOnForeground
import io.socket.client.IO
Expand Down Expand Up @@ -98,31 +96,20 @@ class BackendWorker(private val context: Context, workerParams: WorkerParameters
withContext(Dispatchers.IO) {

// Get and store data port for usage in methods across the app
val dataPort = Utils.getOpenPort(11000)
val socketPort = Utils.getOpenPort(11000)
val socketIOSecret = Utils.generateRandomString(20)

(applicationContext as MainApplication).setSocketPort(socketPort)
(applicationContext as MainApplication).setSocketIOSecret(socketIOSecret)

// Init nodejs project
launch {
nodeProject.init()
}

launch {
notificationHandler = NotificationHandler(context)
subscribePushNotifications(dataPort, socketIOSecret)
}

launch {
/*
* Wait for CommunicationModule to be initialized with reactContext
* (there's no callback we can use for that purpose).
*
* Code featured below suspends nothing but the websocket connection
* and it doesn't affect anything besides that.
*
* In any case, websocket won't connect until data server starts listening
*/
delay(WEBSOCKET_CONNECTION_DELAY)
startWebsocketConnection(dataPort, socketIOSecret)
subscribePushNotifications(socketPort, socketIOSecret)
}

val dataPath = Utils.createDirectory(context)
Expand All @@ -139,7 +126,7 @@ class BackendWorker(private val context: Context, workerParams: WorkerParameters
* https://github.com/TryQuiet/quiet/issues/2214
*/
delay(500)
startNodeProjectWithArguments("bundle.cjs --torBinary $torBinary --dataPath $dataPath --dataPort $dataPort --platform $platform --socketIOSecret $socketIOSecret")
startNodeProjectWithArguments("bundle.cjs --torBinary $torBinary --dataPath $dataPath --dataPort $socketPort --platform $platform --socketIOSecret $socketIOSecret")
}
}

Expand All @@ -155,8 +142,6 @@ class BackendWorker(private val context: Context, workerParams: WorkerParameters
return Result.success()
}

private external fun sendMessageToNodeChannel(channelName: String, message: String): Void

private external fun startNodeWithArguments(
arguments: Array<String?>?,
modulesPath: String?
Expand Down Expand Up @@ -214,17 +199,6 @@ class BackendWorker(private val context: Context, workerParams: WorkerParameters
notificationHandler.notify(message, username)
}

private fun startWebsocketConnection(port: Int, socketIOSecret: String) {
Log.d("WEBSOCKET CONNECTION", "Starting on $port")
// Proceed only if data port is defined
val websocketConnectionPayload = WebsocketConnectionPayload(port, socketIOSecret)
CommunicationModule.handleIncomingEvents(
CommunicationModule.WEBSOCKET_CONNECTION_CHANNEL,
Gson().toJson(websocketConnectionPayload),
"" // Empty extras
)
}

fun handleNodeMessages(channelName: String, msg: String?) {
print("handle node message - channel name $channelName")
print("handle node message - msg $msg")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.RCTNativeAppEventEmitter;
import com.google.gson.Gson;
import com.quietmobile.MainApplication;
import com.quietmobile.Notification.NotificationHandler;
import com.quietmobile.Scheme.WebsocketConnectionPayload;

import androidx.annotation.NonNull;

Expand All @@ -24,6 +27,8 @@

public class CommunicationModule extends ReactContextBaseJavaModule {

public static final String APP_READY_CHANNEL = "_APP_READY_";

public static final String PUSH_NOTIFICATION_CHANNEL = "_PUSH_NOTIFICATION_";
public static final String WEBSOCKET_CONNECTION_CHANNEL = "_WEBSOCKET_CONNECTION_";
public static final String INIT_CHECK_CHANNEL = "_INIT_CHECK_";
Expand All @@ -48,14 +53,16 @@ public CommunicationModule(ReactApplicationContext reactContext) {
}

@ReactMethod
public static void handleIncomingEvents(String event, String payload, String extra) {
public static void handleIncomingEvents(String event, @Nullable String payload, @Nullable String extra) {
switch (event) {
case APP_READY_CHANNEL:
startWebsocketConnection();
break;
case PUSH_NOTIFICATION_CHANNEL:
String message = payload;
String username = extra;
notificationHandler.notify(message, username);
break;
case WEBSOCKET_CONNECTION_CHANNEL:
case INIT_CHECK_CHANNEL:
case BACKEND_CLOSED_CHANNEL:
passDataToReact(event, payload);
Expand Down Expand Up @@ -88,6 +95,15 @@ private static void sendEvent(@Nullable WritableMap params) {
}
}

private static void startWebsocketConnection() {
Context context = reactContext.getApplicationContext();
int port = ((MainApplication) context).getSocketPort();
String socketIOSecret = ((MainApplication) context).getSocketIOSecret();

WebsocketConnectionPayload websocketConnectionPayload = new WebsocketConnectionPayload(port, socketIOSecret);
passDataToReact(WEBSOCKET_CONNECTION_CHANNEL, new Gson().toJson(websocketConnectionPayload));
}

@ReactMethod
private static void deleteBackendData() {
Context context = reactContext.getApplicationContext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,25 @@ class MainApplication : Application(), ReactApplication {
createNotificationChannel()
}

private var socketPort: Int = 0
private var socketIOSecret: String = ""

fun getSocketPort(): Int {
return socketPort
}

fun setSocketPort(value: Int) {
this.socketPort = value
}

fun getSocketIOSecret(): String {
return socketIOSecret
}

fun setSocketIOSecret(value: String) {
this.socketIOSecret = value
}

private fun createForegroundServiceNotificationChannel() {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,4 @@ object Const {
const val NODEJS_PROJECT_DIR = "nodejs-project"
const val NODEJS_BUILTIN_NATIVE_ASSETS_PREFIX = "nodejs-native-assets-"
const val NODEJS_TRASH_DIR = "nodejs-project-trash"

// Websocket
const val WEBSOCKET_CONNECTION_DELAY: Long = 7000
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ object Utils {
return dataDirectory.absolutePath
}

@JvmStatic
fun generateRandomString(length: Int): String {
val CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
val secureRandom = SecureRandom()
Expand All @@ -43,6 +44,7 @@ object Utils {
return randomString.toString()
}

@JvmStatic
suspend fun getOpenPort(starting: Int) = suspendCoroutine<Int> { continuation ->
val port = checkPort(starting)
continuation.resume(port)
Expand Down
6 changes: 4 additions & 2 deletions packages/mobile/ios/Quiet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -5482,7 +5482,8 @@
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
Expand Down Expand Up @@ -5547,7 +5548,8 @@
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
Expand Down
5 changes: 3 additions & 2 deletions packages/mobile/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React, { useEffect } from 'react'
import { useDispatch } from 'react-redux'

import { LogBox, StatusBar } from 'react-native'
import { LogBox, NativeModules, StatusBar } from 'react-native'

import { APP_READY_CHANNEL } from '@quiet/state-manager'

import WebviewCrypto from 'react-native-webview-crypto'

Expand All @@ -28,7 +30,6 @@ import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'

import { navigationRef } from './RootNavigation'
import { initActions } from './store/init/init.slice'
import { navigationActions } from './store/navigation/navigation.slice'

import { rootSaga } from './store/root.saga'
Expand Down

This file was deleted.

4 changes: 1 addition & 3 deletions packages/mobile/src/store/init/init.master.saga.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { all, takeEvery, takeLatest, takeLeading } from 'typed-redux-saga'
import { all, takeLatest, takeLeading } from 'typed-redux-saga'
import { initActions } from './init.slice'
import { blindConnectionSaga } from './blindConnection/blindConnection.saga'
import { startConnectionSaga } from './startConnection/startConnection.saga'
import { deepLinkSaga } from './deepLink/deepLink.saga'

export function* initMasterSaga(): Generator {
yield all([
takeEvery(initActions.blindWebsocketConnection.type, blindConnectionSaga),
takeLatest(initActions.startWebsocketConnection.type, startConnectionSaga),
takeLeading(initActions.deepLink.type, deepLinkSaga),
])
Expand Down
1 change: 0 additions & 1 deletion packages/mobile/src/store/init/init.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export const initSlice = createSlice({
id: event,
})
},
blindWebsocketConnection: state => state,
startWebsocketConnection: (state, _action: PayloadAction<WebsocketConnectionPayload>) => state,
suspendWebsocketConnection: state => {
state.isWebsocketConnected = false
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { io } from 'socket.io-client'
import { select, put, call, cancel, fork, takeEvery, FixedTask } from 'typed-redux-saga'
import { select, put, call, cancel, fork, takeEvery, delay, FixedTask } from 'typed-redux-saga'
import { PayloadAction } from '@reduxjs/toolkit'
import { socket as stateManager, Socket } from '@quiet/state-manager'
import { encodeSecret } from '@quiet/common'
Expand All @@ -13,6 +13,16 @@ export function* startConnectionSaga(
const isAlreadyConnected = yield* select(initSelectors.isWebsocketConnected)
if (isAlreadyConnected) return

while (true) {
const isCryptoEngineInitialized = yield* select(initSelectors.isCryptoEngineInitialized)
console.log('WEBSOCKET', 'Waiting for crypto engine to initialize')
if (!isCryptoEngineInitialized) {
yield* delay(500)
} else {
break
}
}

const { dataPort, socketIOSecret } = action.payload

console.log('WEBSOCKET', 'Entered start connection saga', dataPort)
Expand Down
Loading

0 comments on commit 34cd60f

Please sign in to comment.