diff --git a/README.md b/README.md index 213c808..705d7f3 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,50 @@ sockets-for-cordova =================== Cordova plugin for socket network communication + +## API +### Event handlers +#### `onData: (data: Uint8Array) => void` +Called when new batch of data was send from server to client. Data are represented as typed array of bytes (`Uint8Array`). + +#### `onClose: (hasError: boolean) => void` +Called after connection was successfully closed. Parameter `hasError` indicates whether connection was closed as a result of some error. + +#### `onError: (message: string) => void` +Called when some error occurs on opened connection. + +### Methods +#### `open(host, port, onSuccess?, onError?): void` +Establishes connection with the remote host. + +| parameter | type | description | +| ----------- |-----------------------------|--------------| +| `host` | `string` | Remote host/ip address | +| `port` | `number` | Tcp port number | +| `onSuccess` | `() => void` | Success callback, called after successfull connection to the remote host. (optional)| +| `onError` | `(message: string) => void` | Error callback, called when some error occurs during connecting to the remote host. (optional)| + +#### `write(data, onSuccess?, onError?): void` +Sends data to the remote host. + +| parameter | type | description | +| ----------- |-----------------------------|--------------| +| `data` | `Uint8Array` | Typed array of bytes to be written to the output stream | +| `onSuccess` | `() => void` | Success callback, called after data are successfully written to the output stream. (optional)| +| `onError` | `(message: string) => void` | Error callback, called when some error occurs during writing of data to the output stream. (optional)| + +#### `shutdownWrite(onSuccess?, onError?): void` +Finishes sending of data by sending `FIN` to remote host. If you call `write` after invoking `shutdownWrite`, callback `onError` (of `write` method) will be called. + +| parameter | type | description | +| ----------- |-----------------------------|--------------| +| `onSuccess` | `() => void` | Success callback, called after sending of all data is finished. (optional)| +| `onError` | `(message: string) => void` | Error callback, called when some error occurs during this procedure. (optional)| + +#### `close(onSuccess?, onError?): void` +Closes the connection. `onClose` event handler is called when closing of socket was succesfull. + +| parameter | type | description | +| ----------- |-----------------------------|--------------| +| `onSuccess` | `() => void` | Success callback, called after connection was successfully closed. `onClose` event handler is called before that callback. (optional)| +| `onError` | `(message: string) => void` | Error callback, called when some error occurs during this procedure. (optional)| diff --git a/socket.js b/socket.js index 43a33d5..7b23569 100644 --- a/socket.js +++ b/socket.js @@ -10,31 +10,30 @@ function Socket() { this.socketKey = guid(); } -Socket.create = function(callback) { - - var socket = new Socket(); +Socket.prototype.open = function (host, port, success, error) { + var _that = this; function socketEventHandler(event) { var payload = event.payload; - if (payload.socketKey !== socket.socketKey) { + if (payload.socketKey !== _that.socketKey) { return; } switch(payload.type) { case "Close": - console.debug("SocketsForCordova: Close event, socket key: " + payload.socketKey); + //console.debug("SocketsForCordova: Close event, socket key: " + payload.socketKey); window.document.removeEventListener(SOCKET_EVENT, socketEventHandler); - socket.onClose(); + _that.onClose(); break; case "DataReceived": - console.debug("SocketsForCordova: DataReceived event, socket key: " + payload.socketKey); - socket.onData(new Int8Array(payload.data)); + //console.debug("SocketsForCordova: DataReceived event, socket key: " + payload.socketKey); + _that.onData(new Uint8Array(payload.data)); break; case "Error": - console.debug("SocketsForCordova: Error event, socket key: " + payload.socketKey); - socket.onError(payload.errorMessage); + //console.debug("SocketsForCordova: Error event, socket key: " + payload.socketKey); + _that.onError(payload.errorMessage); break; default: console.error("SocketsForCordova: Unknown event type " + payload.type + ", socket key: " + payload.socketKey); @@ -42,52 +41,37 @@ Socket.create = function(callback) { } } - window.document.addEventListener(SOCKET_EVENT, socketEventHandler); - exec( function() { - console.debug("SocketsForCordova: Socket object successfully constructed."); - callback(socket); - }, - function(error) { - console.error("SocketsForCordova: Unexpected error during constructing Socket object. Error: " + error); - }, - CORDOVA_SERVICE_NAME, - "create", - [ socket.socketKey ]); -}; - -Socket.prototype.connect = function (host, port, success, error) { - exec( - function() { - console.debug("SocketsForCordova: Socket successfully connected."); + //console.debug("SocketsForCordova: Socket successfully opened."); + window.document.addEventListener(SOCKET_EVENT, socketEventHandler); if (success) success(); }, function(errorMessage) { - console.error("SocketsForCordova: Error during socket connecting. Error: " + errorMessage); + //console.error("SocketsForCordova: Error during opening socket. Error: " + errorMessage); if (error) error(errorMessage); }, CORDOVA_SERVICE_NAME, - "connect", + "open", [ this.socketKey, host, port ]); }; Socket.prototype.write = function (data, success, error) { - var dataToWrite = data instanceof Int8Array + var dataToWrite = data instanceof Uint8Array ? Socket._copyToArray(data) : data; exec( function() { - console.debug("SocketsForCordova: Data successfully written to socket. Number of bytes: " + data.length); + //console.debug("SocketsForCordova: Data successfully written to socket. Number of bytes: " + data.length); if (success) success(); }, function(errorMessage) { - console.error("SocketsForCordova: Error during writing data to socket. Error: " + errorMessage); + //console.error("SocketsForCordova: Error during writing data to socket. Error: " + errorMessage); if (error) error(errorMessage); }, @@ -104,13 +88,17 @@ Socket._copyToArray = function(array) { return outputArray; }; -Socket.prototype.shutdownWrite = function () { +Socket.prototype.shutdownWrite = function (success, error) { exec( function() { - console.debug("SocketsForCordova: Shutdown write successfully called."); + //console.debug("SocketsForCordova: Shutdown write successfully called."); + if (success) + success(); }, function(errorMessage) { - console.error("SocketsForCordova: Error when call shutdownWrite on socket. Error: " + errorMessage); + //console.error("SocketsForCordova: Error when call shutdownWrite on socket. Error: " + errorMessage); + if (error) + error(errorMessage); }, CORDOVA_SERVICE_NAME, "shutdownWrite", @@ -120,10 +108,14 @@ Socket.prototype.shutdownWrite = function () { Socket.prototype.close = function () { exec( function() { - console.debug("SocketsForCordova: Close successfully called."); + //console.debug("SocketsForCordova: Close successfully called."); + if (success) + success(); }, function(errorMessage) { - console.error("SocketsForCordova: Error when call close on socket. Error: " + errorMessage); + //console.error("SocketsForCordova: Error when call close on socket. Error: " + errorMessage); + if (error) + error(errorMessage); }, CORDOVA_SERVICE_NAME, "close", diff --git a/src/android/src/cz/blocshop/socketsforcordova/SocketAdapter.java b/src/android/src/cz/blocshop/socketsforcordova/SocketAdapter.java index 8eb6c70..4e29b6b 100644 --- a/src/android/src/cz/blocshop/socketsforcordova/SocketAdapter.java +++ b/src/android/src/cz/blocshop/socketsforcordova/SocketAdapter.java @@ -5,11 +5,12 @@ import java.net.SocketException; public interface SocketAdapter { - public void connect(String host, int port) throws Throwable; + public void open(String host, int port) throws Throwable; public void write(byte[] data) throws IOException; + public void shutdownWrite() throws IOException; public void close() throws IOException; public void setOptions(SocketAdapterOptions options) throws SocketException; public void setDataConsumer(Consumer dataConsumer); public void setCloseEventHandler(Consumer closeEventHandler); - public void setErrorHandler(Consumer errorHandler); + public void setErrorHandler(Consumer errorHandler); } \ No newline at end of file diff --git a/src/android/src/cz/blocshop/socketsforcordova/SocketAdapterImpl.java b/src/android/src/cz/blocshop/socketsforcordova/SocketAdapterImpl.java index 008dae8..bc11d3f 100644 --- a/src/android/src/cz/blocshop/socketsforcordova/SocketAdapterImpl.java +++ b/src/android/src/cz/blocshop/socketsforcordova/SocketAdapterImpl.java @@ -1,7 +1,6 @@ package cz.blocshop.socketsforcordova; import java.io.IOException; -import java.io.InvalidObjectException; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; @@ -19,7 +18,7 @@ public class SocketAdapterImpl implements SocketAdapter { private Consumer dataConsumer; private Consumer closeEventHandler; - private Consumer exceptionHandler; + private Consumer exceptionHandler; private ExecutorService executor; @@ -29,8 +28,8 @@ public class SocketAdapterImpl implements SocketAdapter { } @Override - public void connect(String host, int port) throws Throwable { - this.connectSocket(host, port); + public void open(String host, int port) throws Throwable { + this.openWithBackgroundThread(host, port); this.submitReadTask(); } @@ -39,11 +38,15 @@ public class SocketAdapterImpl implements SocketAdapter { this.socket.getOutputStream().write(data); } + @Override + public void shutdownWrite() throws IOException { + this.socket.shutdownOutput(); + } + @Override public void close() throws IOException { - if (!this.socket.isClosed()) { - this.socket.shutdownOutput(); - } + this.invokeCloseEventHandler(false); + this.socket.close(); } @Override @@ -82,24 +85,25 @@ public class SocketAdapterImpl implements SocketAdapter { } @Override - public void setErrorHandler(Consumer exceptionHandler) { + public void setErrorHandler(Consumer exceptionHandler) { this.exceptionHandler = exceptionHandler; } - private void connectSocket(final String host, final int port) throws Throwable { - Future future = this.executor.submit(new Runnable() { + private void openWithBackgroundThread(final String host, final int port) throws Throwable { + Future future = this.executor.submit(new Runnable() { @Override public void run() { try { socket.connect(new InetSocketAddress(host, port)); } catch (IOException e) { - Logging.Error(SocketAdapterImpl.class.getName(), "Error during connecting of socket", e); + Logging.Error(SocketAdapterImpl.class.getName(), "Error during connecting of socket", e.getCause()); + throw new RuntimeException(e); } } }); try { - Object result = future.get(); + future.get(); } catch (ExecutionException e) { throw e.getCause(); @@ -122,7 +126,7 @@ public class SocketAdapterImpl implements SocketAdapter { } catch (Throwable e) { Logging.Error(SocketAdapterImpl.class.getName(), "Error during reading of socket input stream", e); hasError = true; - invokeExceptionHandler(e); + invokeExceptionHandler(e.getMessage()); } finally { try { socket.close(); @@ -159,9 +163,9 @@ public class SocketAdapterImpl implements SocketAdapter { } } - private void invokeExceptionHandler(Throwable exception) { + private void invokeExceptionHandler(String errorMessage) { if (this.exceptionHandler != null) { - this.exceptionHandler.accept(exception); + this.exceptionHandler.accept(errorMessage); } } } diff --git a/src/android/src/cz/blocshop/socketsforcordova/SocketPlugin.java b/src/android/src/cz/blocshop/socketsforcordova/SocketPlugin.java index 862892e..369db58 100644 --- a/src/android/src/cz/blocshop/socketsforcordova/SocketPlugin.java +++ b/src/android/src/cz/blocshop/socketsforcordova/SocketPlugin.java @@ -1,13 +1,11 @@ package cz.blocshop.socketsforcordova; -import android.annotation.SuppressLint; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; + import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaArgs; import org.apache.cordova.CordovaPlugin; @@ -15,6 +13,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import android.annotation.SuppressLint; public class SocketPlugin extends CordovaPlugin { @@ -23,12 +22,12 @@ public class SocketPlugin extends CordovaPlugin { @Override public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) throws JSONException { - if(action.equals("create")) { - this.create(args, callbackContext); - } else if (action.equals("connect")) { - this.connect(args, callbackContext); + if (action.equals("open")) { + this.open(args, callbackContext); } else if (action.equals("write")) { this.write(args, callbackContext); + } else if (action.equals("shutdownWrite")) { + this.shutdownWrite(args, callbackContext); } else if (action.equals("close")) { this.close(args, callbackContext); } else if (action.equals("setOptions")) { @@ -40,29 +39,19 @@ public class SocketPlugin extends CordovaPlugin { return true; } - private void create(CordovaArgs args, CallbackContext callbackContext) throws JSONException { - - final String socketKey = args.getString(0); + private void open(CordovaArgs args, CallbackContext callbackContext) throws JSONException { + String socketKey = args.getString(0); + String host = args.getString(1); + int port = args.getInt(2); SocketAdapter socketAdapter = new SocketAdapterImpl(); socketAdapter.setCloseEventHandler(new CloseEventHandler(socketKey)); socketAdapter.setDataConsumer(new DataConsumer(socketKey)); socketAdapter.setErrorHandler(new ErrorHandler(socketKey)); - - this.socketAdapters.put(socketKey, socketAdapter); - - callbackContext.success(socketKey); - } - - private void connect(CordovaArgs args, CallbackContext callbackContext) throws JSONException { - String socketKey = args.getString(0); - String host = args.getString(1); - int port = args.getInt(2); - - SocketAdapter socket = this.getSocketAdapter(socketKey); try { - socket.connect(host, port); + socketAdapter.open(host, port); + this.socketAdapters.put(socketKey, socketAdapter); callbackContext.success(); } catch (Throwable t) { callbackContext.error(t.toString()); @@ -87,6 +76,19 @@ public class SocketPlugin extends CordovaPlugin { callbackContext.error(e.toString()); } } + + private void shutdownWrite(CordovaArgs args, CallbackContext callbackContext) throws JSONException { + String socketKey = args.getString(0); + + SocketAdapter socket = this.getSocketAdapter(socketKey); + + try { + socket.shutdownWrite(); + callbackContext.success(); + } catch (IOException e) { + callbackContext.error(e.toString()); + } + } private void close(CordovaArgs args, CallbackContext callbackContext) throws JSONException { String socketKey = args.getString(0); @@ -135,8 +137,7 @@ public class SocketPlugin extends CordovaPlugin { private SocketAdapter getSocketAdapter(String socketKey) { if (!this.socketAdapters.containsKey(socketKey)) { - throw new IllegalArgumentException( - String.format("Cannot find socketKey: %s. Connection is probably closed.", socketKey)); + throw new IllegalStateException("Socket isn't connected."); } return this.socketAdapters.get(socketKey); } @@ -197,17 +198,17 @@ public class SocketPlugin extends CordovaPlugin { } } - private class ErrorHandler implements Consumer { + private class ErrorHandler implements Consumer { private String socketKey; public ErrorHandler(String socketKey) { this.socketKey = socketKey; } @Override - public void accept(Throwable exception) { + public void accept(String errorMessage) { try { JSONObject event = new JSONObject(); event.put("type", "Error"); - event.put("errorMessage", exception.toString()); + event.put("errorMessage", errorMessage); event.put("socketKey", socketKey); dispatchEvent(event); diff --git a/src/ios/SocketsForCordova/Classes/SocketAdapter.m b/src/ios/SocketsForCordova/Classes/SocketAdapter.m index 761bd06..dd29830 100644 --- a/src/ios/SocketsForCordova/Classes/SocketAdapter.m +++ b/src/ios/SocketsForCordova/Classes/SocketAdapter.m @@ -37,7 +37,6 @@ BOOL wasOpenned = FALSE; [inputStream open]; outputStream = (__bridge NSOutputStream *)writeStream; - //[outputStream setDelegate:self]; [outputStream open]; [self performSelectorOnMainThread:@selector(runReadLoop) withObject:nil waitUntilDone:NO]; @@ -125,13 +124,11 @@ BOOL wasOpenned = FALSE; case NSStreamEventHasBytesAvailable: { if(stream == inputStream) { uint8_t buf[65535]; - unsigned int len = 0; - - len = [inputStream read:buf maxLength:65535]; - NSLog(@"%d", len); + long len = [inputStream read:buf maxLength:65535]; + if(len > 0) { NSMutableArray *dataArray = [[NSMutableArray alloc] init]; - for (int i = 0; i < len; i++) { + for (long i = 0; i < len; i++) { [dataArray addObject:[NSNumber numberWithUnsignedChar:buf[i]]]; } @@ -184,6 +181,7 @@ BOOL wasOpenned = FALSE; } - (void)close { + self.closeEventHandler(FALSE); [self closeOutputStream]; [self closeInputStream]; } diff --git a/src/ios/SocketsForCordova/Classes/SocketPlugin.h b/src/ios/SocketsForCordova/Classes/SocketPlugin.h index 4bec071..7c1a02c 100644 --- a/src/ios/SocketsForCordova/Classes/SocketPlugin.h +++ b/src/ios/SocketsForCordova/Classes/SocketPlugin.h @@ -4,7 +4,6 @@ NSMutableDictionary *socketAdapters; } --(void) create: (CDVInvokedUrlCommand *) command; -(void) connect: (CDVInvokedUrlCommand *) command; -(void) write: (CDVInvokedUrlCommand *) command; -(void) close: (CDVInvokedUrlCommand *) command; diff --git a/src/ios/SocketsForCordova/Classes/SocketPlugin.m b/src/ios/SocketsForCordova/Classes/SocketPlugin.m index 20b254c..c24cd97 100644 --- a/src/ios/SocketsForCordova/Classes/SocketPlugin.m +++ b/src/ios/SocketsForCordova/Classes/SocketPlugin.m @@ -5,15 +5,6 @@ @implementation SocketPlugin : CDVPlugin -- (void) create : (CDVInvokedUrlCommand*) command { - //NSString* socketKey = [command.arguments objectAtIndex : 0]; - - - [self.commandDelegate - sendPluginResult: [CDVPluginResult resultWithStatus : CDVCommandStatus_OK] - callbackId: command.callbackId]; -} - - (void) connect : (CDVInvokedUrlCommand*) command { NSString *socketKey = [command.arguments objectAtIndex:0]; diff --git a/src/wp8/src/SocketAdapter.cs b/src/wp8/src/SocketAdapter.cs index 83892ea..f71126e 100644 --- a/src/wp8/src/SocketAdapter.cs +++ b/src/wp8/src/SocketAdapter.cs @@ -12,6 +12,7 @@ namespace Blocshop.ScoketsForCordova { Task Connect(String host, int port); Task Write(byte[] data); + void ShutdownWrite(); void Close(); SocketAdapterOptions Options { set; } Action DataConsumer { set; } @@ -55,11 +56,17 @@ namespace Blocshop.ScoketsForCordova await this.socket.SendTaskAsync(socketAsyncEventArgs); } - public void Close() + public void ShutdownWrite() { this.socket.Shutdown(SocketShutdown.Send); } + public void Close() + { + this.CloseEventHandler(false); + this.socket.Close(); + } + private void StartReadTask() { Task.Factory.StartNew(() => this.RunRead()); diff --git a/src/wp8/src/SocketPlugin.cs b/src/wp8/src/SocketPlugin.cs index cced065..c85ab4f 100644 --- a/src/wp8/src/SocketPlugin.cs +++ b/src/wp8/src/SocketPlugin.cs @@ -21,54 +21,36 @@ namespace Blocshop.ScoketsForCordova this.socketStorage = SocketStorage.CreateSocketStorage(); } - public void create(string parameters) - { - string socketKey = JsonHelper.Deserialize(parameters)[0]; - - ISocketAdapter socketAdapter = new SocketAdapter(); - socketAdapter.CloseEventHandler = (hasError) => this.CloseEventHandler(socketKey, hasError); - socketAdapter.DataConsumer = (data) => this.DataConsumer(socketKey, data); - socketAdapter.ErrorHandler = (ex) => this.ErrorHandler(socketKey, ex); - - this.socketStorage.Add(socketKey, socketAdapter); - - this.DispatchCommandResult(new PluginResult(PluginResult.Status.OK)); - } - - public void registerWPEventDispatcher(string parameters) - { - this.eventDispatcherCallbackId = this.CurrentCommandCallbackId; - PluginResult result = new PluginResult(PluginResult.Status.OK); - result.KeepCallback = true; - DispatchCommandResult(result, this.eventDispatcherCallbackId); - } - public void connect(string parameters) { string socketKey = JsonHelper.Deserialize(parameters)[0]; string host = JsonHelper.Deserialize(parameters)[1]; int port = int.Parse(JsonHelper.Deserialize(parameters)[2]); - ISocketAdapter socket = this.socketStorage.Get(socketKey); + ISocketAdapter socketAdapter = new SocketAdapter(); + socketAdapter.CloseEventHandler = (hasError) => this.CloseEventHandler(socketKey, hasError); + socketAdapter.DataConsumer = (data) => this.DataConsumer(socketKey, data); + socketAdapter.ErrorHandler = (ex) => this.ErrorHandler(socketKey, ex); + try { - socket.Connect(host, port).Wait(); + socketAdapter.Connect(host, port).Wait(); + + this.socketStorage.Add(socketKey, socketAdapter); this.DispatchCommandResult(new PluginResult(PluginResult.Status.OK)); } catch (SocketException ex) { this.DispatchCommandResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, ex.Message)); - socketStorage.Remove(socketKey); } catch (AggregateException ex) { this.DispatchCommandResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, ex.InnerException.Message)); - socketStorage.Remove(socketKey); } } - public void write(string parameters/*, string socketKey, byte[] data*/) + public void write(string parameters) { string socketKey = JsonHelper.Deserialize(parameters)[0]; string dataJsonArray = JsonHelper.Deserialize(parameters)[1]; @@ -87,6 +69,15 @@ namespace Blocshop.ScoketsForCordova } } + public void shutdownWrite(string parameters) + { + string socketKey = JsonHelper.Deserialize(parameters)[0]; + + ISocketAdapter socket = this.socketStorage.Get(socketKey); + + socket.ShutdownWrite(); + } + public void close(string parameters) { string socketKey = JsonHelper.Deserialize(parameters)[0]; @@ -96,6 +87,14 @@ namespace Blocshop.ScoketsForCordova socket.Close(); } + public void registerWPEventDispatcher(string parameters) + { + this.eventDispatcherCallbackId = this.CurrentCommandCallbackId; + PluginResult result = new PluginResult(PluginResult.Status.OK); + result.KeepCallback = true; + DispatchCommandResult(result, this.eventDispatcherCallbackId); + } + //private void setOptions(CordovaArgs args, CallbackContext callbackContext) throws JSONException { // String socketKey = args.getString(0);