Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
package betaflight.configurator;

import android.os.Bundle;
import com.getcapacitor.BridgeActivity;
import betaflight.configurator.plugin.SocketPlugin;

public class MainActivity extends BridgeActivity {}
public class MainActivity extends BridgeActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
registerPlugin(SocketPlugin.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package betaflight.configurator.plugin;

import android.util.Log;

import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.annotation.CapacitorPlugin;

import java.io.*;
import java.net.Socket;

/**
* Capacitor plugin that provides raw TCP socket functionality.
* Implements methods to connect, send, receive, and disconnect.
*/
@CapacitorPlugin(name = "SocketPlugin")
public class SocketPlugin extends Plugin {
private Socket socket;
private BufferedReader reader;
private BufferedWriter writer;
private boolean isConnected = false;

@PluginMethod
public void connect(PluginCall call) {
String ip = call.getString("ip");
int port = call.getInt("port");

// Validate inputs
if (ip == null || ip.isEmpty()) {
call.reject("IP address is required");
return;
}

if (port <= 0 || port > 65535) {
call.reject("Invalid port number");
return;
}

// Prevent duplicate connections
if (socket != null && !socket.isClosed()) {
call.reject("Already connected; please disconnect first");
return;
}

// Run network operations on a background thread
getBridge().getExecutor().execute(() -> {
try {
socket = new Socket(ip, port);
socket.setSoTimeout(30_000); // 30s timeout
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
isConnected = true;
JSObject ret = new JSObject();
ret.put("success", true);
call.resolve(ret);
} catch (Exception e) {
closeResources();
call.reject("Connection failed: " + e.getMessage());
}
});
}

@PluginMethod
public void send(PluginCall call) {
String data = call.getString("data");

// Validate input
if (data == null) {
call.reject("Data is required");
return;
}

// Check connection state
if (socket == null || socket.isClosed() || !isConnected || reader == null || writer == null) {
call.reject("Not connected to any server");
return;
}

// Run write operation on a background thread and synchronize on writer
getBridge().getExecutor().execute(() -> {
try {
synchronized (writer) {
// Append newline for framing; adjust as needed for your protocol
writer.write(data);
writer.newLine();
writer.flush();
}
JSObject ret = new JSObject();
ret.put("success", true);
call.resolve(ret);
} catch (Exception e) {
closeResources();
isConnected = false;
call.reject("Send failed: " + e.getMessage());
}
});
}

@PluginMethod
public void receive(PluginCall call) {
// Check connection state
if (socket == null || socket.isClosed() || !isConnected || reader == null) {
call.reject("Not connected to any server");
return;
}

// Run read operation on a background thread to avoid blocking the UI
getBridge().getExecutor().execute(() -> {
try {
String data = reader.readLine();
if (data == null) {
// Stream ended or connection closed by peer
closeResources();
isConnected = false;
call.reject("Connection closed by peer");
return;
}
JSObject ret = new JSObject();
ret.put("data", data);
call.resolve(ret);
} catch (Exception e) {
call.reject("Receive failed: " + e.getMessage());
}
});
}

@PluginMethod
public void disconnect(PluginCall call) {
try {
closeResources();
isConnected = false;
JSObject ret = new JSObject();
ret.put("success", true);
call.resolve(ret);
} catch (Exception e) {
call.reject("Disconnect failed: " + e.getMessage());
}
}

/**
* Helper method to close all resources and clean up state
*/
private void closeResources() {
try {
if (reader != null) {
reader.close();
reader = null;
}
if (writer != null) {
writer.close();
writer = null;
}
if (socket != null) {
socket.close();
socket = null;
}
} catch (IOException e) {
// Log but continue cleanup
isConnected = false;
getContext().getActivity().runOnUiThread(() ->
Log.e("SocketPlugin", "Error closing resources", e));
}
}
}
21 changes: 21 additions & 0 deletions capacitor-plugin-socket/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "capacitor-plugin-socket",
"version": "1.0.0",
"description": "A Capacitor plugin for handling raw TCP sockets.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist/*", "package.json", "README.md"],
"scripts": {
"clean": "rimraf dist",
"build": "npm run clean && tsc -p tsconfig.json"
},
"keywords": ["capacitor", "plugin", "tcp", "socket"],
"author": "Betaflight <dev.betaflight.com>",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": "^5.0.0"
},
"devDependencies": {
"typescript": "^5.0.0"
}
}
6 changes: 6 additions & 0 deletions capacitor-plugin-socket/src/definitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface SocketPlugin {
connect(options: { ip: string; port: number }): Promise<{ success: boolean }>;
send(options: { data: string }): Promise<{ success: boolean }>;
receive(): Promise<{ data: string }>;
disconnect(): Promise<{ success: boolean }>;
}
8 changes: 8 additions & 0 deletions capacitor-plugin-socket/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { registerPlugin } from '@capacitor/core';

const SocketPlugin = registerPlugin('SocketPlugin', {
web: () => import('./web').then(m => new m.SocketPluginWeb()),
});

export * from './definitions';
export { SocketPlugin };
24 changes: 24 additions & 0 deletions capacitor-plugin-socket/src/web.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { WebPlugin } from '@capacitor/core';
import { SocketPlugin } from './definitions';

export class SocketPluginWeb extends WebPlugin implements SocketPlugin {
async connect(options: { ip: string; port: number }): Promise<{ success: boolean }> {
console.log('Web implementation does not support raw TCP sockets.', options);
return { success: false };
}

async send(options: { data: string }): Promise<{ success: boolean }> {
console.log('Web implementation does not support raw TCP sockets.', options);
return { success: false };
}

async receive(): Promise<{ data: string }> {
console.log('Web implementation does not support raw TCP sockets.');
return { data: '' };
}

async disconnect(): Promise<{ success: boolean }> {
console.log('Web implementation does not support raw TCP sockets.');
return { success: false };
}
}