diff --git a/plugin.xml b/plugin.xml
new file mode 100644
index 0000000..a06dfcc
--- /dev/null
+++ b/plugin.xml
@@ -0,0 +1,66 @@
+
+
+
+ SocketsForCordova
+ Cordova plugin for socket communication
+ Apache 2.0
+ cordova,sockets,socket
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/socket.js b/socket.js
new file mode 100644
index 0000000..47513be
--- /dev/null
+++ b/socket.js
@@ -0,0 +1,154 @@
+var exec = require('cordova/exec');
+
+var SOCKET_EVENT = "SOCKET_EVENT";
+var CORDOVA_SERVICE_NAME = "SocketsForCordova";
+
+function Socket() {
+ this.onData = null;
+ this.onClose = null;
+ this.onError = null;
+ this.socketKey = guid();
+}
+
+Socket.create = function(callback) {
+
+ var socket = new Socket();
+
+ function socketEventHandler(event) {
+
+ var payload = event.payload;
+
+ if (payload.socketKey !== socket.socketKey) {
+ return;
+ }
+
+ switch(payload.type) {
+ case "Close":
+ console.debug("SocketsForCordova: Close event, socket key: " + payload.socketKey);
+ window.document.removeEventListener(SOCKET_EVENT, socketEventHandler);
+ socket.onClose();
+ break;
+ case "DataReceived":
+ console.debug("SocketsForCordova: DataReceived event, socket key: " + payload.socketKey);
+ socket.onData(new Int8Array(payload.data));
+ break;
+ case "Error":
+ console.debug("SocketsForCordova: Error event, socket key: " + payload.socketKey);
+ socket.onError(payload.errorMessage);
+ break;
+ default:
+ console.error("SocketsForCordova: Unknown event type " + payload.type + ", socket key: " + payload.socketKey);
+ break;
+ }
+ }
+
+ 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.");
+ if (success)
+ success();
+ },
+ function(errorMessage) {
+ console.error("SocketsForCordova: Error during socket connecting. Error: " + errorMessage);
+ if (error)
+ error(errorMessage);
+ },
+ CORDOVA_SERVICE_NAME,
+ "connect",
+ [ this.socketKey, host, port ]);
+};
+
+Socket.prototype.write = function (data, success, error) {
+
+ var dataToWrite = data instanceof Int8Array
+ ? Socket._copyToArray(data)
+ : data;
+
+ exec(
+ function() {
+ 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);
+ if (error)
+ error(errorMessage);
+ },
+ CORDOVA_SERVICE_NAME,
+ "write",
+ [ this.socketKey, dataToWrite ]);
+};
+
+Socket._copyToArray = function(array) {
+ var outputArray = new Array(array.length);
+ for (var i = 0; i < array.length; i++) {
+ outputArray[i] = array[i];
+ }
+ return outputArray;
+};
+
+Socket.prototype.close = function () {
+ exec(
+ function() {
+ console.debug("SocketsForCordova: Close successfully closed.");
+ },
+ function(errorMessage) {
+ console.error("SocketsForCordova: Error when call close on socket. Error: " + errorMessage);
+ },
+ CORDOVA_SERVICE_NAME,
+ "close",
+ [ this.socketKey ]);
+};
+
+Socket.dispatchEvent = function(event) {
+ var eventReceive = document.createEvent('Events');
+ eventReceive.initEvent(SOCKET_EVENT, true, true);
+ eventReceive.payload = event;
+
+ document.dispatchEvent(eventReceive);
+};
+
+var guid = (function() {
+ function s4() {
+ return Math.floor((1 + Math.random()) * 0x10000)
+ .toString(16)
+ .substring(1);
+ }
+ return function() {
+ return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
+ s4() + '-' + s4() + s4() + s4();
+ };
+})();
+
+// Register event dispatcher for Windows Phone
+if (navigator.userAgent.match(/iemobile/i)) {
+ window.document.addEventListener("deviceready", function() {
+ exec(
+ Socket.dispatchEvent,
+ function(errorMessage) {
+ console.error("SocketsForCordova: Cannot register WP event dispatcher, Error: " + errorMessage);
+ },
+ CORDOVA_SERVICE_NAME,
+ "registerWPEventDispatcher",
+ [ ]);
+ });
+}
+
+module.exports = Socket;
diff --git a/src/android/src/cz/blocshop/socketsforcordova/Consumer.java b/src/android/src/cz/blocshop/socketsforcordova/Consumer.java
new file mode 100644
index 0000000..ee38fa4
--- /dev/null
+++ b/src/android/src/cz/blocshop/socketsforcordova/Consumer.java
@@ -0,0 +1,5 @@
+package cz.blocshop.socketsforcordova;
+
+public interface Consumer {
+ void accept(T t);
+}
\ No newline at end of file
diff --git a/src/android/src/cz/blocshop/socketsforcordova/Logging.java b/src/android/src/cz/blocshop/socketsforcordova/Logging.java
new file mode 100644
index 0000000..f807fe1
--- /dev/null
+++ b/src/android/src/cz/blocshop/socketsforcordova/Logging.java
@@ -0,0 +1,10 @@
+package cz.blocshop.socketsforcordova;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class Logging {
+ public static void Error(String klass, String message, Throwable t) {
+ Logger.getLogger(klass).log(Level.SEVERE, message, t);
+ }
+}
diff --git a/src/android/src/cz/blocshop/socketsforcordova/SocketAdapter.java b/src/android/src/cz/blocshop/socketsforcordova/SocketAdapter.java
new file mode 100644
index 0000000..ee131d5
--- /dev/null
+++ b/src/android/src/cz/blocshop/socketsforcordova/SocketAdapter.java
@@ -0,0 +1,15 @@
+package cz.blocshop.socketsforcordova;
+
+import java.io.IOException;
+import java.net.SocketException;
+
+
+public interface SocketAdapter {
+ public void connect(String host, int port) throws IOException;
+ public void write(byte[] data) 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);
+}
\ 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
new file mode 100644
index 0000000..4a565a7
--- /dev/null
+++ b/src/android/src/cz/blocshop/socketsforcordova/SocketAdapterImpl.java
@@ -0,0 +1,142 @@
+package cz.blocshop.socketsforcordova;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.Arrays;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+
+public class SocketAdapterImpl implements SocketAdapter {
+
+ private final int INPUT_STREAM_BUFFER_SIZE = 16 * 1024;
+ private final Socket socket;
+
+ private Consumer dataConsumer;
+ private Consumer closeEventHandler;
+ private Consumer exceptionHandler;
+
+ public SocketAdapterImpl() {
+ this.socket = new Socket();
+ }
+
+ @Override
+ public void connect(String host, int port) throws IOException {
+ this.socket.connect(new InetSocketAddress(host, port));
+ this.submitReadTask();
+ }
+
+ @Override
+ public void write(byte[] data) throws IOException {
+ this.socket.getOutputStream().write(data);
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (!this.socket.isClosed()) {
+ this.socket.shutdownOutput();
+ }
+ }
+
+ @Override
+ public void setOptions(SocketAdapterOptions options) throws SocketException {
+ if (options.getKeepAlive() != null) {
+ this.socket.setKeepAlive(options.getKeepAlive());
+ }
+ if (options.getOobInline() != null) {
+ this.socket.setOOBInline(options.getOobInline());
+ }
+ if (options.getSoLinger() != null) {
+ this.socket.setSoLinger(true, options.getSoLinger());
+ }
+ if (options.getSoTimeout() != null) {
+ this.socket.setSoTimeout(options.getSoTimeout());
+ }
+ if (options.getReceiveBufferSize() != null) {
+ this.socket.setReceiveBufferSize(options.getReceiveBufferSize());
+ }
+ if (options.getSendBufferSize() != null) {
+ this.socket.setSendBufferSize(options.getSendBufferSize());
+ }
+ if (options.getTrafficClass() != null) {
+ this.socket.setTrafficClass(options.getTrafficClass());
+ }
+ }
+
+ @Override
+ public void setDataConsumer(Consumer dataConsumer) {
+ this.dataConsumer = dataConsumer;
+ }
+
+ @Override
+ public void setCloseEventHandler(Consumer closeEventHandler) {
+ this.closeEventHandler = closeEventHandler;
+ }
+
+ @Override
+ public void setErrorHandler(Consumer exceptionHandler) {
+ this.exceptionHandler = exceptionHandler;
+ }
+
+ private void submitReadTask() {
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ runRead();
+ }
+ });
+ }
+
+ private void runRead() {
+ boolean hasError = false;
+ try {
+ runReadLoop();
+ } catch (IOException e) {
+ Logging.Error(SocketAdapterImpl.class.getName(), "Error during reading of socket input stream", e);
+ hasError = true;
+ invokeExceptionHandler(e);
+ } finally {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ Logging.Error(SocketAdapterImpl.class.getName(), "Error during closing of socket", e);
+ } finally {
+ invokeCloseEventHandler(hasError);
+ }
+ }
+ }
+
+ private void runReadLoop() throws IOException {
+ byte[] buffer = new byte[INPUT_STREAM_BUFFER_SIZE];
+ int bytesRead = 0;
+
+ while ((bytesRead = socket.getInputStream().read(buffer)) >= 0) {
+ byte[] data = buffer.length == bytesRead
+ ? buffer
+ : Arrays.copyOfRange(buffer, 0, bytesRead);
+
+ this.invokeDataConsumer(data);
+ }
+ }
+
+ private void invokeDataConsumer(byte[] data) {
+ if (this.dataConsumer != null) {
+ this.dataConsumer.accept(data);
+ }
+ }
+
+ private void invokeCloseEventHandler(boolean hasError) {
+ if (this.closeEventHandler != null) {
+ this.closeEventHandler.accept(hasError);
+ }
+ }
+
+ private void invokeExceptionHandler(IOException exception) {
+ if (this.exceptionHandler != null) {
+ this.exceptionHandler.accept(exception);
+ }
+ }
+}
diff --git a/src/android/src/cz/blocshop/socketsforcordova/SocketAdapterOptions.java b/src/android/src/cz/blocshop/socketsforcordova/SocketAdapterOptions.java
new file mode 100644
index 0000000..ec0d67f
--- /dev/null
+++ b/src/android/src/cz/blocshop/socketsforcordova/SocketAdapterOptions.java
@@ -0,0 +1,110 @@
+package cz.blocshop.socketsforcordova;
+
+public class SocketAdapterOptions {
+
+ private Boolean keepAlive;
+ private Boolean oobInline;
+ private Integer soLinger;
+ private Integer soTimeout;
+ private Integer receiveBufferSize;
+ private Integer sendBufferSize;
+ private Integer trafficClass;
+
+ /**
+ * @return the keepAlive
+ */
+ public Boolean getKeepAlive() {
+ return keepAlive;
+ }
+
+ /**
+ * @param keepAlive the keepAlive to set
+ */
+ public void setKeepAlive(Boolean keepAlive) {
+ this.keepAlive = keepAlive;
+ }
+
+ /**
+ * @return the oobInline
+ */
+ public Boolean getOobInline() {
+ return oobInline;
+ }
+
+ /**
+ * @param oobInline the oobInline to set
+ */
+ public void setOobInline(Boolean oobInline) {
+ this.oobInline = oobInline;
+ }
+
+ /**
+ * @return the soLinger
+ */
+ public Integer getSoLinger() {
+ return soLinger;
+ }
+
+ /**
+ * @param soLinger the soLinger to set
+ */
+ public void setSoLinger(Integer soLinger) {
+ this.soLinger = soLinger;
+ }
+
+ /**
+ * @return the soTimeout
+ */
+ public Integer getSoTimeout() {
+ return soTimeout;
+ }
+
+ /**
+ * @param soTimeout the soTimeout to set
+ */
+ public void setSoTimeout(Integer soTimeout) {
+ this.soTimeout = soTimeout;
+ }
+
+ /**
+ * @return the receiveBufferSize
+ */
+ public Integer getReceiveBufferSize() {
+ return receiveBufferSize;
+ }
+
+ /**
+ * @param receiveBufferSize the receiveBufferSize to set
+ */
+ public void setReceiveBufferSize(Integer receiveBufferSize) {
+ this.receiveBufferSize = receiveBufferSize;
+ }
+
+ /**
+ * @return the sendBufferSize
+ */
+ public Integer getSendBufferSize() {
+ return sendBufferSize;
+ }
+
+ /**
+ * @param sendBufferSize the sendBufferSize to set
+ */
+ public void setSendBufferSize(Integer sendBufferSize) {
+ this.sendBufferSize = sendBufferSize;
+ }
+
+ /**
+ * @return the trafficClass
+ */
+ public Integer getTrafficClass() {
+ return trafficClass;
+ }
+
+ /**
+ * @param trafficClass the trafficClass to set
+ */
+ public void setTrafficClass(Integer trafficClass) {
+ this.trafficClass = trafficClass;
+ }
+}
diff --git a/src/android/src/cz/blocshop/socketsforcordova/SocketPlugin.java b/src/android/src/cz/blocshop/socketsforcordova/SocketPlugin.java
new file mode 100644
index 0000000..9f8f531
--- /dev/null
+++ b/src/android/src/cz/blocshop/socketsforcordova/SocketPlugin.java
@@ -0,0 +1,206 @@
+package cz.blocshop.socketsforcordova;
+
+import android.annotation.SuppressLint;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaArgs;
+import org.apache.cordova.CordovaPlugin;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+
+public class SocketPlugin extends CordovaPlugin {
+
+ Map socketAdapters = new HashMap();
+
+ @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);
+ } else if (action.equals("write")) {
+ this.write(args, callbackContext);
+ } else if (action.equals("close")) {
+ this.close(args, callbackContext);
+ } else if (action.equals("setOptions")) {
+ this.setOptions(args, callbackContext);
+ } else {
+ callbackContext.error(String.format("SocketPlugin - invalid action:", action));
+ return false;
+ }
+ return true;
+ }
+
+ private void create(CordovaArgs args, CallbackContext callbackContext) throws JSONException {
+
+ final String socketKey = args.getString(0);
+
+ 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);
+ callbackContext.success();
+ } catch (IOException e) {
+ callbackContext.error(e.toString());
+ }
+ }
+
+ private void write(CordovaArgs args, CallbackContext callbackContext) throws JSONException {
+ String socketKey = args.getString(0);
+ JSONArray data = args.getJSONArray(1);
+
+ byte[] dataBuffer = new byte[data.length()];
+ for(int i = 0; i < dataBuffer.length; i++) {
+ dataBuffer[i] = (byte) data.getInt(i);
+ }
+
+ SocketAdapter socket = this.getSocketAdapter(socketKey);
+
+ try {
+ socket.write(dataBuffer);
+ callbackContext.success();
+ } catch (IOException e) {
+ callbackContext.error(e.toString());
+ }
+ }
+
+ private void close(CordovaArgs args, CallbackContext callbackContext) throws JSONException {
+ String socketKey = args.getString(0);
+
+ SocketAdapter socket = this.getSocketAdapter(socketKey);
+
+ try {
+ socket.close();
+ callbackContext.success();
+ } catch (IOException e) {
+ callbackContext.error(e.toString());
+ }
+ }
+
+ private void setOptions(CordovaArgs args, CallbackContext callbackContext) throws JSONException {
+
+ String socketKey = args.getString(0);
+ JSONObject optionsJSON = args.getJSONObject(1);
+
+ SocketAdapter socket = this.getSocketAdapter(socketKey);
+
+ SocketAdapterOptions options = new SocketAdapterOptions();
+ options.setKeepAlive(getBooleanPropertyFromJSON(optionsJSON, "keepAlive"));
+ options.setOobInline(getBooleanPropertyFromJSON(optionsJSON, "oobInline"));
+ options.setReceiveBufferSize(getIntegerPropertyFromJSON(optionsJSON, "receiveBufferSize"));
+ options.setSendBufferSize(getIntegerPropertyFromJSON(optionsJSON, "sendBufferSize"));
+ options.setSoLinger(getIntegerPropertyFromJSON(optionsJSON, "soLinger"));
+ options.setSoTimeout(getIntegerPropertyFromJSON(optionsJSON, "soTimeout"));
+ options.setTrafficClass(getIntegerPropertyFromJSON(optionsJSON, "trafficClass"));
+
+ try {
+ socket.close();
+ callbackContext.success();
+ } catch (IOException e) {
+ callbackContext.error(e.toString());
+ }
+ }
+
+ private Boolean getBooleanPropertyFromJSON(JSONObject jsonObject, String propertyName) throws JSONException {
+ return jsonObject.has(propertyName) ? jsonObject.getBoolean(propertyName) : null;
+ }
+
+ private Integer getIntegerPropertyFromJSON(JSONObject jsonObject, String propertyName) throws JSONException {
+ return jsonObject.has(propertyName) ? jsonObject.getInt(propertyName) : null;
+ }
+
+ private SocketAdapter getSocketAdapter(String socketKey) {
+ if (!this.socketAdapters.containsKey(socketKey)) {
+ throw new IllegalArgumentException(
+ String.format("Cannot find socketKey: %s. Connection is probably closed.", socketKey));
+ }
+ return this.socketAdapters.get(socketKey);
+ }
+
+ private void dispatchEvent(JSONObject jsonEventObject) {
+ this.webView.sendJavascript(String.format("window.Socket.dispatchEvent(%s);", jsonEventObject.toString()));
+ }
+
+ private class CloseEventHandler implements Consumer {
+ private String socketKey;
+ public CloseEventHandler(String socketKey) {
+ this.socketKey = socketKey;
+ }
+ @Override
+ public void accept(Boolean hasError) {
+ socketAdapters.remove(this.socketKey);
+
+ try {
+ JSONObject event = new JSONObject();
+ event.put("type", "Close");
+ event.put("hasError", hasError.booleanValue());
+ event.put("socketKey", this.socketKey);
+
+ dispatchEvent(event);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private class DataConsumer implements Consumer {
+ private String socketKey;
+ public DataConsumer(String socketKey) {
+ this.socketKey = socketKey;
+ }
+ @SuppressLint("NewApi")
+ @Override
+ public void accept(byte[] data) {
+ try {
+ JSONObject event = new JSONObject();
+ event.put("type", "DataReceived");
+ event.put("data", new JSONArray(data));
+ event.put("socketKey", socketKey);
+
+ dispatchEvent(event);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private class ErrorHandler implements Consumer {
+ private String socketKey;
+ public ErrorHandler(String socketKey) {
+ this.socketKey = socketKey;
+ }
+ @Override
+ public void accept(IOException exception) {
+ try {
+ JSONObject event = new JSONObject();
+ event.put("type", "Error");
+ event.put("errorMessage", exception.toString());
+ event.put("socketKey", socketKey);
+
+ dispatchEvent(event);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/src/android/src/cz/blocshop/socketsforcordova/SocketsForCordovaActivity.java b/src/android/src/cz/blocshop/socketsforcordova/SocketsForCordovaActivity.java
new file mode 100644
index 0000000..9d6e6a6
--- /dev/null
+++ b/src/android/src/cz/blocshop/socketsforcordova/SocketsForCordovaActivity.java
@@ -0,0 +1,37 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+package cz.blocshop.socketsforcordova;
+
+import android.os.Bundle;
+import org.apache.cordova.*;
+
+public class SocketsForCordovaActivity extends CordovaActivity
+{
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ super.init();
+ // Set by in config.xml
+ super.loadUrl(Config.getStartUrl());
+ //super.loadUrl("file:///android_asset/www/index.html");
+ }
+}
+
diff --git a/src/wp8/src/SocketAdapter.cs b/src/wp8/src/SocketAdapter.cs
new file mode 100644
index 0000000..83892ea
--- /dev/null
+++ b/src/wp8/src/SocketAdapter.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Blocshop.ScoketsForCordova
+{
+ public interface ISocketAdapter
+ {
+ Task Connect(String host, int port);
+ Task Write(byte[] data);
+ void Close();
+ SocketAdapterOptions Options { set; }
+ Action DataConsumer { set; }
+ Action CloseEventHandler { set; }
+ Action ErrorHandler { set; }
+ }
+
+
+ public class SocketAdapter : ISocketAdapter
+ {
+ private const int InputStreamBufferSize = 16 * 1024;
+ private readonly Socket socket;
+
+ public Action DataConsumer { get; set; }
+ public Action CloseEventHandler { get; set; }
+ public Action ErrorHandler { get; set; }
+ public SocketAdapterOptions Options { get; set; }
+
+ public SocketAdapter()
+ {
+ this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ }
+
+ public async Task Connect(string host, int port)
+ {
+ var connectSocketAsyncEventArgs = new SocketAsyncEventArgs
+ {
+ RemoteEndPoint = new DnsEndPoint(host, port)
+ };
+
+ await this.socket.ConnectTaskAsync(connectSocketAsyncEventArgs);
+
+ this.StartReadTask();
+ }
+
+ public async Task Write(byte[] data)
+ {
+ var socketAsyncEventArgs = new SocketAsyncEventArgs();
+ socketAsyncEventArgs.SetBuffer(data, 0, data.Length);
+
+ await this.socket.SendTaskAsync(socketAsyncEventArgs);
+ }
+
+ public void Close()
+ {
+ this.socket.Shutdown(SocketShutdown.Send);
+ }
+
+ private void StartReadTask()
+ {
+ Task.Factory.StartNew(() => this.RunRead());
+ }
+
+ private async Task RunRead()
+ {
+ bool hasError = false;
+ try
+ {
+ await this.RunReadLoop();
+ }
+ catch (SocketException ex)
+ {
+ hasError = true;
+ this.ErrorHandler(ex);
+ }
+ catch (Exception ex)
+ {
+ }
+ finally
+ {
+ this.socket.Close();
+ this.CloseEventHandler(hasError);
+ }
+ }
+
+ private async Task RunReadLoop()
+ {
+ byte[] buffer = new byte[InputStreamBufferSize];
+ int bytesRead = 0;
+ do
+ {
+ var eventArgs = new SocketAsyncEventArgs();
+ eventArgs.SetBuffer(buffer, 0, InputStreamBufferSize);
+
+ await this.socket.ReceiveTaskAsync(eventArgs);
+
+ bytesRead = eventArgs.BytesTransferred;
+
+ byte[] data = new byte[bytesRead];
+ Array.Copy(buffer, data, data.Length);
+ this.DataConsumer(data);
+ }
+ while (bytesRead != 0);
+ }
+ }
+}
diff --git a/src/wp8/src/SocketAdapterOptions.cs b/src/wp8/src/SocketAdapterOptions.cs
new file mode 100644
index 0000000..dc09cee
--- /dev/null
+++ b/src/wp8/src/SocketAdapterOptions.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Blocshop.ScoketsForCordova
+{
+ public class SocketAdapterOptions
+ {
+ }
+}
diff --git a/src/wp8/src/SocketEvent.cs b/src/wp8/src/SocketEvent.cs
new file mode 100644
index 0000000..75be9f1
--- /dev/null
+++ b/src/wp8/src/SocketEvent.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Blocshop.ScoketsForCordova
+{
+ [DataContract]
+ public abstract class SocketEvent
+ {
+ [DataMember(Name = "type")]
+ public abstract string Type { get; set; }
+
+ [DataMember(Name = "socketKey")]
+ public string SocketKey { get; set; }
+ }
+
+ [DataContract]
+ public class CloseSocketEvent : SocketEvent
+ {
+ public override string Type
+ {
+ get
+ {
+ return "Close";
+ }
+ set
+ {
+ }
+ }
+
+ [DataMember(Name = "hasError")]
+ public bool HasError { get; set; }
+ }
+
+ [DataContract]
+ public class DataReceivedSocketEvent : SocketEvent
+ {
+ public override string Type
+ {
+ get
+ {
+ return "DataReceived";
+ }
+ set
+ {
+ }
+ }
+
+ [DataMember(Name = "data")]
+ public byte[] Data { get; set; }
+ }
+
+ [DataContract]
+ public class ErrorSocketEvent : SocketEvent
+ {
+ public override string Type
+ {
+ get
+ {
+ return "Error";
+ }
+ set
+ {
+ }
+ }
+
+ [DataMember(Name="errorMessage")]
+ public string ErrorMessage { get; set; }
+ }
+}
diff --git a/src/wp8/src/SocketExtensions.cs b/src/wp8/src/SocketExtensions.cs
new file mode 100644
index 0000000..a8e2585
--- /dev/null
+++ b/src/wp8/src/SocketExtensions.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Blocshop.ScoketsForCordova
+{
+ public static class SocketExtensions
+ {
+ public static async Task ConnectTaskAsync(this Socket socket, SocketAsyncEventArgs socketAsyncEventArgs)
+ {
+ var task = CreateTaskFromCompletionHandler(socketAsyncEventArgs, SocketAsyncOperation.Connect);
+
+ socket.ConnectAsync(socketAsyncEventArgs);
+
+ await task;
+ }
+
+ public static async Task SendTaskAsync(this Socket socket, SocketAsyncEventArgs socketAsyncEventArgs)
+ {
+ var task = CreateTaskFromCompletionHandler(socketAsyncEventArgs, SocketAsyncOperation.Send);
+
+ socket.SendAsync(socketAsyncEventArgs);
+
+ await task;
+ }
+
+ public static async Task ReceiveTaskAsync(this Socket socket, SocketAsyncEventArgs socketAsyncEventArgs)
+ {
+ var task = CreateTaskFromCompletionHandler(socketAsyncEventArgs, SocketAsyncOperation.Receive);
+
+ socket.ReceiveAsync(socketAsyncEventArgs);
+
+ await task;
+
+ return socketAsyncEventArgs.Buffer;
+ }
+
+ private static Task CreateTaskFromCompletionHandler(SocketAsyncEventArgs socketAsyncEventArgs, SocketAsyncOperation socketAsyncOperation)
+ {
+ TaskCompletionSource completionSource = new TaskCompletionSource();
+ socketAsyncEventArgs.Completed += new EventHandler((o, eventArgs) =>
+ {
+ if (eventArgs.LastOperation == socketAsyncOperation)
+ {
+ if (eventArgs.SocketError == SocketError.Success)
+ {
+ completionSource.SetResult("");
+ }
+ else
+ {
+ completionSource.SetException(new SocketException((int)eventArgs.SocketError));
+ }
+ }
+ });
+
+ return completionSource.Task;
+ }
+ }
+}
diff --git a/src/wp8/src/SocketPlugin.cs b/src/wp8/src/SocketPlugin.cs
new file mode 100644
index 0000000..243eeb8
--- /dev/null
+++ b/src/wp8/src/SocketPlugin.cs
@@ -0,0 +1,152 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Tasks;
+using WPCordovaClassLib.Cordova;
+using WPCordovaClassLib.Cordova.Commands;
+using WPCordovaClassLib.Cordova.JSON;
+
+namespace Blocshop.ScoketsForCordova
+{
+ public class SocketPlugin : BaseCommand
+ {
+ private readonly ISocketStorage socketStorage;
+ private string eventDispatcherCallbackId;
+
+ public SocketPlugin()
+ {
+ System.Diagnostics.Debug.WriteLine("SocketPlugin constructor: " + DateTime.Now.Ticks);
+ 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);
+ try
+ {
+ socket.Connect(host, port).Wait();
+
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.OK));
+ }
+ catch (SocketException ex)
+ {
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, ex.Message));
+ }
+ }
+
+ public void write(string parameters/*, string socketKey, byte[] data*/)
+ {
+ string socketKey = JsonHelper.Deserialize(parameters)[0];
+ string dataJsonArray = JsonHelper.Deserialize(parameters)[1];
+ byte[] data = JsonHelper.Deserialize(dataJsonArray);
+
+ ISocketAdapter socket = this.socketStorage.Get(socketKey);
+ try
+ {
+ socket.Write(data).Wait();
+
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.OK));
+ }
+ catch (SocketException ex)
+ {
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, ex.Message));
+ }
+ }
+
+ public void close(string parameters)
+ {
+ string socketKey = JsonHelper.Deserialize(parameters)[0];
+
+ ISocketAdapter socket = this.socketStorage.Get(socketKey);
+
+ socket.Close();
+ }
+
+ //private void setOptions(CordovaArgs args, CallbackContext callbackContext) throws JSONException {
+
+ // String socketKey = args.getString(0);
+ // JSONObject optionsJSON = args.getJSONObject(1);
+
+ // SocketAdapter socket = this.getSkocketAdapter(socketKey);
+
+ // SocketAdapterOptions options = new SocketAdapterOptions();
+ // options.setKeepAlive(getBooleanPropertyFromJSON(optionsJSON, "keepAlive"));
+ // options.setOobInline(getBooleanPropertyFromJSON(optionsJSON, "oobInline"));
+ // options.setReceiveBufferSize(getIntegerPropertyFromJSON(optionsJSON, "receiveBufferSize"));
+ // options.setSendBufferSize(getIntegerPropertyFromJSON(optionsJSON, "sendBufferSize"));
+ // options.setSoLinger(getIntegerPropertyFromJSON(optionsJSON, "soLinger"));
+ // options.setSoTimeout(getIntegerPropertyFromJSON(optionsJSON, "soTimeout"));
+ // options.setTrafficClass(getIntegerPropertyFromJSON(optionsJSON, "trafficClass"));
+
+ // try {
+ // socket.close();
+ // callbackContext.success();
+ // } catch (IOException e) {
+ // callbackContext.error(e.toString());
+ // }
+ //}
+
+ private void CloseEventHandler(string socketKey, bool hasError)
+ {
+ socketStorage.Remove(socketKey);
+ this.DispatchEvent(new CloseSocketEvent
+ {
+ HasError = hasError,
+ SocketKey = socketKey
+ });
+ }
+
+ private void DataConsumer(string socketKey, byte[] data)
+ {
+ this.DispatchEvent(new DataReceivedSocketEvent
+ {
+ Data = data,
+ SocketKey = socketKey
+ });
+ }
+
+ private void ErrorHandler(string socketKey, Exception exception)
+ {
+ this.DispatchEvent(new ErrorSocketEvent
+ {
+ ErrorMessage = exception.Message,
+ SocketKey = socketKey
+ });
+ }
+
+ private void DispatchEvent(SocketEvent eventObject)
+ {
+ PluginResult result = new PluginResult(PluginResult.Status.OK, JsonHelper.Serialize(eventObject));
+ result.KeepCallback = true;
+ DispatchCommandResult(result, this.eventDispatcherCallbackId);
+ }
+ }
+}
diff --git a/src/wp8/src/SocketStorage.cs b/src/wp8/src/SocketStorage.cs
new file mode 100644
index 0000000..3d77e78
--- /dev/null
+++ b/src/wp8/src/SocketStorage.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Blocshop.ScoketsForCordova
+{
+ public interface ISocketStorage
+ {
+ void Add(string socketKey, ISocketAdapter socketAdapter);
+ ISocketAdapter Get(string socketKey);
+ void Remove(string socketKey);
+ }
+
+ public class SocketStorage : ISocketStorage
+ {
+ private readonly IDictionary socketAdapters = new Dictionary();
+
+ private object syncRoot = new object();
+
+ public void Add(string socketKey, ISocketAdapter socketAdapter)
+ {
+ lock (syncRoot)
+ {
+ System.Diagnostics.Debug.WriteLine("Add: " + DateTime.Now.Ticks);
+ this.socketAdapters.Add(socketKey, socketAdapter);
+ }
+ }
+
+ public ISocketAdapter Get(string socketKey)
+ {
+ lock (syncRoot)
+ {
+ System.Diagnostics.Debug.WriteLine("Get: " + DateTime.Now.Ticks);
+ if (!this.socketAdapters.ContainsKey(socketKey))
+ {
+ throw new ArgumentException(
+ string.Format("Cannot find socketKey: {0}. Connection is probably closed.", socketKey));
+ }
+
+ return this.socketAdapters[socketKey];
+ }
+ }
+
+ public void Remove(string socketKey)
+ {
+ lock (syncRoot)
+ {
+ System.Diagnostics.Debug.WriteLine("Remove: " + DateTime.Now.Ticks);
+ this.socketAdapters.Remove(socketKey);
+ }
+ }
+
+ public static ISocketStorage CreateSocketStorage()
+ {
+ return new SocketStorage();
+ }
+ }
+}