Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/plugins/terminal/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
</js-module>

<platform name="android">

<framework src="org.java-websocket:Java-WebSocket:1.6.0" />

<config-file parent="/*" target="res/xml/config.xml">
<feature name="Executor">
<param name="android-package" value="com.foxdebug.acode.rk.exec.terminal.Executor" />
Expand All @@ -28,6 +31,7 @@
<source-file src="src/android/StreamHandler.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />

<source-file src="src/android/Executor.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />
<source-file src="src/android/ProcessServer.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />

<source-file src="src/android/TerminalService.java" target-dir="src/com/foxdebug/acode/rk/exec/terminal" />

Expand Down
32 changes: 32 additions & 0 deletions src/plugins/terminal/src/android/Executor.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
import android.app.Activity;
import com.foxdebug.acode.rk.exec.terminal.*;

import java.net.ServerSocket;
import java.io.IOException;
import java.net.InetSocketAddress;

public class Executor extends CordovaPlugin {

private Messenger serviceMessenger;
Expand All @@ -41,6 +45,7 @@ public class Executor extends CordovaPlugin {
private final java.util.Map<String, CallbackContext> callbackContextMap = new java.util.concurrent.ConcurrentHashMap<>();

private static final int REQUEST_POST_NOTIFICATIONS = 1001;
private static int nextPort = 9000;

private void askNotificationPermission(Activity context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Expand Down Expand Up @@ -252,6 +257,33 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo
return true;
}

if (action.equals("spawn")) {
try {
JSONArray cmdArr = args.getJSONArray(0);
String[] cmd = new String[cmdArr.length()];
for (int i = 0; i < cmdArr.length(); i++) {
cmd[i] = cmdArr.getString(i);
}

int port;
try (ServerSocket socket = new ServerSocket(0)) {
socket.setReuseAddress(true);
port = socket.getLocalPort();
}

ProcessServer server = new ProcessServer(port, cmd);
server.start();

callbackContext.success(port);
} catch (Exception e) {
e.printStackTrace();
callbackContext.error("Failed to spawn process: " + e.getMessage());
}

return true;
}


// For all other actions, ensure service is bound first
if (!ensureServiceBound(callbackContext)) {
// Error already sent by ensureServiceBound
Expand Down
92 changes: 92 additions & 0 deletions src/plugins/terminal/src/android/ProcessServer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.foxdebug.acode.rk.exec.terminal;

import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.net.InetSocketAddress;

class ProcessServer extends WebSocketServer {

private final String[] cmd;

private static final class ConnState {
final Process process;
final OutputStream stdin;

ConnState(Process process, OutputStream stdin) {
this.process = process;
this.stdin = stdin;
}
}

ProcessServer(int port, String[] cmd) {
super(new InetSocketAddress(port));
this.cmd = cmd;
}

@Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {
try {
Process process = new ProcessBuilder(cmd).redirectErrorStream(true).start();
InputStream stdout = process.getInputStream();
OutputStream stdin = process.getOutputStream();

conn.setAttachment(new ConnState(process, stdin));

new Thread(() -> {
try {
byte[] buf = new byte[8192];
int len;
while ((len = stdout.read(buf)) != -1) {
conn.send(ByteBuffer.wrap(buf, 0, len));
}
} catch (Exception ignored) {}
}).start();

} catch (Exception ignored) {}
}

@Override
public void onMessage(WebSocket conn, ByteBuffer msg) {
try {
ConnState state = conn.getAttachment();
state.stdin.write(msg.array(), msg.position(), msg.remaining());
state.stdin.flush();
} catch (Exception ignored) {}
}

@Override
public void onMessage(WebSocket conn, String message) {
try {
ConnState state = conn.getAttachment();
state.stdin.write(message.getBytes());
state.stdin.flush();
} catch (Exception ignored) {}
}

@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
stopProcess(conn);
}

@Override
public void onError(WebSocket conn, Exception ex) {
if (conn != null) stopProcess(conn);
}

@Override
public void onStart() {}

private void stopProcess(WebSocket conn) {
try {
ConnState state = conn.getAttachment();
if (state != null) state.process.destroy();
stop();
} catch (Exception ignored) {}
}
}
18 changes: 18 additions & 0 deletions src/plugins/terminal/www/Executor.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@ class Executor {
constructor(BackgroundExecutor = false) {
this.ExecutorType = BackgroundExecutor ? "BackgroundExecutor" : "Executor";
}

spawnStream(cmd, callback){

exec((port)=>{
const ws = new WebSocket(`ws://127.0.0.1:${port}`);
ws.binaryType = "arraybuffer";

ws.onopen = ()=>{
callback(ws);
};

}, null, "Executor", "spawn", [cmd]);

}


/**
* Starts a shell process and enables real-time streaming of stdout, stderr, and exit status.
*
Expand Down Expand Up @@ -150,6 +166,8 @@ class Executor {
*
* @returns {Promise<string>} Resolves when the service has been stopped.
*
* Note: This does not gurantee that all running processes have been killed, but the service will no longer be active. Use with caution.
*
* @example
* executor.stopService();
*/
Expand Down