Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
45 changes: 45 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,9 @@
import android.app.Activity;
import com.foxdebug.acode.rk.exec.terminal.*;

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

public class Executor extends CordovaPlugin {

private Messenger serviceMessenger;
Expand All @@ -41,6 +44,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 @@ -284,12 +288,53 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo
callbackContextMap.put(pidCheck, callbackContext);
isProcessRunning(pidCheck);
return true;
case "spawn":
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 = findFreePort(nextPort++);

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

callbackContext.success(port);
return true;
default:
callbackContext.error("Unknown action: " + action);
return false;
}
}

private int findFreePort(int preferredPort) {

// try preferred port first
if (isPortFree(preferredPort)) {
return preferredPort;
}

// fallback to random ports
while (true) {
int port = 10000 + (int)(Math.random() * 50000);

if (isPortFree(port)) {
return port;
}
}
}

private boolean isPortFree(int port) {
try (ServerSocket socket = new ServerSocket(port)) {
socket.setReuseAddress(true);
return true;
} catch (IOException e) {
return false;
}
}

private void stopServiceNow() {
if (isServiceBound) {
try {
Expand Down
90 changes: 90 additions & 0 deletions src/plugins/terminal/src/android/ProcessServer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
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.InetSocketAddress;
import java.nio.ByteBuffer;

class ProcessServer extends WebSocketServer {

private String[] cmd;
private Process process;

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

@Override
public void onOpen(WebSocket conn, ClientHandshake handshake){

try{

process = new ProcessBuilder(cmd).start();

InputStream stdout = process.getInputStream();
OutputStream stdin = process.getOutputStream();

conn.setAttachment(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 {
OutputStream stdin = conn.getAttachment();
stdin.write(msg.array(), msg.position(), msg.remaining());
stdin.flush();
} catch (Exception ignored) {}
}

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

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

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

@Override
public void onStart(){
}

private void stopProcess(){
try{
if(process != null) 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