Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: improve self hosted runner (Detox) #2453

Merged
merged 13 commits into from
Apr 24, 2024
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
Loading