From 53d85827d60587db59c0a68c3e94771e7d5ad190 Mon Sep 17 00:00:00 2001 From: Stephen Blythe Date: Mon, 30 Jun 2014 21:35:37 +0100 Subject: [PATCH 01/14] Issue #54 - added support for sending binary data. (Implemented for Android only) Interface is: sendBinary(successCallback, errorCallback, connectionId, data) data is a JSONArray, one element per byte, e.g. [ 0x00, 0x01, 0x00, 0xFD ] --- src/android/Connection.java | 14 ++++++++ src/android/SocketPlugin.java | 63 ++++++++++++++++++++++++++++++++++- www/socket.js | 6 ++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/src/android/Connection.java b/src/android/Connection.java index f834dd7..e7457e9 100644 --- a/src/android/Connection.java +++ b/src/android/Connection.java @@ -3,6 +3,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; @@ -19,6 +20,7 @@ public class Connection extends Thread { private Socket callbackSocket; private PrintWriter writer; + private OutputStream outputStream; private BufferedReader reader; private Boolean mustClose; @@ -101,6 +103,17 @@ public void write(String data) { + /** + * Outputs to socket output stream to send binary data to target host. + * + * @param data information to be sent + */ + public void writeBinary(byte[] data) throws IOException { + this.outputStream.write(data); + } + + + /* (non-Javadoc) * @see java.lang.Thread#run() */ @@ -111,6 +124,7 @@ public void run() { try { this.callbackSocket = new Socket(this.host, this.port); this.writer = new PrintWriter(this.callbackSocket.getOutputStream(), true); + this.outputStream = this.callbackSocket.getOutputStream(); this.reader = new BufferedReader(new InputStreamReader(callbackSocket.getInputStream())); // receiving data chunk diff --git a/src/android/SocketPlugin.java b/src/android/SocketPlugin.java index 91f5e37..e9b9caa 100644 --- a/src/android/SocketPlugin.java +++ b/src/android/SocketPlugin.java @@ -2,6 +2,8 @@ import android.annotation.SuppressLint; +import java.io.IOException; + import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -43,6 +45,10 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo this.send(args, callbackContext); return true; + }else if(action.equals("sendBinary")) { + this.sendBinary(args, callbackContext); + return true; + } else if (action.equals("disconnect")) { this.disconnect(args, callbackContext); return true; @@ -192,6 +198,61 @@ private void send(JSONArray args, CallbackContext callbackContext) { } } + + /** + * Send binary information to target host + * + * @param args + * @param callbackContext + */ + private void sendBinary(JSONArray args, CallbackContext callbackContext) { + Connection socket; + + // validating parameters + if (args.length() < 2) { + callbackContext.error("Missing arguments when calling 'sendBinary' action."); + } else { + try { + // retrieving parameters + String key = args.getString(0); + JSONArray jsData = args.getJSONArray(1); + byte[] data = new byte[jsData.length()]; + for (int i=0; i Date: Mon, 30 Jun 2014 21:42:29 +0100 Subject: [PATCH 02/14] Added sendBinary doc to README.md --- README.md | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 39d1a30..de7a354 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,8 @@ The plugin creates a "Socket" object exposed on window.tlantic.plugins.socket. T * connect: opens a socket connection; * disconnect: closes a socket connection; * disconnectAll: closes ALL opened connections; -* send: send data using a given connection; +* send: send text data using a given connection; +* sendBinary: send binary data using a given connection; * isConnected: returns a boolean falg representing socket connectivity status; * receive: callback used by plugin's native code. Can be override by a custom implementation. @@ -90,7 +91,7 @@ window.tlantic.plugins.socket.connect( ### send (successCallback, errorCallback, connectionId, data) -Sends information and calls success callback if information was send and does not wait for any response. To check how to receive data, please see the item below. +Sends text information and calls success callback if information was sent and does not wait for any response. To check how to receive data, please see the item below. Example: @@ -108,7 +109,29 @@ window.tlantic.plugins.socket.send( ); ``` -### isConnected (connectionId, successCallback, errorCallback) +### sendBinary (successCallback, errorCallback, connectionId, data) + +Sends binary data and calls success callback if information was sent and does not wait for any response. To check how to receive data, please see the item below. + +Binary data to be sent is passed in `data` which is a JSONArray, one integer element per byte. + +Example: + +``` +window.tlantic.plugins.socket.sendBinary( + function () { + console.log('worked!'); + }, + + function () { + console.log('failed!'); + }, + '192.168.2.5:18002', + [ 0x00, 0x01, 0x00, 0xFD ] +); +``` + +#### isConnected (connectionId, successCallback, errorCallback) Returns a boolean value representing the connection status. True, if socket streams are opened and false case else. Both values are supposed to be returned through successCallback. The error callback is called only when facing errors due the check process. From 3572684dbaae9a5caa971b30aaa26f497622ab79 Mon Sep 17 00:00:00 2001 From: Kevin Mershon Date: Thu, 23 Apr 2015 19:38:23 -0700 Subject: [PATCH 03/14] Read 512 bytes at a chunk directly instead of reading strings --- src/android/Connection.java | 12 ++++++------ src/android/SocketPlugin.java | 28 +++++++++++++++------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/android/Connection.java b/src/android/Connection.java index e7457e9..b9bf683 100644 --- a/src/android/Connection.java +++ b/src/android/Connection.java @@ -118,7 +118,7 @@ public void writeBinary(byte[] data) throws IOException { * @see java.lang.Thread#run() */ public void run() { - String chunk = null; + byte[] chunk = new byte[512]; // creating connection try { @@ -133,12 +133,12 @@ public void run() { try { if (this.isConnected()) { - chunk = reader.readLine(); + int bytesRead = callbackSocket.getInputStream().read(chunk); + byte[] line = new byte[bytesRead]; + System.arraycopy(chunk, 0, line, 0, bytesRead); - if (chunk != null) { - chunk = chunk.replaceAll("\"\"", "null"); - System.out.print("## RECEIVED DATA: " + chunk); - hook.sendMessage(this.host, this.port, chunk); + if (bytesRead > 0) { + hook.sendMessage(this.host, this.port, line); } } } catch (Exception e) { diff --git a/src/android/SocketPlugin.java b/src/android/SocketPlugin.java index e9b9caa..94551f8 100644 --- a/src/android/SocketPlugin.java +++ b/src/android/SocketPlugin.java @@ -2,6 +2,8 @@ import android.annotation.SuppressLint; +import android.util.Base64; + import java.io.IOException; import java.util.HashMap; @@ -293,55 +295,55 @@ private void disconnect (JSONArray args, CallbackContext callbackContext) { } catch (JSONException e) { callbackContext.error("Invalid parameters for 'connect' action:" + e.getMessage()); } - } + } } /** * Closes all existing connections - * + * * @param callbackContext */ private void disconnectAll (CallbackContext callbackContext) { // building iterator Iterator> it = this.pool.entrySet().iterator(); - + while( it.hasNext() ) { - + // retrieving object Map.Entry pairs = (Entry) it.next(); Connection socket = pairs.getValue(); - + // checking connection if (socket.isConnected()) { socket.close(); } - + // removing from pool this.pool.remove(pairs.getKey()); } - + callbackContext.success("All connections were closed."); } /** * Callback for Connection object data receive. Relay information to javascript object method: window.tlantic.plugins.socket.receive(); - * + * * @param host * @param port * @param chunk */ - public synchronized void sendMessage(String host, int port, String chunk) { - final String receiveHook = "window.tlantic.plugins.socket.receive(\"" + host + "\"," + port + ",\"" + this.buildKey(host, port) + "\",\"" + chunk.replace("\"", "\\\"") + "\");"; - + public synchronized void sendMessage(String host, int port, byte[] chunk) { + final String receiveHook = "window.tlantic.plugins.socket.receive(\"" + host + "\"," + port + ",\"" + this.buildKey(host, port) + "\",\"" + Base64.encodeToString(chunk, Base64.URL_SAFE) + "\");"; + cordova.getActivity().runOnUiThread(new Runnable() { @Override public void run() { webView.loadUrl("javascript:" + receiveHook); } - + }); } - + } From 763544ed68e9e08bb9850b189e1e4ade958bbe09 Mon Sep 17 00:00:00 2001 From: Kevin Mershon Date: Sun, 26 Apr 2015 23:49:48 -0700 Subject: [PATCH 04/14] Use default flags for base64 encoding --- src/android/SocketPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/android/SocketPlugin.java b/src/android/SocketPlugin.java index 94551f8..8a25a25 100644 --- a/src/android/SocketPlugin.java +++ b/src/android/SocketPlugin.java @@ -334,7 +334,7 @@ private void disconnectAll (CallbackContext callbackContext) { * @param chunk */ public synchronized void sendMessage(String host, int port, byte[] chunk) { - final String receiveHook = "window.tlantic.plugins.socket.receive(\"" + host + "\"," + port + ",\"" + this.buildKey(host, port) + "\",\"" + Base64.encodeToString(chunk, Base64.URL_SAFE) + "\");"; + final String receiveHook = "window.tlantic.plugins.socket.receive(\"" + host + "\"," + port + ",\"" + this.buildKey(host, port) + "\",\"" + Base64.encodeToString(chunk, Base64.DEFAULT) + "\");"; cordova.getActivity().runOnUiThread(new Runnable() { From beee81519b89287fb3d73e19eb9688ecf5e4aff4 Mon Sep 17 00:00:00 2001 From: Kevin Mershon Date: Tue, 12 May 2015 22:52:31 -0700 Subject: [PATCH 05/14] Use atob to decode base64 string --- src/android/Connection.java | 275 ++++++++------- src/android/SocketPlugin.java | 638 +++++++++++++++++----------------- www/socket.js | 4 +- 3 files changed, 458 insertions(+), 459 deletions(-) diff --git a/src/android/Connection.java b/src/android/Connection.java index b9bf683..d8015da 100644 --- a/src/android/Connection.java +++ b/src/android/Connection.java @@ -12,148 +12,147 @@ /** * @author viniciusl * - * This class represents a socket connection, behaving like a thread to listen + * This class represents a socket connection, behaving like a thread to listen * a TCP port and receive data */ public class Connection extends Thread { - private SocketPlugin hook; - - private Socket callbackSocket; - private PrintWriter writer; - private OutputStream outputStream; - private BufferedReader reader; - - private Boolean mustClose; - private String host; - private int port; - - - /** - * Creates a TCP socket connection object. - * - * @param pool Object containing "sendMessage" method to be called as a callback for data receive. - * @param host Target host for socket connection. - * @param port Target port for socket connection - */ - public Connection(SocketPlugin pool, String host, int port) { - super(); - setDaemon(true); - - this.mustClose = false; - this.host = host; - this.port = port; - this.hook = pool; - } - - - /** - * Returns socket connection state. - * - * @return true if socket connection is established or false case else. - */ - public boolean isConnected() { - - boolean result = ( - this.callbackSocket == null ? false : - this.callbackSocket.isConnected() && - this.callbackSocket.isBound() && - !this.callbackSocket.isClosed() && - !this.callbackSocket.isInputShutdown() && - !this.callbackSocket.isOutputShutdown()); - - // if everything apparently is fine, time to test the streams - if (result) { - try { - this.callbackSocket.getInputStream().available(); - } catch (IOException e) { - // connection lost - result = false; - } - } - - return result; - } - - /** - * Closes socket connection. - */ - public void close() { - // closing connection - try { - //this.writer.close(); - //this.reader.close(); - callbackSocket.shutdownInput(); - callbackSocket.shutdownOutput(); - callbackSocket.close(); - this.mustClose = true; - } catch (IOException e) { - e.printStackTrace(); - } - } - - - /** - * Writes on socket output stream to send data to target host. - * - * @param data information to be sent - */ - public void write(String data) { - this.writer.println(data); - } - - - - /** - * Outputs to socket output stream to send binary data to target host. - * - * @param data information to be sent - */ - public void writeBinary(byte[] data) throws IOException { - this.outputStream.write(data); - } - - - - /* (non-Javadoc) - * @see java.lang.Thread#run() - */ - public void run() { + private SocketPlugin hook; + + private Socket callbackSocket; + private PrintWriter writer; + private OutputStream outputStream; + private BufferedReader reader; + + private Boolean mustClose; + private String host; + private int port; + + + /** + * Creates a TCP socket connection object. + * + * @param pool Object containing "sendMessage" method to be called as a callback for data receive. + * @param host Target host for socket connection. + * @param port Target port for socket connection + */ + public Connection(SocketPlugin pool, String host, int port) { + super(); + setDaemon(true); + + this.mustClose = false; + this.host = host; + this.port = port; + this.hook = pool; + } + + + /** + * Returns socket connection state. + * + * @return true if socket connection is established or false case else. + */ + public boolean isConnected() { + + boolean result = ( + this.callbackSocket == null ? false : + this.callbackSocket.isConnected() && + this.callbackSocket.isBound() && + !this.callbackSocket.isClosed() && + !this.callbackSocket.isInputShutdown() && + !this.callbackSocket.isOutputShutdown()); + + // if everything apparently is fine, time to test the streams + if (result) { + try { + this.callbackSocket.getInputStream().available(); + } catch (IOException e) { + // connection lost + result = false; + } + } + + return result; + } + + /** + * Closes socket connection. + */ + public void close() { + // closing connection + try { + //this.writer.close(); + //this.reader.close(); + callbackSocket.shutdownInput(); + callbackSocket.shutdownOutput(); + callbackSocket.close(); + this.mustClose = true; + } catch (IOException e) { + e.printStackTrace(); + } + } + + + /** + * Writes on socket output stream to send data to target host. + * + * @param data information to be sent + */ + public void write(String data) { + this.writer.println(data); + } + + + + /** + * Outputs to socket output stream to send binary data to target host. + * + * @param data information to be sent + */ + public void writeBinary(byte[] data) throws IOException { + this.outputStream.write(data); + } + + + /* (non-Javadoc) + * @see java.lang.Thread#run() + */ + public void run() { byte[] chunk = new byte[512]; - // creating connection - try { - this.callbackSocket = new Socket(this.host, this.port); - this.writer = new PrintWriter(this.callbackSocket.getOutputStream(), true); - this.outputStream = this.callbackSocket.getOutputStream(); - this.reader = new BufferedReader(new InputStreamReader(callbackSocket.getInputStream())); - - // receiving data chunk - while(!this.mustClose){ - - try { - - if (this.isConnected()) { - int bytesRead = callbackSocket.getInputStream().read(chunk); - byte[] line = new byte[bytesRead]; - System.arraycopy(chunk, 0, line, 0, bytesRead); - - if (bytesRead > 0) { - hook.sendMessage(this.host, this.port, line); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - } catch (UnknownHostException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } catch (IOException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - - } + // creating connection + try { + this.callbackSocket = new Socket(this.host, this.port); + this.writer = new PrintWriter(this.callbackSocket.getOutputStream(), true); + this.outputStream = this.callbackSocket.getOutputStream(); + this.reader = new BufferedReader(new InputStreamReader(callbackSocket.getInputStream())); + + // receiving data chunk + while(!this.mustClose){ + + try { + + if (this.isConnected()) { + int bytesRead = callbackSocket.getInputStream().read(chunk); + byte[] line = new byte[bytesRead]; + System.arraycopy(chunk, 0, line, 0, bytesRead); + + if (bytesRead > 0) { + hook.sendMessage(this.host, this.port, line); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + } catch (UnknownHostException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + + } } diff --git a/src/android/SocketPlugin.java b/src/android/SocketPlugin.java index 8a25a25..f88ad7c 100644 --- a/src/android/SocketPlugin.java +++ b/src/android/SocketPlugin.java @@ -19,7 +19,7 @@ /** * @author viniciusl * - * Plugin to handle TCP socket connections. + * Plugin to handle TCP socket connections. */ /** * @author viniciusl @@ -27,323 +27,323 @@ */ public class SocketPlugin extends CordovaPlugin { - private Map pool = new HashMap(); // pool of "active" connections - - /* (non-Javadoc) - * @see org.apache.cordova.CordovaPlugin#execute(java.lang.String, org.json.JSONArray, org.apache.cordova.CallbackContext) - */ - @Override - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - - if (action.equals("connect")) { - this.connect(args, callbackContext); - return true; - - }else if(action.equals("isConnected")) { - this.isConnected(args, callbackContext); - return true; - - }else if(action.equals("send")) { - this.send(args, callbackContext); - return true; - - }else if(action.equals("sendBinary")) { - this.sendBinary(args, callbackContext); - return true; - - } else if (action.equals("disconnect")) { - this.disconnect(args, callbackContext); - return true; - - } else if (action.equals("disconnectAll")) { - this.disconnectAll(callbackContext); - return true; - - } else { - return false; - } - } - - /** - * Build a key to identify a socket connection based on host and port information. - * - * @param host Target host - * @param port Target port - * @return connection key - */ - @SuppressLint("DefaultLocale") - private String buildKey(String host, int port) { - return (host.toLowerCase() + ":" + port); - } - - /** - * Opens a socket connection. - * - * @param args - * @param callbackContext - */ - private void connect (JSONArray args, CallbackContext callbackContext) { - String key; - String host; - int port; - Connection socket; - - // validating parameters - if (args.length() < 2) { - callbackContext.error("Missing arguments when calling 'connect' action."); - } else { - - // opening connection and adding into pool - try { - - // preparing parameters - host = args.getString(0); - port = args.getInt(1); - key = this.buildKey(host, port); - - // creating connection - if (this.pool.get(key) == null) { - socket = new Connection(this, host, port); - socket.start(); - this.pool.put(key, socket); - } - - // adding to pool - callbackContext.success(key); - - } catch (JSONException e) { - callbackContext.error("Invalid parameters for 'connect' action: " + e.getMessage()); - } - } - } - - /** - * Returns connection information - * - * @param args - * @param callbackContext - */ - private void isConnected(JSONArray args, CallbackContext callbackContext) { - Connection socket; - - // validating parameters - if (args.length() < 1) { - callbackContext.error("Missing arguments when calling 'isConnected' action."); - } else { - try { - // retrieving parameters - String key = args.getString(0); - - // getting socket - socket = this.pool.get(key); - - // checking if socket was not found and his connectivity - if (socket == null) { - callbackContext.error("No connection found with host " + key); - - } else { - - // ending send process - callbackContext.success( (socket.isConnected() ? 1 : 0) ); - } - - } catch (JSONException e) { - callbackContext.error("Unexpected error sending information: " + e.getMessage()); - } - } - } - - - /** - * Send information to target host - * - * @param args - * @param callbackContext - */ - private void send(JSONArray args, CallbackContext callbackContext) { - Connection socket; - - // validating parameters - if (args.length() < 2) { - callbackContext.error("Missing arguments when calling 'send' action."); - } else { - try { - // retrieving parameters - String key = args.getString(0); - String data = args.getString(1); - - // getting socket - socket = this.pool.get(key); - - // checking if socket was not found and his connectivity - if (socket == null) { - callbackContext.error("No connection found with host " + key); - - } else if (!socket.isConnected()) { - callbackContext.error("Invalid connection with host " + key); - - } else if (data.length() == 0) { - callbackContext.error("Cannot send empty data to " + key); - - } else { - - // write on output stream - socket.write(data); - - // ending send process - callbackContext.success(); - } - - } catch (JSONException e) { - callbackContext.error("Unexpected error sending information: " + e.getMessage()); - } - } - } - - - /** - * Send binary information to target host - * - * @param args - * @param callbackContext - */ - private void sendBinary(JSONArray args, CallbackContext callbackContext) { - Connection socket; - - // validating parameters - if (args.length() < 2) { - callbackContext.error("Missing arguments when calling 'sendBinary' action."); - } else { - try { - // retrieving parameters - String key = args.getString(0); - JSONArray jsData = args.getJSONArray(1); - byte[] data = new byte[jsData.length()]; - for (int i=0; i> it = this.pool.entrySet().iterator(); - - while( it.hasNext() ) { - - // retrieving object - Map.Entry pairs = (Entry) it.next(); - Connection socket = pairs.getValue(); - - // checking connection - if (socket.isConnected()) { - socket.close(); - } - - // removing from pool - this.pool.remove(pairs.getKey()); - } - - callbackContext.success("All connections were closed."); - } - - - /** - * Callback for Connection object data receive. Relay information to javascript object method: window.tlantic.plugins.socket.receive(); - * - * @param host - * @param port - * @param chunk - */ - public synchronized void sendMessage(String host, int port, byte[] chunk) { - final String receiveHook = "window.tlantic.plugins.socket.receive(\"" + host + "\"," + port + ",\"" + this.buildKey(host, port) + "\",\"" + Base64.encodeToString(chunk, Base64.DEFAULT) + "\");"; - - cordova.getActivity().runOnUiThread(new Runnable() { - - @Override - public void run() { - webView.loadUrl("javascript:" + receiveHook); - } - - }); - } + private Map pool = new HashMap(); // pool of "active" connections + + /* (non-Javadoc) + * @see org.apache.cordova.CordovaPlugin#execute(java.lang.String, org.json.JSONArray, org.apache.cordova.CallbackContext) + */ + @Override + public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { + + if (action.equals("connect")) { + this.connect(args, callbackContext); + return true; + + }else if(action.equals("isConnected")) { + this.isConnected(args, callbackContext); + return true; + + }else if(action.equals("send")) { + this.send(args, callbackContext); + return true; + + }else if(action.equals("sendBinary")) { + this.sendBinary(args, callbackContext); + return true; + + } else if (action.equals("disconnect")) { + this.disconnect(args, callbackContext); + return true; + + } else if (action.equals("disconnectAll")) { + this.disconnectAll(callbackContext); + return true; + + } else { + return false; + } + } + + /** + * Build a key to identify a socket connection based on host and port information. + * + * @param host Target host + * @param port Target port + * @return connection key + */ + @SuppressLint("DefaultLocale") + private String buildKey(String host, int port) { + return (host.toLowerCase() + ":" + port); + } + + /** + * Opens a socket connection. + * + * @param args + * @param callbackContext + */ + private void connect (JSONArray args, CallbackContext callbackContext) { + String key; + String host; + int port; + Connection socket; + + // validating parameters + if (args.length() < 2) { + callbackContext.error("Missing arguments when calling 'connect' action."); + } else { + + // opening connection and adding into pool + try { + + // preparing parameters + host = args.getString(0); + port = args.getInt(1); + key = this.buildKey(host, port); + + // creating connection + if (this.pool.get(key) == null) { + socket = new Connection(this, host, port); + socket.start(); + this.pool.put(key, socket); + } + + // adding to pool + callbackContext.success(key); + + } catch (JSONException e) { + callbackContext.error("Invalid parameters for 'connect' action: " + e.getMessage()); + } + } + } + + /** + * Returns connection information + * + * @param args + * @param callbackContext + */ + private void isConnected(JSONArray args, CallbackContext callbackContext) { + Connection socket; + + // validating parameters + if (args.length() < 1) { + callbackContext.error("Missing arguments when calling 'isConnected' action."); + } else { + try { + // retrieving parameters + String key = args.getString(0); + + // getting socket + socket = this.pool.get(key); + + // checking if socket was not found and his connectivity + if (socket == null) { + callbackContext.error("No connection found with host " + key); + + } else { + + // ending send process + callbackContext.success( (socket.isConnected() ? 1 : 0) ); + } + + } catch (JSONException e) { + callbackContext.error("Unexpected error sending information: " + e.getMessage()); + } + } + } + + + /** + * Send information to target host + * + * @param args + * @param callbackContext + */ + private void send(JSONArray args, CallbackContext callbackContext) { + Connection socket; + + // validating parameters + if (args.length() < 2) { + callbackContext.error("Missing arguments when calling 'send' action."); + } else { + try { + // retrieving parameters + String key = args.getString(0); + String data = args.getString(1); + + // getting socket + socket = this.pool.get(key); + + // checking if socket was not found and his connectivity + if (socket == null) { + callbackContext.error("No connection found with host " + key); + + } else if (!socket.isConnected()) { + callbackContext.error("Invalid connection with host " + key); + + } else if (data.length() == 0) { + callbackContext.error("Cannot send empty data to " + key); + + } else { + + // write on output stream + socket.write(data); + + // ending send process + callbackContext.success(); + } + + } catch (JSONException e) { + callbackContext.error("Unexpected error sending information: " + e.getMessage()); + } + } + } + + + /** + * Send binary information to target host + * + * @param args + * @param callbackContext + */ + private void sendBinary(JSONArray args, CallbackContext callbackContext) { + Connection socket; + + // validating parameters + if (args.length() < 2) { + callbackContext.error("Missing arguments when calling 'sendBinary' action."); + } else { + try { + // retrieving parameters + String key = args.getString(0); + JSONArray jsData = args.getJSONArray(1); + byte[] data = new byte[jsData.length()]; + for (int i=0; i> it = this.pool.entrySet().iterator(); + + while( it.hasNext() ) { + + // retrieving object + Map.Entry pairs = (Entry) it.next(); + Connection socket = pairs.getValue(); + + // checking connection + if (socket.isConnected()) { + socket.close(); + } + + // removing from pool + this.pool.remove(pairs.getKey()); + } + + callbackContext.success("All connections were closed."); + } + + + /** + * Callback for Connection object data receive. Relay information to javascript object method: window.tlantic.plugins.socket.receive(); + * + * @param host + * @param port + * @param chunk + */ + public synchronized void sendMessage(String host, int port, byte[] chunk) { + final String receiveHook = "window.tlantic.plugins.socket.receive(\"" + host + "\"," + port + ",\"" + this.buildKey(host, port) + "\",window.atob(\"" + Base64.encodeToString(chunk, Base64.DEFAULT) + ")\");"; + + cordova.getActivity().runOnUiThread(new Runnable() { + + @Override + public void run() { + webView.loadUrl("javascript:" + receiveHook); + } + + }); + } } diff --git a/www/socket.js b/www/socket.js index f2340de..ceeadc5 100644 --- a/www/socket.js +++ b/www/socket.js @@ -26,7 +26,7 @@ Socket.prototype.disconnectAll = function (successCallback, errorCallback) { 'use strict'; exec(successCallback, errorCallback, this.pluginRef, 'disconnectAll', []); }; - + // Socket.prototype.isConnected = function (connectionId, successCallback, errorCallback) { 'use strict'; @@ -50,7 +50,7 @@ Socket.prototype.receive = function (host, port, connectionId, chunk) { 'use strict'; var evReceive = document.createEvent('Events'); - + evReceive.initEvent(this.receiveHookName, true, true); evReceive.metadata = { connection: { From 74c69a77dcafad3973ba4797f4a4340975bf51b9 Mon Sep 17 00:00:00 2001 From: Kevin Mershon Date: Tue, 12 May 2015 22:59:14 -0700 Subject: [PATCH 06/14] Fix typo --- src/android/SocketPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/android/SocketPlugin.java b/src/android/SocketPlugin.java index f88ad7c..e50e52e 100644 --- a/src/android/SocketPlugin.java +++ b/src/android/SocketPlugin.java @@ -334,7 +334,7 @@ private void disconnectAll (CallbackContext callbackContext) { * @param chunk */ public synchronized void sendMessage(String host, int port, byte[] chunk) { - final String receiveHook = "window.tlantic.plugins.socket.receive(\"" + host + "\"," + port + ",\"" + this.buildKey(host, port) + "\",window.atob(\"" + Base64.encodeToString(chunk, Base64.DEFAULT) + ")\");"; + final String receiveHook = "window.tlantic.plugins.socket.receive(\"" + host + "\"," + port + ",\"" + this.buildKey(host, port) + "\",window.atob(\"" + Base64.encodeToString(chunk, Base64.DEFAULT) + "\"));"; cordova.getActivity().runOnUiThread(new Runnable() { From cc9df4dbfa7ef608d24d398dbd1e707ed2e2aa3b Mon Sep 17 00:00:00 2001 From: Kevin Mershon Date: Tue, 12 May 2015 23:53:20 -0700 Subject: [PATCH 07/14] Implement writing/reading binary --- src/ios/CDVSocketPlugin.m | 116 ++++++++++++++++++-------------------- src/ios/Connection.h | 1 + src/ios/Connection.m | 50 ++++++++-------- 3 files changed, 82 insertions(+), 85 deletions(-) diff --git a/src/ios/CDVSocketPlugin.m b/src/ios/CDVSocketPlugin.m index 1dd6e72..8339cec 100644 --- a/src/ios/CDVSocketPlugin.m +++ b/src/ios/CDVSocketPlugin.m @@ -7,19 +7,19 @@ @implementation CDVSocketPlugin : CDVPlugin - (NSString*) buildKey : (NSString*) host : (int) port { NSString* tempHost = [host lowercaseString]; NSString* tempPort = [NSString stringWithFormat : @"%d", port]; - + return [[tempHost stringByAppendingString : @":"] stringByAppendingString:tempPort]; } - (void) connect : (CDVInvokedUrlCommand*) command { // Validating parameters if ([command.arguments count] < 2) { - + // Triggering parameter error CDVPluginResult* result = [CDVPluginResult resultWithStatus : CDVCommandStatus_ERROR messageAsString : @"Missing arguments when calling 'connect' action."]; - + [self.commandDelegate sendPluginResult : result callbackId : command.callbackId @@ -29,23 +29,23 @@ - (void) connect : (CDVInvokedUrlCommand*) command { if (!pool) { self->pool = [[NSMutableDictionary alloc] init]; } - + // Running in background to avoid thread locks [self.commandDelegate runInBackground:^{ - + CDVPluginResult* result = nil; Connection* socket = nil; NSString* key = nil; NSString* host = nil; int port = 0; - + // Opening connection and adding into pool @try { // Preparing parameters host = [command.arguments objectAtIndex : 0]; port = [[command.arguments objectAtIndex : 1] integerValue]; key = [self buildKey : host : port]; - + // Checking existing connections if ([pool objectForKey : key]) { NSLog(@"Recovered connection with %@", key); @@ -59,10 +59,10 @@ - (void) connect : (CDVInvokedUrlCommand*) command { socket = [[Connection alloc] initWithNetworkAddress:host :port]; [socket setDelegate:self]; [socket open]; - + // Adding to pool [self->pool setObject:socket forKey:key]; - + // Formatting success response result = [CDVPluginResult resultWithStatus : @@ -75,7 +75,7 @@ - (void) connect : (CDVInvokedUrlCommand*) command { resultWithStatus : CDVCommandStatus_ERROR messageAsString : @"Unexpected exception when executing 'connect' action."]; } - + // Returns the Callback Resolution [self.commandDelegate sendPluginResult : result callbackId : command.callbackId]; @@ -86,33 +86,33 @@ - (void) connect : (CDVInvokedUrlCommand*) command { - (void) isConnected : (CDVInvokedUrlCommand *) command { // Validating parameters if ([command.arguments count] < 1) { - + // Triggering parameter error CDVPluginResult* result = [CDVPluginResult resultWithStatus : CDVCommandStatus_ERROR messageAsString : @"Missing arguments when calling 'isConnected' action."]; - + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId ]; - + } else { - + // running in background to avoid thread locks [self.commandDelegate runInBackground : ^{ - + CDVPluginResult* result= nil; Connection* socket = nil; NSString* key = nil; - + @try { // Preparing parameters key = [command.arguments objectAtIndex:0]; - + // Getting connection from pool socket = [pool objectForKey:key]; - + // Checking if socket was not found and his conenctivity if (socket == nil) { NSLog(@"Connection not found"); @@ -121,7 +121,7 @@ - (void) isConnected : (CDVInvokedUrlCommand *) command { messageAsString : @"No connection found with host."]; } else { NSLog(@"Checking data connection..."); - + // Formatting success response result = [CDVPluginResult resultWithStatus : CDVCommandStatus_OK @@ -134,7 +134,7 @@ - (void) isConnected : (CDVInvokedUrlCommand *) command { resultWithStatus : CDVCommandStatus_ERROR messageAsString : @"Unexpected exception when executon 'isConnected' action."]; } - + // Returning callback resolution [self.commandDelegate sendPluginResult : result @@ -147,24 +147,24 @@ - (void) isConnected : (CDVInvokedUrlCommand *) command { - (BOOL) disposeConnection : (NSString *) key { Connection* socket = nil; BOOL result = NO; - + @try { // Getting connection from pool socket = [pool objectForKey : key]; - + // Closing connection if (socket) { [pool removeObjectForKey : key]; - + if ([socket isConnected]) [socket close]; - + socket = nil; - + NSLog(@"Closed connection with %@", key); } else NSLog(@"Connection %@ already closed!", key); - + // Setting success result = YES; } @@ -184,15 +184,15 @@ - (void) disconnect : (CDVInvokedUrlCommand*) command { CDVPluginResult* result = [CDVPluginResult resultWithStatus : CDVCommandStatus_ERROR messageAsString : @"Missing arguments when calling 'disconnect' action."]; - + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } else { // Running in background to avoid thread locks [self.commandDelegate runInBackground : ^{ - + CDVPluginResult* result= nil; NSString *key = nil; - + @try { // Preparing parameters key = [command.arguments objectAtIndex : 0]; @@ -211,7 +211,7 @@ - (void) disconnect : (CDVInvokedUrlCommand*) command { resultWithStatus : CDVCommandStatus_ERROR messageAsString : @"Unexpected exception when executing 'disconnect' action."]; } - + // Returns the Callback Resolution [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; }]; @@ -221,24 +221,24 @@ - (void) disconnect : (CDVInvokedUrlCommand*) command { - (void) disconnectAll: (CDVInvokedUrlCommand *) command { // Running in background to avoid thread locks [self.commandDelegate runInBackground:^{ - + CDVPluginResult* result = nil; Connection * socket = nil; BOOL partial = NO; - + @try { - + // Iterating connection pool for (id key in pool) { socket = [pool objectForKey : key]; - + // Try to close it if (![self disposeConnection : key]) { // If no success, need to set as partial disconnection partial = YES; } } - + // Formatting result if (partial) result = [CDVPluginResult @@ -263,43 +263,43 @@ - (void) disconnectAll: (CDVInvokedUrlCommand *) command { } - (void) send: (CDVInvokedUrlCommand *) command { - + // Validating parameters if ([command.arguments count] < 2) { // Triggering parameter error CDVPluginResult* result = [CDVPluginResult resultWithStatus : CDVCommandStatus_ERROR messageAsString : @"Missing arguments when calling 'send' action."]; - + [self.commandDelegate sendPluginResult : result callbackId:command.callbackId ]; - + } else { - + // Running in background to avoid thread locks [self.commandDelegate runInBackground : ^{ - + CDVPluginResult* result= nil; Connection* socket = nil; NSString* data = nil; NSString* key = nil; - + @try { // Preparing parameters key = [command.arguments objectAtIndex : 0]; - + // Getting connection from pool socket = [pool objectForKey : key]; - + // Checking if socket was not found and his conenctivity if (socket == nil) { NSLog(@"Connection not found"); result = [CDVPluginResult resultWithStatus : CDVCommandStatus_ERROR messageAsString : @"No connection found with host."]; - + } else if (![socket isConnected]) { NSLog(@"Socket is not connected."); result = [CDVPluginResult @@ -308,11 +308,11 @@ - (void) send: (CDVInvokedUrlCommand *) command { } else { // Writting on output stream data = [command.arguments objectAtIndex : 1]; - + NSLog(@"Sending data to %@ - %@", key, data); - + [socket write:data]; - + // Formatting success response result = [CDVPluginResult resultWithStatus : CDVCommandStatus_OK @@ -325,27 +325,21 @@ - (void) send: (CDVInvokedUrlCommand *) command { resultWithStatus : CDVCommandStatus_ERROR messageAsString : @"Unexpected exception when executon 'send' action."]; } - + // Returning callback resolution [self.commandDelegate sendPluginResult : result callbackId : command.callbackId]; }]; } } -- (void) sendMessage :(NSString *)host :(int)port :(NSString *)chunk { - - // Handling escape chars - NSMutableString *data = [NSMutableString stringWithString : chunk]; - [data replaceOccurrencesOfString : @"\n" - withString : @"\\n" - options : NSCaseInsensitiveSearch - range : NSMakeRange(0, [data length])]; - +- (void) sendMessage :(NSString *)host :(int)port :(NSData *)chunk { + NSString *base64Encoded = [nsdata base64EncodedStringWithOptions:0]; + // Relay to webview - NSString *receiveHook = [NSString stringWithFormat : @"window.tlantic.plugins.socket.receive('%@', %d, '%@', '%@' );", - host, port, [self buildKey : host : port], [NSString stringWithString : data]]; - + NSString *receiveHook = [NSString stringWithFormat : @"window.tlantic.plugins.socket.receive('%@', %d, '%@', window.atob('%@') );", + host, port, [self buildKey : host : port], base64Encoded]; + [self writeJavascript:receiveHook]; } -@end \ No newline at end of file +@end diff --git a/src/ios/Connection.h b/src/ios/Connection.h index 651a316..c37611e 100644 --- a/src/ios/Connection.h +++ b/src/ios/Connection.h @@ -21,6 +21,7 @@ - (void) open; - (void) close; - (void) write : (NSString*) data; +- (void) writeBinary : (NSData*) chunk; - (void) stream : (NSStream *) theStream handleEvent : (NSStreamEvent) streamEvent; diff --git a/src/ios/Connection.m b/src/ios/Connection.m index 958847c..a27e909 100644 --- a/src/ios/Connection.m +++ b/src/ios/Connection.m @@ -24,16 +24,16 @@ - (void) open { // Init network communication settings CFReadStreamRef readStream; CFWriteStreamRef writeStream; - + // Opening connection CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)_host, _port, &readStream, &writeStream); - + // Configuring input stream reader = objc_retainedObject(readStream); [reader setDelegate:self]; [reader scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; [reader open]; - + // Configuring output stream writer = objc_retainedObject(writeStream); [writer setDelegate:self]; @@ -47,7 +47,7 @@ - (void) close { [writer removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [writer setDelegate:nil]; writer = nil; - + // Closing input stream [reader close]; [reader removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; @@ -60,50 +60,52 @@ - (void) write : (NSString *) data { [writer write : [chunk bytes] maxLength : [chunk length]]; } +- (void) writeBinary : (NSData *) chunk { + [writer write : [chunk bytes] maxLength : [chunk length]]; +} + - (void) stream : (NSStream *) theStream handleEvent : (NSStreamEvent) streamEvent { - switch (streamEvent) { - case NSStreamEventOpenCompleted: - NSLog(@"Stream opened!"); + switch (streamEvent) { + case NSStreamEventOpenCompleted: + NSLog(@"Stream opened!"); connected = YES; - break; - + break; + // Data receiving - case NSStreamEventHasBytesAvailable: + case NSStreamEventHasBytesAvailable: if (theStream == reader) { - uint8_t buffer[10240]; + uint8_t buffer[512]; NSInteger len; - + while ([reader hasBytesAvailable]) { len = [reader read : buffer maxLength : sizeof(buffer)]; - + + NSData *line = [buffer subdataWithRange:NSMakeRange(0, len)]; + if (len > 0) { - - NSString *chunk = [[NSString alloc] initWithBytes : buffer - length : len - encoding : NSASCIIStringEncoding]; - + if (nil != chunk) { - NSLog(@"Received data: %@", chunk); - [_hook sendMessage : _host : _port : chunk]; + NSLog(@"Received data: %@", line); + [_hook sendMessage : _host : _port : line]; } } } } break; - + case NSStreamEventErrorOccurred: NSLog(@"Cannot connect to the host!"); connected = NO; break; - + case NSStreamEventEndEncountered: NSLog(@"Stream closed!"); connected = NO; break; - + default: NSLog(@"Unknown event!"); } } -@end \ No newline at end of file +@end From 0afcb5c270e69551c9c03acfb95aeff44cea6efa Mon Sep 17 00:00:00 2001 From: Kevin Mershon Date: Wed, 13 May 2015 00:31:19 -0700 Subject: [PATCH 08/14] Fixes --- src/ios/CDVSocketPlugin.h | 3 ++- src/ios/CDVSocketPlugin.m | 2 +- src/ios/Connection.h | 4 ++-- src/ios/Connection.m | 8 +++++--- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/ios/CDVSocketPlugin.h b/src/ios/CDVSocketPlugin.h index a9d445b..ff9003a 100644 --- a/src/ios/CDVSocketPlugin.h +++ b/src/ios/CDVSocketPlugin.h @@ -10,7 +10,8 @@ -(void) disconnectAll: (CDVInvokedUrlCommand *) command; -(void) isConnected: (CDVInvokedUrlCommand *) command; -(void) send: (CDVInvokedUrlCommand *) command; +-(void) sendBinary: (CDVInvokedUrlCommand *) command; -(BOOL) disposeConnection :(NSString *)key; -@end \ No newline at end of file +@end diff --git a/src/ios/CDVSocketPlugin.m b/src/ios/CDVSocketPlugin.m index 8339cec..70c0482 100644 --- a/src/ios/CDVSocketPlugin.m +++ b/src/ios/CDVSocketPlugin.m @@ -333,7 +333,7 @@ - (void) send: (CDVInvokedUrlCommand *) command { } - (void) sendMessage :(NSString *)host :(int)port :(NSData *)chunk { - NSString *base64Encoded = [nsdata base64EncodedStringWithOptions:0]; + NSString *base64Encoded = [chunk base64EncodedStringWithOptions:0]; // Relay to webview NSString *receiveHook = [NSString stringWithFormat : @"window.tlantic.plugins.socket.receive('%@', %d, '%@', window.atob('%@') );", diff --git a/src/ios/Connection.h b/src/ios/Connection.h index c37611e..9b5ed6f 100644 --- a/src/ios/Connection.h +++ b/src/ios/Connection.h @@ -1,6 +1,6 @@ @protocol ConnectionDelegate -- (void) sendMessage : (NSString *) host : (int)port : (NSString *) chunk; +- (void) sendMessage : (NSString *) host : (int)port : (NSData *) line; @end @@ -25,4 +25,4 @@ - (void) stream : (NSStream *) theStream handleEvent : (NSStreamEvent) streamEvent; -@end \ No newline at end of file +@end diff --git a/src/ios/Connection.m b/src/ios/Connection.m index a27e909..439ee10 100644 --- a/src/ios/Connection.m +++ b/src/ios/Connection.m @@ -74,17 +74,19 @@ - (void) stream : (NSStream *) theStream handleEvent : (NSStreamEvent) streamEve // Data receiving case NSStreamEventHasBytesAvailable: if (theStream == reader) { - uint8_t buffer[512]; + /* uint8_t buffer[512]; */ + void* buffer = malloc(512); + NSData *sharedData = [[NSData alloc] initWithBytesNoCopy:buffer length:512 freeWhenDone:YES]; NSInteger len; while ([reader hasBytesAvailable]) { len = [reader read : buffer maxLength : sizeof(buffer)]; - NSData *line = [buffer subdataWithRange:NSMakeRange(0, len)]; + NSData *line = [sharedData subdataWithRange:NSMakeRange(0, len)]; if (len > 0) { - if (nil != chunk) { + if (nil != line) { NSLog(@"Received data: %@", line); [_hook sendMessage : _host : _port : line]; } From f1e64bbad0cd71b9369735eba126b8544d07697b Mon Sep 17 00:00:00 2001 From: Kevin Mershon Date: Wed, 13 May 2015 00:43:21 -0700 Subject: [PATCH 09/14] Start implementing sendBinary --- src/ios/CDVSocketPlugin.m | 75 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/ios/CDVSocketPlugin.m b/src/ios/CDVSocketPlugin.m index 70c0482..8f0e0b7 100644 --- a/src/ios/CDVSocketPlugin.m +++ b/src/ios/CDVSocketPlugin.m @@ -332,6 +332,81 @@ - (void) send: (CDVInvokedUrlCommand *) command { } } +- (void) sendBinary: (CDVInvokedUrlCommand *) command { + + // Validating parameters + if ([command.arguments count] < 2) { + // Triggering parameter error + CDVPluginResult* result = [CDVPluginResult + resultWithStatus : CDVCommandStatus_ERROR + messageAsString : @"Missing arguments when calling 'sendBinary' action."]; + + [self.commandDelegate + sendPluginResult : result + callbackId:command.callbackId + ]; + + } else { + + // Running in background to avoid thread locks + [self.commandDelegate runInBackground : ^{ + + CDVPluginResult* result= nil; + Connection* socket = nil; + NSData* data = nil; + NSString* key = nil; + + @try { + // Preparing parameters + key = [command.arguments objectAtIndex : 0]; + + // Getting connection from pool + socket = [pool objectForKey : key]; + + // Checking if socket was not found and his conenctivity + if (socket == nil) { + NSLog(@"Connection not found"); + result = [CDVPluginResult + resultWithStatus : CDVCommandStatus_ERROR + messageAsString : @"No connection found with host."]; + + } else if (![socket isConnected]) { + NSLog(@"Socket is not connected."); + result = [CDVPluginResult + resultWithStatus : CDVCommandStatus_ERROR + messageAsString : @"Invalid connection with host."]; + } else { + // Writting on output stream + data = [command.arguments objectAtIndex : 1]; + + /* void* buffer = malloc(512); */ + /* nsdata *shareddata = [[nsdata alloc] initwithbytesnocopy:buffer length:512 freewhendone:yes]; */ + + + + //NSLog(@"Sending data to %@ - %@", key, data); + + [socket writeBinary:data]; + + // Formatting success response + result = [CDVPluginResult + resultWithStatus : CDVCommandStatus_OK + messageAsString : key]; + } + } + @catch (NSException *exception) { + NSLog(@"Exception: %@", exception); + result = [CDVPluginResult + resultWithStatus : CDVCommandStatus_ERROR + messageAsString : @"Unexpected exception when executon 'sendBinary' action."]; + } + + // Returning callback resolution + [self.commandDelegate sendPluginResult : result callbackId : command.callbackId]; + }]; + } +} + - (void) sendMessage :(NSString *)host :(int)port :(NSData *)chunk { NSString *base64Encoded = [chunk base64EncodedStringWithOptions:0]; From bf1cc0bb9dcc76d50b0bcc374813d21d97c76fe4 Mon Sep 17 00:00:00 2001 From: Kevin Mershon Date: Wed, 13 May 2015 01:10:51 -0700 Subject: [PATCH 10/14] Get binary transfer to compile --- src/ios/CDVSocketPlugin.m | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ios/CDVSocketPlugin.m b/src/ios/CDVSocketPlugin.m index 8f0e0b7..7550bec 100644 --- a/src/ios/CDVSocketPlugin.m +++ b/src/ios/CDVSocketPlugin.m @@ -353,7 +353,7 @@ - (void) sendBinary: (CDVInvokedUrlCommand *) command { CDVPluginResult* result= nil; Connection* socket = nil; - NSData* data = nil; + NSArray* data = nil; NSString* key = nil; @try { @@ -363,7 +363,7 @@ - (void) sendBinary: (CDVInvokedUrlCommand *) command { // Getting connection from pool socket = [pool objectForKey : key]; - // Checking if socket was not found and his conenctivity + // Checking if socket was not found and his connectivity if (socket == nil) { NSLog(@"Connection not found"); result = [CDVPluginResult @@ -379,14 +379,15 @@ - (void) sendBinary: (CDVInvokedUrlCommand *) command { // Writting on output stream data = [command.arguments objectAtIndex : 1]; - /* void* buffer = malloc(512); */ - /* nsdata *shareddata = [[nsdata alloc] initwithbytesnocopy:buffer length:512 freewhendone:yes]; */ - - + NSMutableData *buf = [[NSMutableData alloc] init]; - //NSLog(@"Sending data to %@ - %@", key, data); + for (int i = 0; i < [data count]; i++) + { + int byte = [data[i] intValue]; + [buf appendBytes : &byte length:1]; + } - [socket writeBinary:data]; + [socket writeBinary:buf]; // Formatting success response result = [CDVPluginResult From 9d928cffc5c7223ee486adb0a01b3433023d69ac Mon Sep 17 00:00:00 2001 From: Kevin Mershon Date: Wed, 13 May 2015 01:31:03 -0700 Subject: [PATCH 11/14] Use newer API to eval javascript --- src/ios/CDVSocketPlugin.m | 2 +- src/ios/Connection.m | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ios/CDVSocketPlugin.m b/src/ios/CDVSocketPlugin.m index 7550bec..6693a64 100644 --- a/src/ios/CDVSocketPlugin.m +++ b/src/ios/CDVSocketPlugin.m @@ -415,7 +415,7 @@ - (void) sendMessage :(NSString *)host :(int)port :(NSData *)chunk { NSString *receiveHook = [NSString stringWithFormat : @"window.tlantic.plugins.socket.receive('%@', %d, '%@', window.atob('%@') );", host, port, [self buildKey : host : port], base64Encoded]; - [self writeJavascript:receiveHook]; + [self.commandDelegate evalJs : receiveHook]; } @end diff --git a/src/ios/Connection.m b/src/ios/Connection.m index 439ee10..fca6818 100644 --- a/src/ios/Connection.m +++ b/src/ios/Connection.m @@ -87,7 +87,6 @@ - (void) stream : (NSStream *) theStream handleEvent : (NSStreamEvent) streamEve if (len > 0) { if (nil != line) { - NSLog(@"Received data: %@", line); [_hook sendMessage : _host : _port : line]; } } From 9d5b2497947394b9c89e165f5d40834e1f96dce9 Mon Sep 17 00:00:00 2001 From: Kevin Mershon Date: Wed, 13 May 2015 07:38:28 -0700 Subject: [PATCH 12/14] Fix preexisting typo, delete dead code, add a comment --- src/ios/CDVSocketPlugin.m | 2 +- src/ios/Connection.m | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ios/CDVSocketPlugin.m b/src/ios/CDVSocketPlugin.m index 6693a64..fe32409 100644 --- a/src/ios/CDVSocketPlugin.m +++ b/src/ios/CDVSocketPlugin.m @@ -376,7 +376,7 @@ - (void) sendBinary: (CDVInvokedUrlCommand *) command { resultWithStatus : CDVCommandStatus_ERROR messageAsString : @"Invalid connection with host."]; } else { - // Writting on output stream + // Writing on output stream data = [command.arguments objectAtIndex : 1]; NSMutableData *buf = [[NSMutableData alloc] init]; diff --git a/src/ios/Connection.m b/src/ios/Connection.m index fca6818..5553ef7 100644 --- a/src/ios/Connection.m +++ b/src/ios/Connection.m @@ -74,12 +74,13 @@ - (void) stream : (NSStream *) theStream handleEvent : (NSStreamEvent) streamEve // Data receiving case NSStreamEventHasBytesAvailable: if (theStream == reader) { - /* uint8_t buffer[512]; */ void* buffer = malloc(512); NSData *sharedData = [[NSData alloc] initWithBytesNoCopy:buffer length:512 freeWhenDone:YES]; NSInteger len; while ([reader hasBytesAvailable]) { + // This has a tendency to not fully read the packet, + // requiring subsequent combination of values len = [reader read : buffer maxLength : sizeof(buffer)]; NSData *line = [sharedData subdataWithRange:NSMakeRange(0, len)]; From 4f0254eda34346090a1a8b50977dbe0d16e1acf9 Mon Sep 17 00:00:00 2001 From: Kevin Mershon Date: Wed, 13 May 2015 12:03:06 -0700 Subject: [PATCH 13/14] Finish implementing packet completion --- src/ios/Connection.m | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/ios/Connection.m b/src/ios/Connection.m index 5553ef7..ff655fb 100644 --- a/src/ios/Connection.m +++ b/src/ios/Connection.m @@ -75,21 +75,27 @@ - (void) stream : (NSStream *) theStream handleEvent : (NSStreamEvent) streamEve case NSStreamEventHasBytesAvailable: if (theStream == reader) { void* buffer = malloc(512); - NSData *sharedData = [[NSData alloc] initWithBytesNoCopy:buffer length:512 freeWhenDone:YES]; - NSInteger len; + NSInteger len = 0; + NSMutableData *packet = [[NSMutableData alloc] init]; + NSData *line; + NSInteger totalLength = 0; while ([reader hasBytesAvailable]) { - // This has a tendency to not fully read the packet, + // NSInputStream is notorious for not fully reading a whole TCP packet, // requiring subsequent combination of values len = [reader read : buffer maxLength : sizeof(buffer)]; - NSData *line = [sharedData subdataWithRange:NSMakeRange(0, len)]; + // copy the bytes to the mutable buffer and update the total length + [packet appendBytes : buffer length:len]; + totalLength = totalLength + len; - if (len > 0) { + line = [packet subdataWithRange:NSMakeRange(0, totalLength)]; + } - if (nil != line) { - [_hook sendMessage : _host : _port : line]; - } + // now that no more bytes are available, send the packet + if (len >= 0) { + if (nil != line) { + [_hook sendMessage : _host : _port : line]; } } } From 1706d5919caab4cbb77e212ac5f0fe93e11b50ce Mon Sep 17 00:00:00 2001 From: Kevin Mershon Date: Fri, 3 Jul 2015 14:16:58 -0700 Subject: [PATCH 14/14] Call close on the socket, any time we dispose the connection --- src/ios/CDVSocketPlugin.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ios/CDVSocketPlugin.m b/src/ios/CDVSocketPlugin.m index fe32409..f79601b 100644 --- a/src/ios/CDVSocketPlugin.m +++ b/src/ios/CDVSocketPlugin.m @@ -156,8 +156,10 @@ - (BOOL) disposeConnection : (NSString *) key { if (socket) { [pool removeObjectForKey : key]; - if ([socket isConnected]) - [socket close]; + // Call close on the socket whether it's connected or not, to clean + // up the read/write streams and event handlers so we don't leak + // memory + [socket close]; socket = nil;