From c477da9698828fa92fc943e0fe43c7606315633e Mon Sep 17 00:00:00 2001 From: Lucas de castro Date: Wed, 3 May 2017 09:41:38 -0300 Subject: [PATCH 1/8] Bugfix --- .../com/gcrabtree/rctsocketio/SocketIoReadableNativeMap.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoReadableNativeMap.java b/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoReadableNativeMap.java index c9a48fc..5e455f6 100644 --- a/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoReadableNativeMap.java +++ b/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoReadableNativeMap.java @@ -15,6 +15,11 @@ public class SocketIoReadableNativeMap extends ReadableNativeMap { private static final String TAG = "SIOReadableNativeMap"; + + protected SocketIoReadableNativeMap(HybridData hybridData) { + super(hybridData); + } + /** * Note: This will only be necessary until RN version 0.26 goes live * It will be deprecated from the project, as this is just included in that version of RN. From 989e518593837583ce55239656ba5a59ff35b57c Mon Sep 17 00:00:00 2001 From: Lucas de castro Date: Wed, 3 May 2017 09:42:33 -0300 Subject: [PATCH 2/8] Changed emit method to receive a new parameter --- .../java/com/gcrabtree/rctsocketio/SocketIoModule.java | 10 ++++++++-- index.js | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java b/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java index ae5ee80..6cfa8f1 100644 --- a/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java +++ b/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java @@ -63,12 +63,18 @@ public void initialize(String connection, ReadableMap options) { * Emit event to server * @param event The name of the event. * @param items The data to pass through the SocketIo engine to the server endpoint. + * @param Ack callback */ @ReactMethod - public void emit(String event, ReadableMap items) { + public void emit(String event, ReadableMap items, final Callback ack) { HashMap map = SocketIoReadableNativeMap.toHashMap((ReadableNativeMap) items); if (mSocket != null) { - mSocket.emit(event, new JSONObject(map)); + mSocket.emit(event, new JSONObject(map), new Ack() { + @Override + public void call(Object... args) { + ack.invoke(SocketIoJSONUtil.objectsFromJSON(args)); + } + }); } else { Log.e(TAG, "Cannot execute emit. mSocket is null. Initialize socket first!!!"); diff --git a/index.js b/index.js index ff7f5fb..980e609 100644 --- a/index.js +++ b/index.js @@ -63,8 +63,8 @@ class Socket { this.onAnyHandler = handler; } - emit (event, data) { - this.sockets.emit(event, data); + emit (event, data, ack = () => console.log(`ACK ${event}`)) { + this.sockets.emit(event, data, ack); } joinNamespace (namespace) { From 35a92c9c763dc4f92d2ea46cb7d4cce4afb25b79 Mon Sep 17 00:00:00 2001 From: Lucas de castro Date: Wed, 3 May 2017 10:54:22 -0300 Subject: [PATCH 3/8] bugfix --- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 160 +++++++++++++++++++++++ gradlew.bat | 90 +++++++++++++ 4 files changed, 256 insertions(+) create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..13372aef5e24af05341d49695ee84e5f9b594659 GIT binary patch literal 53636 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ⅅ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 H-+%o-(+5aQ literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..04e285f --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Dec 28 10:00:20 PST 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From c3bcf627676c2b554ac8ef21e89dfaebcb9ca4cc Mon Sep 17 00:00:00 2001 From: Lucas de castro Date: Wed, 3 May 2017 14:55:58 -0300 Subject: [PATCH 4/8] Bugfix --- .../com/gcrabtree/rctsocketio/SocketIoReadableNativeMap.java | 1 + 1 file changed, 1 insertion(+) diff --git a/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoReadableNativeMap.java b/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoReadableNativeMap.java index 5e455f6..972016a 100644 --- a/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoReadableNativeMap.java +++ b/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoReadableNativeMap.java @@ -2,6 +2,7 @@ import android.util.Log; +import com.facebook.jni.HybridData; import com.facebook.react.bridge.ReadableMapKeySetIterator; import com.facebook.react.bridge.ReadableNativeMap; From 0c77f2d5645eaed5d062f6808f956dfd9f8c90e5 Mon Sep 17 00:00:00 2001 From: Lucas de castro Date: Wed, 3 May 2017 17:13:09 -0300 Subject: [PATCH 5/8] Bugfix --- .../src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java | 1 + 1 file changed, 1 insertion(+) diff --git a/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java b/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java index 6cfa8f1..5b0e730 100644 --- a/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java +++ b/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java @@ -15,6 +15,7 @@ import com.facebook.react.bridge.ReadableNativeMap; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.Callback; import org.json.JSONObject; From b69033071ed20fc8b53be472ba271a0880db0d7d Mon Sep 17 00:00:00 2001 From: Lucas de castro Date: Wed, 3 May 2017 17:19:20 -0300 Subject: [PATCH 6/8] Bugfix --- .../src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java | 1 + 1 file changed, 1 insertion(+) diff --git a/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java b/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java index 5b0e730..cf902f2 100644 --- a/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java +++ b/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java @@ -22,6 +22,7 @@ import java.net.URISyntaxException; import java.util.HashMap; +import io.socket.client.Ack; import io.socket.client.IO; import io.socket.client.Socket; import io.socket.emitter.Emitter; From 0d561180c4df5c2eadfbab44a0be499e90d851d1 Mon Sep 17 00:00:00 2001 From: Bruno Pessoa Date: Tue, 9 May 2017 12:10:33 -0300 Subject: [PATCH 7/8] react-native-socketio socket.io-client-swift updated to newest version with swift 3. Necessary changes done in Socket.swift class, func emit modified with a callback. --- index.js | 1 + ios/RNSwiftSocketIO/Socket.swift | 71 +- ios/RNSwiftSocketIO/SocketBridge.h | 4 +- ios/RNSwiftSocketIO/SocketBridge.m | 20 +- .../SocketIOClient/NSCharacterSet.swift | 31 - .../SocketIOClient/SSLSecurity.swift | 259 +++++ .../SocketIOClient/SocketAckEmitter.swift | 48 +- .../SocketIOClient/SocketAckManager.swift | 31 +- .../SocketIOClient/SocketAnyEvent.swift | 6 +- .../SocketIOClient/SocketClientManager.swift | 82 ++ .../SocketIOClient/SocketEngine.swift | 459 ++++---- .../SocketIOClient/SocketEngineClient.swift | 6 +- .../SocketEnginePacketType.swift | 2 +- .../SocketIOClient/SocketEnginePollable.swift | 90 +- .../SocketIOClient/SocketEngineSpec.swift | 64 +- .../SocketEngineWebsocket.swift | 24 +- .../SocketIOClient/SocketEventHandler.swift | 4 +- .../SocketIOClient/SocketExtensions.swift | 127 ++ .../SocketIOClient/SocketIOClient.swift | 300 +++-- .../SocketIOClientConfiguration.swift | 108 ++ .../SocketIOClient/SocketIOClientOption.swift | 198 ++-- .../SocketIOClient/SocketIOClientSpec.swift | 6 +- .../SocketIOClient/SocketIOClientStatus.swift | 2 +- .../SocketIOClient/SocketLogger.swift | 16 +- .../SocketIOClient/SocketPacket.swift | 186 ++- .../SocketIOClient/SocketParsable.swift | 134 +-- .../SocketIOClient/SocketStringReader.swift | 25 +- .../SocketIOClient/SocketTypes.swift | 28 +- .../SocketIOClient/String.swift | 31 - .../SocketIOClient/SwiftRegex.swift | 195 ---- .../SocketIOClient/WebSocket.swift | 1017 ++++++++--------- 31 files changed, 1861 insertions(+), 1714 deletions(-) delete mode 100644 ios/RNSwiftSocketIO/SocketIOClient/NSCharacterSet.swift create mode 100644 ios/RNSwiftSocketIO/SocketIOClient/SSLSecurity.swift create mode 100644 ios/RNSwiftSocketIO/SocketIOClient/SocketClientManager.swift create mode 100644 ios/RNSwiftSocketIO/SocketIOClient/SocketExtensions.swift create mode 100644 ios/RNSwiftSocketIO/SocketIOClient/SocketIOClientConfiguration.swift delete mode 100644 ios/RNSwiftSocketIO/SocketIOClient/String.swift delete mode 100644 ios/RNSwiftSocketIO/SocketIOClient/SwiftRegex.swift diff --git a/index.js b/index.js index 980e609..8433ca4 100644 --- a/index.js +++ b/index.js @@ -16,6 +16,7 @@ class Socket { this.handlers = {}; this.onAnyHandler = null; + if(Platform.OS === "ios") this.sockets.addListener("socketEvent"); this.deviceEventSubscription = DeviceEventEmitter.addListener( 'socketEvent', this._handleEvent.bind(this) ); diff --git a/ios/RNSwiftSocketIO/Socket.swift b/ios/RNSwiftSocketIO/Socket.swift index f13c035..a15dcbb 100644 --- a/ios/RNSwiftSocketIO/Socket.swift +++ b/ios/RNSwiftSocketIO/Socket.swift @@ -9,33 +9,33 @@ import Foundation @objc(SocketIO) -class SocketIO: NSObject { +class SocketIO : RCTEventEmitter { var socket: SocketIOClient! var connectionSocket: NSURL! - var bridge: RCTBridge! /** * Construct and expose RCTBridge to module */ - @objc func initWithBridge(_bridge: RCTBridge) { + @objc(initWithBridge:) + func initWithBridge(_bridge: RCTBridge) { self.bridge = _bridge } /** * Initialize and configure socket */ - - @objc func initialize(connection: String, config: NSDictionary) -> Void { - connectionSocket = NSURL(string: connection); - + + @objc(initialize:config:) + func initialize(connection: NSString, config: NSDictionary) -> Void { + connectionSocket = NSURL(string: String(connection)); // Connect to socket with config self.socket = SocketIOClient( - socketURL: self.connectionSocket, - options:config as? [String : AnyObject] + socketURL: self.connectionSocket!, + config: config as NSDictionary? ) - + // Initialize onAny events self.onAnyEvent() } @@ -44,15 +44,17 @@ class SocketIO: NSObject { * Manually join the namespace */ - @objc func joinNamespace(namespace: String) -> Void { - self.socket.joinNamespace(namespace); + @objc(joinNamespace:) + func joinNamespace(namespace: String) -> Void { + self.socket.joinNamespace(namespace); } /** * Leave namespace back to '/' */ - @objc func leaveNamespace() { + @objc(leaveNamespace) + func leaveNamespace() { self.socket.leaveNamespace(); } @@ -61,11 +63,11 @@ class SocketIO: NSObject { * add NSDictionary of handler events */ - @objc func addHandlers(handlers: NSDictionary) -> Void { + @objc(addHandlers:) + func addHandlers(handlers: NSDictionary) -> Void { for handler in handlers { - self.socket.on(handler.key as! String) { data, ack in - self.bridge.eventDispatcher.sendDeviceEventWithName( - "socketEvent", body: handler.key as! String) + self.socket.on(handler.value as! String) { data, ack in + self.sendEvent(withName: "socketEvent", body: handler.value as! String) } } } @@ -73,9 +75,16 @@ class SocketIO: NSObject { /** * Emit event to server */ - - @objc func emit(event: String, items: AnyObject) -> Void { - self.socket.emit(event, items) + + @objc(emit:items:ack:) + func emit(event: String, items: AnyObject, ack: RCTResponseSenderBlock?) -> Void { + if let ack = ack { + self.socket.emitWithAck(event, items as! SocketData).timingOut(after: 1) { data in + ack(data) + } + } else { + self.socket.emit(event, items as! SocketData) + } } /** @@ -84,11 +93,9 @@ class SocketIO: NSObject { private func onAnyEventHandler (sock: SocketAnyEvent) -> Void { if let items = sock.items { - self.bridge.eventDispatcher.sendDeviceEventWithName("socketEvent", - body: ["name": sock.event, "items": items]) + self.sendEvent(withName: "socketEvent", body: ["name": sock.event, "items": items]) } else { - self.bridge.eventDispatcher.sendDeviceEventWithName("socketEvent", - body: ["name": sock.event]) + self.sendEvent(withName: "socketEvent", body: ["name": sock.event]) } } @@ -97,22 +104,30 @@ class SocketIO: NSObject { * Currently adding handlers to event on the JS layer */ - @objc func onAnyEvent() -> Void { + @objc(onAnyEvent) + func onAnyEvent() -> Void { self.socket.onAny(self.onAnyEventHandler) } // Connect to socket - @objc func connect() -> Void { + @objc(connect) + func connect() -> Void { self.socket.connect() } // Reconnect to socket - @objc func reconnect() -> Void { + @objc(reconnect) + func reconnect() -> Void { self.socket.reconnect() } // Disconnect from socket - @objc func disconnect() -> Void { + @objc(disconnect) + func disconnect() -> Void { self.socket.disconnect() } + + override func supportedEvents() -> [String]! { + return ["socketEvent"] + } } diff --git a/ios/RNSwiftSocketIO/SocketBridge.h b/ios/RNSwiftSocketIO/SocketBridge.h index 22c2a4e..852aa4c 100644 --- a/ios/RNSwiftSocketIO/SocketBridge.h +++ b/ios/RNSwiftSocketIO/SocketBridge.h @@ -9,8 +9,6 @@ #ifndef RNSwiftSocketIO_SocketBridge_h #define RNSwiftSocketIO_SocketBridge_h -#import "RCTBridge.h" -#import "RCTBridgeModule.h" -#import "RCTEventDispatcher.h" +#import "RCTEventEmitter.h" #endif diff --git a/ios/RNSwiftSocketIO/SocketBridge.m b/ios/RNSwiftSocketIO/SocketBridge.m index 8679b09..db0e611 100644 --- a/ios/RNSwiftSocketIO/SocketBridge.m +++ b/ios/RNSwiftSocketIO/SocketBridge.m @@ -6,17 +6,17 @@ // Copyright (c) 2015 Facebook. All rights reserved. // -#import "RCTBridge.h" +#import "RCTEventEmitter.h" -@interface RCT_EXTERN_MODULE(SocketIO, NSObject) +@interface RCT_EXTERN_MODULE(SocketIO, RCTEventEmitter) -RCT_EXTERN_METHOD(initialize:(NSString*)connection config:(NSDictionary*)config) -RCT_EXTERN_METHOD(addHandlers:(NSDictionary*)handlers) -RCT_EXTERN_METHOD(connect) -RCT_EXTERN_METHOD(disconnect) -RCT_EXTERN_METHOD(reconnect) -RCT_EXTERN_METHOD(emit:(NSString*)event items:(id)items) -RCT_EXTERN_METHOD(joinNamespace:(NSString *)namespace) -RCT_EXTERN_METHOD(leaveNamespace) +RCT_EXTERN_METHOD(initialize:(NSString*)connection config:(NSDictionary*)config); +RCT_EXTERN_METHOD(addHandlers:(NSDictionary*)handlers); +RCT_EXTERN_METHOD(connect); +RCT_EXTERN_METHOD(disconnect); +RCT_EXTERN_METHOD(reconnect); +RCT_EXTERN_METHOD(emit:(NSString*)event items:(id)items ack:(RCTResponseSenderBlock)ack); +RCT_EXTERN_METHOD(joinNamespace:(NSString *)namespace); +RCT_EXTERN_METHOD(leaveNamespace); @end diff --git a/ios/RNSwiftSocketIO/SocketIOClient/NSCharacterSet.swift b/ios/RNSwiftSocketIO/SocketIOClient/NSCharacterSet.swift deleted file mode 100644 index 3ccde2c..0000000 --- a/ios/RNSwiftSocketIO/SocketIOClient/NSCharacterSet.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// NSCharacterSet.swift -// Socket.IO-Client-Swift -// -// Created by Yannick Loriot on 5/4/16. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import Foundation - -extension NSCharacterSet { - class var allowedURLCharacterSet: NSCharacterSet { - return NSCharacterSet(charactersInString: "!*'();:@&=+$,/?%#[]\" {}").invertedSet - } -} diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SSLSecurity.swift b/ios/RNSwiftSocketIO/SocketIOClient/SSLSecurity.swift new file mode 100644 index 0000000..4d2c60a --- /dev/null +++ b/ios/RNSwiftSocketIO/SocketIOClient/SSLSecurity.swift @@ -0,0 +1,259 @@ +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// SSLSecurity.swift +// Starscream +// +// Created by Dalton Cherry on 5/16/15. +// Copyright (c) 2014-2016 Dalton Cherry. +// +// Licensed 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. +// +////////////////////////////////////////////////////////////////////////////////////////////////// +import Foundation +import Security + +public protocol SSLTrustValidator { + func isValid(_ trust: SecTrust, domain: String?) -> Bool +} + +open class SSLCert : NSObject { + var certData: Data? + var key: SecKey? + + /** + Designated init for certificates + + - parameter data: is the binary data of the certificate + + - returns: a representation security object to be used with + */ + public init(data: Data) { + self.certData = data + } + + /** + Designated init for public keys + + - parameter key: is the public key to be used + + - returns: a representation security object to be used with + */ + public init(key: SecKey) { + self.key = key + } +} + +open class SSLSecurity : SSLTrustValidator { + public var validatedDN = true //should the domain name be validated? + + var isReady = false //is the key processing done? + var certificates: [Data]? //the certificates + @nonobjc var pubKeys: [SecKey]? //the public keys + var usePublicKeys = false //use public keys or certificate validation? + + /** + Use certs from main app bundle + + - parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation + + - returns: a representation security object to be used with + */ + public convenience init(usePublicKeys: Bool = false) { + let paths = Bundle.main.paths(forResourcesOfType: "cer", inDirectory: ".") + + let certs = paths.reduce([SSLCert]()) { (certs: [SSLCert], path: String) -> [SSLCert] in + var certs = certs + if let data = NSData(contentsOfFile: path) { + certs.append(SSLCert(data: data as Data)) + } + return certs + } + + self.init(certs: certs, usePublicKeys: usePublicKeys) + } + + /** + Designated init + + - parameter certs: is the certificates or public keys to use + - parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation + + - returns: a representation security object to be used with + */ + public init(certs: [SSLCert], usePublicKeys: Bool) { + self.usePublicKeys = usePublicKeys + + if self.usePublicKeys { + DispatchQueue.global(qos: .default).async { + let pubKeys = certs.reduce([SecKey]()) { (pubKeys: [SecKey], cert: SSLCert) -> [SecKey] in + var pubKeys = pubKeys + if let data = cert.certData, cert.key == nil { + cert.key = self.extractPublicKey(data) + } + if let key = cert.key { + pubKeys.append(key) + } + return pubKeys + } + + self.pubKeys = pubKeys + self.isReady = true + } + } else { + let certificates = certs.reduce([Data]()) { (certificates: [Data], cert: SSLCert) -> [Data] in + var certificates = certificates + if let data = cert.certData { + certificates.append(data) + } + return certificates + } + self.certificates = certificates + self.isReady = true + } + } + + /** + Valid the trust and domain name. + + - parameter trust: is the serverTrust to validate + - parameter domain: is the CN domain to validate + + - returns: if the key was successfully validated + */ + public func isValid(_ trust: SecTrust, domain: String?) -> Bool { + + var tries = 0 + while !self.isReady { + usleep(1000) + tries += 1 + if tries > 5 { + return false //doesn't appear it is going to ever be ready... + } + } + var policy: SecPolicy + if self.validatedDN { + policy = SecPolicyCreateSSL(true, domain as NSString?) + } else { + policy = SecPolicyCreateBasicX509() + } + SecTrustSetPolicies(trust,policy) + if self.usePublicKeys { + if let keys = self.pubKeys { + let serverPubKeys = publicKeyChain(trust) + for serverKey in serverPubKeys as [AnyObject] { + for key in keys as [AnyObject] { + if serverKey.isEqual(key) { + return true + } + } + } + } + } else if let certs = self.certificates { + let serverCerts = certificateChain(trust) + var collect = [SecCertificate]() + for cert in certs { + collect.append(SecCertificateCreateWithData(nil,cert as CFData)!) + } + SecTrustSetAnchorCertificates(trust,collect as NSArray) + var result: SecTrustResultType = .unspecified + SecTrustEvaluate(trust,&result) + if result == .unspecified || result == .proceed { + var trustedCount = 0 + for serverCert in serverCerts { + for cert in certs { + if cert == serverCert { + trustedCount += 1 + break + } + } + } + if trustedCount == serverCerts.count { + return true + } + } + } + return false + } + + /** + Get the public key from a certificate data + + - parameter data: is the certificate to pull the public key from + + - returns: a public key + */ + func extractPublicKey(_ data: Data) -> SecKey? { + guard let cert = SecCertificateCreateWithData(nil, data as CFData) else { return nil } + + return extractPublicKey(cert, policy: SecPolicyCreateBasicX509()) + } + + /** + Get the public key from a certificate + + - parameter data: is the certificate to pull the public key from + + - returns: a public key + */ + func extractPublicKey(_ cert: SecCertificate, policy: SecPolicy) -> SecKey? { + var possibleTrust: SecTrust? + SecTrustCreateWithCertificates(cert, policy, &possibleTrust) + + guard let trust = possibleTrust else { return nil } + var result: SecTrustResultType = .unspecified + SecTrustEvaluate(trust, &result) + return SecTrustCopyPublicKey(trust) + } + + /** + Get the certificate chain for the trust + + - parameter trust: is the trust to lookup the certificate chain for + + - returns: the certificate chain for the trust + */ + func certificateChain(_ trust: SecTrust) -> [Data] { + let certificates = (0.. [Data] in + var certificates = certificates + let cert = SecTrustGetCertificateAtIndex(trust, index) + certificates.append(SecCertificateCopyData(cert!) as Data) + return certificates + } + + return certificates + } + + /** + Get the public key chain for the trust + + - parameter trust: is the trust to lookup the certificate chain and extract the public keys + + - returns: the public keys from the certifcate chain for the trust + */ + @nonobjc func publicKeyChain(_ trust: SecTrust) -> [SecKey] { + let policy = SecPolicyCreateBasicX509() + let keys = (0.. [SecKey] in + var keys = keys + let cert = SecTrustGetCertificateAtIndex(trust, index) + if let key = extractPublicKey(cert!, policy: policy) { + keys.append(key) + } + + return keys + } + + return keys + } + + +} diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketAckEmitter.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketAckEmitter.swift index edb2522..4af43e7 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketAckEmitter.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketAckEmitter.swift @@ -27,21 +27,61 @@ import Foundation public final class SocketAckEmitter : NSObject { let socket: SocketIOClient let ackNum: Int + + public var expected: Bool { + return ackNum != -1 + } init(socket: SocketIOClient, ackNum: Int) { self.socket = socket self.ackNum = ackNum } - public func with(items: AnyObject...) { + public func with(_ items: SocketData...) { guard ackNum != -1 else { return } - socket.emitAck(ackNum, withItems: items) + socket.emitAck(ackNum, with: items) } - public func with(items: [AnyObject]) { + public func with(_ items: [Any]) { guard ackNum != -1 else { return } - socket.emitAck(ackNum, withItems: items) + socket.emitAck(ackNum, with: items) + } + +} + +public final class OnAckCallback : NSObject { + private let ackNumber: Int + private let items: [Any] + private weak var socket: SocketIOClient? + + init(ackNumber: Int, items: [Any], socket: SocketIOClient) { + self.ackNumber = ackNumber + self.items = items + self.socket = socket + } + + deinit { + DefaultSocketLogger.Logger.log("OnAckCallback for \(ackNumber) being released", type: "OnAckCallback") + } + + public func timingOut(after seconds: Int, callback: @escaping AckCallback) { + guard let socket = self.socket else { return } + + socket.ackQueue.sync() { + socket.ackHandlers.addAck(ackNumber, callback: callback) + } + + socket._emit(items, ack: ackNumber) + + guard seconds != 0 else { return } + + let time = DispatchTime.now() + Double(UInt64(seconds) * NSEC_PER_SEC) / Double(NSEC_PER_SEC) + + socket.handleQueue.asyncAfter(deadline: time) { + socket.ackHandlers.timeoutAck(self.ackNumber, onQueue: socket.handleQueue) + } } + } diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketAckManager.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketAckManager.swift index 972e1da..eea183b 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketAckManager.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketAckManager.swift @@ -24,7 +24,7 @@ import Foundation -private struct SocketAck : Hashable, Equatable { +private struct SocketAck : Hashable { let ack: Int var callback: AckCallback! var hashValue: Int { @@ -35,7 +35,7 @@ private struct SocketAck : Hashable, Equatable { self.ack = ack } - init(ack: Int, callback: AckCallback) { + init(ack: Int, callback: @escaping AckCallback) { self.ack = ack self.callback = callback } @@ -51,24 +51,29 @@ private func ==(lhs: SocketAck, rhs: SocketAck) -> Bool { struct SocketAckManager { private var acks = Set(minimumCapacity: 1) + private let ackSemaphore = DispatchSemaphore(value: 1) - mutating func addAck(ack: Int, callback: AckCallback) { + mutating func addAck(_ ack: Int, callback: @escaping AckCallback) { acks.insert(SocketAck(ack: ack, callback: callback)) } - mutating func executeAck(ack: Int, items: [AnyObject]) { - let callback = acks.remove(SocketAck(ack: ack)) - - dispatch_async(dispatch_get_main_queue()) { - callback?.callback(items) - } + /// Should be called on handle queue + mutating func executeAck(_ ack: Int, with items: [Any], onQueue: DispatchQueue) { + ackSemaphore.wait() + defer { ackSemaphore.signal() } + let ack = acks.remove(SocketAck(ack: ack)) + + onQueue.async() { ack?.callback(items) } } - mutating func timeoutAck(ack: Int) { - let callback = acks.remove(SocketAck(ack: ack)) + /// Should be called on handle queue + mutating func timeoutAck(_ ack: Int, onQueue: DispatchQueue) { + ackSemaphore.wait() + defer { ackSemaphore.signal() } + let ack = acks.remove(SocketAck(ack: ack)) - dispatch_async(dispatch_get_main_queue()) { - callback?.callback(["NO ACK"]) + onQueue.async() { + ack?.callback?(["NO ACK"]) } } } diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketAnyEvent.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketAnyEvent.swift index 4c26f0e..3647e96 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketAnyEvent.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketAnyEvent.swift @@ -26,12 +26,12 @@ import Foundation public final class SocketAnyEvent : NSObject { public let event: String - public let items: NSArray? + public let items: [Any]? override public var description: String { - return "SocketAnyEvent: Event: \(event) items: \(items ?? nil)" + return "SocketAnyEvent: Event: \(event) items: \(String(describing: items))" } - init(event: String, items: NSArray?) { + init(event: String, items: [Any]?) { self.event = event self.items = items } diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketClientManager.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketClientManager.swift new file mode 100644 index 0000000..e230272 --- /dev/null +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketClientManager.swift @@ -0,0 +1,82 @@ +// +// SocketClientManager.swift +// Socket.IO-Client-Swift +// +// Created by Erik Little on 6/11/16. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +/** + Experimental socket manager. + + API subject to change. + + Can be used to persist sockets across ViewControllers. + + Sockets are strongly stored, so be sure to remove them once they are no + longer needed. + + Example usage: + ``` + let manager = SocketClientManager.sharedManager + manager["room1"] = socket1 + manager["room2"] = socket2 + manager.removeSocket(socket: socket2) + manager["room1"]?.emit("hello") + ``` + */ +open class SocketClientManager : NSObject { + open static let sharedManager = SocketClientManager() + + private var sockets = [String: SocketIOClient]() + + open subscript(string: String) -> SocketIOClient? { + get { + return sockets[string] + } + + set(socket) { + sockets[string] = socket + } + } + + open func addSocket(_ socket: SocketIOClient, labeledAs label: String) { + sockets[label] = socket + } + + open func removeSocket(withLabel label: String) -> SocketIOClient? { + return sockets.removeValue(forKey: label) + } + + open func removeSocket(_ socket: SocketIOClient) -> SocketIOClient? { + var returnSocket: SocketIOClient? + + for (label, dictSocket) in sockets where dictSocket === socket { + returnSocket = sockets.removeValue(forKey: label) + } + + return returnSocket + } + + open func removeSockets() { + sockets.removeAll() + } +} diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketEngine.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketEngine.swift index e06a811..7fa489e 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketEngine.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketEngine.swift @@ -24,24 +24,24 @@ import Foundation -public final class SocketEngine : NSObject, SocketEnginePollable, SocketEngineWebsocket { - public let emitQueue = dispatch_queue_create("com.socketio.engineEmitQueue", DISPATCH_QUEUE_SERIAL) - public let handleQueue = dispatch_queue_create("com.socketio.engineHandleQueue", DISPATCH_QUEUE_SERIAL) - public let parseQueue = dispatch_queue_create("com.socketio.engineParseQueue", DISPATCH_QUEUE_SERIAL) +public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, SocketEngineWebsocket { + public let emitQueue = DispatchQueue(label: "com.socketio.engineEmitQueue", attributes: []) + public let handleQueue = DispatchQueue(label: "com.socketio.engineHandleQueue", attributes: []) + public let parseQueue = DispatchQueue(label: "com.socketio.engineParseQueue", attributes: []) - public var connectParams: [String: AnyObject]? { + public var connectParams: [String: Any]? { didSet { (urlPolling, urlWebSocket) = createURLs() } } - + public var postWait = [String]() public var waitingForPoll = false public var waitingForPost = false - + public private(set) var closed = false public private(set) var connected = false - public private(set) var cookies: [NSHTTPCookie]? + public private(set) var cookies: [HTTPCookie]? public private(set) var doubleEncodeUTF8 = true public private(set) var extraHeaders: [String: String]? public private(set) var fastUpgrade = false @@ -50,166 +50,170 @@ public final class SocketEngine : NSObject, SocketEnginePollable, SocketEngineWe public private(set) var invalidated = false public private(set) var polling = true public private(set) var probing = false - public private(set) var session: NSURLSession? + public private(set) var session: URLSession? public private(set) var sid = "" public private(set) var socketPath = "/engine.io/" - public private(set) var urlPolling = NSURL() - public private(set) var urlWebSocket = NSURL() + public private(set) var urlPolling = URL(string: "http://localhost/")! + public private(set) var urlWebSocket = URL(string: "http://localhost/")! public private(set) var websocket = false public private(set) var ws: WebSocket? public weak var client: SocketEngineClient? - - private weak var sessionDelegate: NSURLSessionDelegate? - private typealias Probe = (msg: String, type: SocketEnginePacketType, data: [NSData]) - private typealias ProbeWaitQueue = [Probe] + private weak var sessionDelegate: URLSessionDelegate? private let logType = "SocketEngine" - private let url: NSURL - + private let url: URL + private var pingInterval: Double? private var pingTimeout = 0.0 { didSet { pongsMissedMax = Int(pingTimeout / (pingInterval ?? 25)) } } + private var pongsMissed = 0 private var pongsMissedMax = 0 private var probeWait = ProbeWaitQueue() private var secure = false + private var security: SSLSecurity? private var selfSigned = false private var voipEnabled = false - public init(client: SocketEngineClient, url: NSURL, options: Set) { + public init(client: SocketEngineClient, url: URL, config: SocketIOClientConfiguration) { self.client = client self.url = url - - for option in options { + for option in config { switch option { - case let .ConnectParams(params): + case let .connectParams(params): connectParams = params - case let .Cookies(cookies): + case let .cookies(cookies): self.cookies = cookies - case let .DoubleEncodeUTF8(encode): + case let .doubleEncodeUTF8(encode): doubleEncodeUTF8 = encode - case let .ExtraHeaders(headers): + case let .extraHeaders(headers): extraHeaders = headers - case let .SessionDelegate(delegate): + case let .sessionDelegate(delegate): sessionDelegate = delegate - case let .ForcePolling(force): + case let .forcePolling(force): forcePolling = force - case let .ForceWebsockets(force): + case let .forceWebsockets(force): forceWebsockets = force - case let .Path(path): + case let .path(path): socketPath = path - case let .VoipEnabled(enable): + + if !socketPath.hasSuffix("/") { + socketPath += "/" + } + case let .voipEnabled(enable): voipEnabled = enable - case let .Secure(secure): + case let .secure(secure): self.secure = secure - case let .SelfSigned(selfSigned): + case let .selfSigned(selfSigned): self.selfSigned = selfSigned + case let .security(security): + self.security = security default: continue } } - + super.init() + sessionDelegate = sessionDelegate ?? self + (urlPolling, urlWebSocket) = createURLs() } - public convenience init(client: SocketEngineClient, url: NSURL, options: NSDictionary?) { - self.init(client: client, url: url, options: options?.toSocketOptionsSet() ?? []) + public convenience init(client: SocketEngineClient, url: URL, options: NSDictionary?) { + self.init(client: client, url: url, config: options?.toSocketConfiguration() ?? []) } - + deinit { DefaultSocketLogger.Logger.log("Engine is being released", type: logType) closed = true stopPolling() } - - private func checkAndHandleEngineError(msg: String) { - guard let stringData = msg.dataUsingEncoding(NSUTF8StringEncoding, - allowLossyConversion: false) else { return } - + + private func checkAndHandleEngineError(_ msg: String) { do { - if let dict = try NSJSONSerialization.JSONObjectWithData(stringData, options: .MutableContainers) as? NSDictionary { - guard let error = dict["message"] as? String else { return } - - /* - 0: Unknown transport - 1: Unknown sid - 2: Bad handshake request - 3: Bad request - */ - didError(error) - } + let dict = try msg.toNSDictionary() + guard let error = dict["message"] as? String else { return } + + /* + 0: Unknown transport + 1: Unknown sid + 2: Bad handshake request + 3: Bad request + */ + didError(reason: error) } catch { - didError("Got unknown error from server \(msg)") + client?.engineDidError(reason: "Got unknown error from server \(msg)") } } - private func checkIfMessageIsBase64Binary(message: String) -> Bool { - if message.hasPrefix("b4") { - // binary in base64 string - let noPrefix = message[message.startIndex.advancedBy(2).. (NSURL, NSURL) { + private func createURLs() -> (URL, URL) { if client == nil { - return (NSURL(), NSURL()) + return (URL(string: "http://localhost/")!, URL(string: "http://localhost/")!) } - let urlPolling = NSURLComponents(string: url.absoluteString)! - let urlWebSocket = NSURLComponents(string: url.absoluteString)! + var urlPolling = URLComponents(string: url.absoluteString)! + var urlWebSocket = URLComponents(string: url.absoluteString)! var queryString = "" urlWebSocket.path = socketPath @@ -234,15 +238,15 @@ public final class SocketEngine : NSObject, SocketEnginePollable, SocketEngineWe urlWebSocket.percentEncodedQuery = "transport=websocket" + queryString urlPolling.percentEncodedQuery = "transport=polling&b64=1" + queryString - - return (urlPolling.URL!, urlWebSocket.URL!) + + return (urlPolling.url!, urlWebSocket.url!) } private func createWebsocketAndConnect() { - ws = WebSocket(url: urlWebSocketWithSid) - + ws = WebSocket(url: urlWebSocketWithSid as URL) + if cookies != nil { - let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) + let headers = HTTPCookie.requestHeaderFields(with: cookies!) for (key, value) in headers { ws?.headers[key] = value } @@ -254,50 +258,46 @@ public final class SocketEngine : NSObject, SocketEnginePollable, SocketEngineWe } } - ws?.queue = handleQueue + ws?.callbackQueue = handleQueue ws?.voipEnabled = voipEnabled ws?.delegate = self - ws?.selfSignedSSL = selfSigned + ws?.disableSSLCertValidation = selfSigned + ws?.security = security ws?.connect() } - - public func didError(error: String) { - DefaultSocketLogger.Logger.error(error, type: logType) - client?.engineDidError(error) - disconnect(error) + + public func didError(reason: String) { + DefaultSocketLogger.Logger.error("%@", type: logType, args: reason) + client?.engineDidError(reason: reason) + disconnect(reason: reason) } - + public func disconnect(reason: String) { - func postSendClose(data: NSData?, _ res: NSURLResponse?, _ err: NSError?) { - sid = "" - closed = true - invalidated = true - connected = false - - ws?.disconnect() - stopPolling() - client?.engineDidClose(reason) - } - + guard connected else { return closeOutEngine(reason: reason) } + DefaultSocketLogger.Logger.log("Engine is being closed.", type: logType) - + if closed { - client?.engineDidClose(reason) - return + return closeOutEngine(reason: reason) } - + if websocket { - sendWebSocketMessage("", withType: .Close, withData: []) - postSendClose(nil, nil, nil) + sendWebSocketMessage("", withType: .close, withData: []) + closeOutEngine(reason: reason) } else { - // We need to take special care when we're polling that we send it ASAP - // Also make sure we're on the emitQueue since we're touching postWait - dispatch_sync(emitQueue) { - self.postWait.append(String(SocketEnginePacketType.Close.rawValue)) - let req = self.createRequestForPostWithPostWait() - self.doRequest(req, withCallback: postSendClose) - } + disconnectPolling(reason: reason) + } + } + + // We need to take special care when we're polling that we send it ASAP + // Also make sure we're on the emitQueue since we're touching postWait + private func disconnectPolling(reason: String) { + emitQueue.sync { + self.postWait.append(String(SocketEnginePacketType.close.rawValue)) + let req = self.createRequestForPostWithPostWait() + self.doRequest(for: req) {_, _, _ in } + self.closeOutEngine(reason: reason) } } @@ -307,7 +307,7 @@ public final class SocketEngine : NSObject, SocketEnginePollable, SocketEngineWe "we'll probably disconnect soon. You should report this.", type: logType) } - sendWebSocketMessage("", withType: .Upgrade, withData: []) + sendWebSocketMessage("", withType: .upgrade, withData: []) websocket = true polling = false fastUpgrade = false @@ -318,132 +318,139 @@ public final class SocketEngine : NSObject, SocketEnginePollable, SocketEngineWe private func flushProbeWait() { DefaultSocketLogger.Logger.log("Flushing probe wait", type: logType) - dispatch_async(emitQueue) { + emitQueue.async { for waiter in self.probeWait { self.write(waiter.msg, withType: waiter.type, withData: waiter.data) } - - self.probeWait.removeAll(keepCapacity: false) - + + self.probeWait.removeAll(keepingCapacity: false) + if self.postWait.count != 0 { self.flushWaitingForPostToWebSocket() } } } - + // We had packets waiting for send when we upgraded // Send them raw public func flushWaitingForPostToWebSocket() { guard let ws = self.ws else { return } - + for msg in postWait { - ws.writeString(fixDoubleUTF8(msg)) + ws.write(string: msg) } - - postWait.removeAll(keepCapacity: true) + + postWait.removeAll(keepingCapacity: false) } - private func handleClose(reason: String) { - client?.engineDidClose(reason) + private func handleClose(_ reason: String) { + client?.engineDidClose(reason: reason) } - private func handleMessage(message: String) { + private func handleMessage(_ message: String) { client?.parseEngineMessage(message) } private func handleNOOP() { doPoll() } - + private func handleOpen(openData: String) { - let mesData = openData.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! - do { - let json = try NSJSONSerialization.JSONObjectWithData(mesData, - options: NSJSONReadingOptions.AllowFragments) as? NSDictionary - if let sid = json?["sid"] as? String { - let upgradeWs: Bool - - self.sid = sid - connected = true - - if let upgrades = json?["upgrades"] as? [String] { - upgradeWs = upgrades.contains("websocket") - } else { - upgradeWs = false - } - - if let pingInterval = json?["pingInterval"] as? Double, pingTimeout = json?["pingTimeout"] as? Double { - self.pingInterval = pingInterval / 1000.0 - self.pingTimeout = pingTimeout / 1000.0 - } - - if !forcePolling && !forceWebsockets && upgradeWs { - createWebsocketAndConnect() - } - - sendPing() - - if !forceWebsockets { - doPoll() - } - - client?.engineDidOpen?("Connect") - } - } catch { - didError("Error parsing open packet") + guard let json = try? openData.toNSDictionary() else { + didError(reason: "Error parsing open packet") + + return } + + guard let sid = json["sid"] as? String else { + didError(reason: "Open packet contained no sid") + + return + } + + let upgradeWs: Bool + + self.sid = sid + connected = true + pongsMissed = 0 + + if let upgrades = json["upgrades"] as? [String] { + upgradeWs = upgrades.contains("websocket") + } else { + upgradeWs = false + } + + if let pingInterval = json["pingInterval"] as? Double, let pingTimeout = json["pingTimeout"] as? Double { + self.pingInterval = pingInterval / 1000.0 + self.pingTimeout = pingTimeout / 1000.0 + } + + if !forcePolling && !forceWebsockets && upgradeWs { + createWebsocketAndConnect() + } + + sendPing() + + if !forceWebsockets { + doPoll() + } + + client?.engineDidOpen(reason: "Connect") } - private func handlePong(pongMessage: String) { + private func handlePong(with message: String) { pongsMissed = 0 // We should upgrade - if pongMessage == "3probe" { + if message == "3probe" { upgradeTransport() } } - - public func parseEngineData(data: NSData) { + + public func parseEngineData(_ data: Data) { DefaultSocketLogger.Logger.log("Got binary data: %@", type: "SocketEngine", args: data) - client?.parseEngineBinaryData(data.subdataWithRange(NSMakeRange(1, data.length - 1))) + + client?.parseEngineBinaryData(data.subdata(in: 1.. pongsMissedMax { - client?.engineDidClose("Ping timeout") + client?.engineDidClose(reason: "Ping timeout") + return } + + guard let pingInterval = pingInterval else { return } - if let pingInterval = pingInterval { - pongsMissed += 1 - write("", withType: .Ping, withData: []) - - let time = dispatch_time(DISPATCH_TIME_NOW, Int64(pingInterval * Double(NSEC_PER_SEC))) - dispatch_after(time, dispatch_get_main_queue()) {[weak self] in - self?.sendPing() - } - } + pongsMissed += 1 + write("", withType: .ping, withData: []) + + let time = DispatchTime.now() + Double(Int64(pingInterval * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) + DispatchQueue.main.asyncAfter(deadline: time) {[weak self] in self?.sendPing() } } - + // Moves from long-polling to websockets private func upgradeTransport() { if ws?.isConnected ?? false { DefaultSocketLogger.Logger.log("Upgrading transport to WebSockets", type: logType) fastUpgrade = true - sendPollMessage("", withType: .Noop, withData: []) + sendPollMessage("", withType: .noop, withData: []) // After this point, we should not send anymore polling messages } } /// Write a message, independent of transport. - public func write(msg: String, withType type: SocketEnginePacketType, withData data: [NSData]) { - dispatch_async(emitQueue) { + public func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) { + emitQueue.async { guard self.connected else { return } - + if self.websocket { DefaultSocketLogger.Logger.log("Writing ws: %@ has data: %@", type: self.logType, args: msg, data.count != 0) @@ -512,7 +514,7 @@ public final class SocketEngine : NSObject, SocketEnginePollable, SocketEngineWe } } } - + // Delegate methods public func websocketDidConnect(socket: WebSocket) { if !forceWebsockets { @@ -524,28 +526,35 @@ public final class SocketEngine : NSObject, SocketEnginePollable, SocketEngineWe polling = false } } - + public func websocketDidDisconnect(socket: WebSocket, error: NSError?) { probing = false - + if closed { - client?.engineDidClose("Disconnect") + client?.engineDidClose(reason: "Disconnect") + return } - + if websocket { connected = false websocket = false - - let reason = error?.localizedDescription ?? "Socket Disconnected" - - if error != nil { - didError(reason) + + if let reason = error?.localizedDescription { + didError(reason: reason) + } else { + client?.engineDidClose(reason: "Socket Disconnected") } - - client?.engineDidClose(reason) } else { flushProbeWait() } } } + +extension SocketEngine { + public func URLSession(session: URLSession, didBecomeInvalidWithError error: NSError?) { + DefaultSocketLogger.Logger.error("Engine URLSession became invalid", type: "SocketEngine") + + didError(reason: "Engine URLSession became invalid") + } +} diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketEngineClient.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketEngineClient.swift index a1db7f6..49cac7c 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketEngineClient.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketEngineClient.swift @@ -28,7 +28,7 @@ import Foundation @objc public protocol SocketEngineClient { func engineDidError(reason: String) func engineDidClose(reason: String) - optional func engineDidOpen(reason: String) - func parseEngineMessage(msg: String) - func parseEngineBinaryData(data: NSData) + func engineDidOpen(reason: String) + func parseEngineMessage(_ msg: String) + func parseEngineBinaryData(_ data: Data) } diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketEnginePacketType.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketEnginePacketType.swift index 592d79b..763335e 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketEnginePacketType.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketEnginePacketType.swift @@ -26,5 +26,5 @@ import Foundation @objc public enum SocketEnginePacketType : Int { - case Open, Close, Ping, Pong, Message, Upgrade, Noop + case open, close, ping, pong, message, upgrade, noop } \ No newline at end of file diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketEnginePollable.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketEnginePollable.swift index c419e51..b050975 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketEnginePollable.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketEnginePollable.swift @@ -30,7 +30,7 @@ public protocol SocketEnginePollable : SocketEngineSpec { /// Holds strings waiting to be sent over polling. /// You shouldn't need to mess with this. var postWait: [String] { get set } - var session: NSURLSession? { get } + var session: URLSession? { get } /// Because socket.io doesn't let you send two polling request at the same time /// we have to keep track if there's an outstanding poll var waitingForPoll: Bool { get set } @@ -39,15 +39,17 @@ public protocol SocketEnginePollable : SocketEngineSpec { var waitingForPost: Bool { get set } func doPoll() - func sendPollMessage(message: String, withType type: SocketEnginePacketType, withData datas: [NSData]) + func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data]) func stopPolling() } // Default polling methods extension SocketEnginePollable { - private func addHeaders(req: NSMutableURLRequest) { + private func addHeaders(for req: URLRequest) -> URLRequest { + var req = req + if cookies != nil { - let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) + let headers = HTTPCookie.requestHeaderFields(with: cookies!) req.allHTTPHeaderFields = headers } @@ -56,9 +58,13 @@ extension SocketEnginePollable { req.setValue(value, forHTTPHeaderField: headerName) } } + + return req } - func createRequestForPostWithPostWait() -> NSURLRequest { + func createRequestForPostWithPostWait() -> URLRequest { + defer { postWait.removeAll(keepingCapacity: true) } + var postStr = "" for packet in postWait { @@ -69,22 +75,18 @@ extension SocketEnginePollable { DefaultSocketLogger.Logger.log("Created POST string: %@", type: "SocketEnginePolling", args: postStr) - postWait.removeAll(keepCapacity: false) + var req = URLRequest(url: urlPollingWithSid) + let postData = postStr.data(using: .utf8, allowLossyConversion: false)! - let req = NSMutableURLRequest(URL: urlPollingWithSid) + req = addHeaders(for: req) - addHeaders(req) - - req.HTTPMethod = "POST" + req.httpMethod = "POST" req.setValue("text/plain; charset=UTF-8", forHTTPHeaderField: "Content-Type") + + req.httpBody = postData + req.setValue(String(postData.count), forHTTPHeaderField: "Content-Length") - let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, - allowLossyConversion: false)! - - req.HTTPBody = postData - req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") - - return req + return req as URLRequest } public func doPoll() { @@ -93,41 +95,41 @@ extension SocketEnginePollable { } waitingForPoll = true - let req = NSMutableURLRequest(URL: urlPollingWithSid) - addHeaders(req) - doLongPoll(req) + var req = URLRequest(url: urlPollingWithSid) + + req = addHeaders(for: req) + doLongPoll(for: req ) } - func doRequest(req: NSURLRequest, withCallback callback: (NSData?, NSURLResponse?, NSError?) -> Void) { + func doRequest(for req: URLRequest, callbackWith callback: @escaping (Data?, URLResponse?, Error?) -> Void) { if !polling || closed || invalidated || fastUpgrade { - DefaultSocketLogger.Logger.error("Tried to do polling request when not supposed to", type: "SocketEnginePolling") return } DefaultSocketLogger.Logger.log("Doing polling request", type: "SocketEnginePolling") - session?.dataTaskWithRequest(req, completionHandler: callback).resume() + session?.dataTask(with: req, completionHandler: callback).resume() } - func doLongPoll(req: NSURLRequest) { - doRequest(req) {[weak self] data, res, err in - guard let this = self where this.polling else { return } + func doLongPoll(for req: URLRequest) { + doRequest(for: req) {[weak self] data, res, err in + guard let this = self, this.polling else { return } if err != nil || data == nil { DefaultSocketLogger.Logger.error(err?.localizedDescription ?? "Error", type: "SocketEnginePolling") if this.polling { - this.didError(err?.localizedDescription ?? "Error") + this.didError(reason: err?.localizedDescription ?? "Error") } return } - + DefaultSocketLogger.Logger.log("Got polling response", type: "SocketEnginePolling") - if let str = String(data: data!, encoding: NSUTF8StringEncoding) { - dispatch_async(this.parseQueue) { + if let str = String(data: data!, encoding: String.Encoding.utf8) { + this.parseQueue.async { this.parsePollingMessage(str) } } @@ -156,14 +158,14 @@ extension SocketEnginePollable { DefaultSocketLogger.Logger.log("POSTing", type: "SocketEnginePolling") - doRequest(req) {[weak self] data, res, err in + doRequest(for: req) {[weak self] data, res, err in guard let this = self else { return } if err != nil { DefaultSocketLogger.Logger.error(err?.localizedDescription ?? "Error", type: "SocketEnginePolling") if this.polling { - this.didError(err?.localizedDescription ?? "Error") + this.didError(reason: err?.localizedDescription ?? "Error") } return @@ -171,7 +173,7 @@ extension SocketEnginePollable { this.waitingForPost = false - dispatch_async(this.emitQueue) { + this.emitQueue.async { if !this.fastUpgrade { this.flushWaitingForPost() this.doPoll() @@ -180,22 +182,18 @@ extension SocketEnginePollable { } } - func parsePollingMessage(str: String) { + func parsePollingMessage(_ str: String) { guard str.characters.count != 1 else { return } var reader = SocketStringReader(message: str) while reader.hasNext { - if let n = Int(reader.readUntilStringOccurence(":")) { - let str = reader.read(n) + if let n = Int(reader.readUntilOccurence(of: ":")) { + let str = reader.read(count: n) - dispatch_async(handleQueue) { - self.parseEngineMessage(str, fromPolling: true) - } + handleQueue.async { self.parseEngineMessage(str, fromPolling: true) } } else { - dispatch_async(handleQueue) { - self.parseEngineMessage(str, fromPolling: true) - } + handleQueue.async { self.parseEngineMessage(str, fromPolling: true) } break } } @@ -203,7 +201,7 @@ extension SocketEnginePollable { /// Send polling message. /// Only call on emitQueue - public func sendPollMessage(message: String, withType type: SocketEnginePacketType, withData datas: [NSData]) { + public func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data]) { DefaultSocketLogger.Logger.log("Sending poll: %@ as type: %@", type: "SocketEnginePolling", args: message, type.rawValue) let fixedMessage: String @@ -213,12 +211,10 @@ extension SocketEnginePollable { fixedMessage = message } - let strMsg = "\(type.rawValue)\(fixedMessage)" - - postWait.append(strMsg) + postWait.append(String(type.rawValue) + fixedMessage) for data in datas { - if case let .Right(bin) = createBinaryDataForSend(data) { + if case let .right(bin) = createBinaryDataForSend(using: data) { postWait.append(bin) } } diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketEngineSpec.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketEngineSpec.swift index 7fdd779..f862889 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketEngineSpec.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketEngineSpec.swift @@ -29,79 +29,79 @@ import Foundation weak var client: SocketEngineClient? { get set } var closed: Bool { get } var connected: Bool { get } - var connectParams: [String: AnyObject]? { get set } + var connectParams: [String: Any]? { get set } var doubleEncodeUTF8: Bool { get } - var cookies: [NSHTTPCookie]? { get } + var cookies: [HTTPCookie]? { get } var extraHeaders: [String: String]? { get } var fastUpgrade: Bool { get } var forcePolling: Bool { get } var forceWebsockets: Bool { get } - var parseQueue: dispatch_queue_t! { get } + var parseQueue: DispatchQueue { get } var polling: Bool { get } var probing: Bool { get } - var emitQueue: dispatch_queue_t! { get } - var handleQueue: dispatch_queue_t! { get } + var emitQueue: DispatchQueue { get } + var handleQueue: DispatchQueue { get } var sid: String { get } var socketPath: String { get } - var urlPolling: NSURL { get } - var urlWebSocket: NSURL { get } + var urlPolling: URL { get } + var urlWebSocket: URL { get } var websocket: Bool { get } var ws: WebSocket? { get } - init(client: SocketEngineClient, url: NSURL, options: NSDictionary?) + init(client: SocketEngineClient, url: URL, options: NSDictionary?) func connect() - func didError(error: String) + func didError(reason: String) func disconnect(reason: String) func doFastUpgrade() func flushWaitingForPostToWebSocket() - func parseEngineData(data: NSData) - func parseEngineMessage(message: String, fromPolling: Bool) - func write(msg: String, withType type: SocketEnginePacketType, withData data: [NSData]) + func parseEngineData(_ data: Data) + func parseEngineMessage(_ message: String, fromPolling: Bool) + func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) } extension SocketEngineSpec { - var urlPollingWithSid: NSURL { - let com = NSURLComponents(URL: urlPolling, resolvingAgainstBaseURL: false)! + var urlPollingWithSid: URL { + var com = URLComponents(url: urlPolling, resolvingAgainstBaseURL: false)! com.percentEncodedQuery = com.percentEncodedQuery! + "&sid=\(sid.urlEncode()!)" - return com.URL! + return com.url! } - var urlWebSocketWithSid: NSURL { - let com = NSURLComponents(URL: urlWebSocket, resolvingAgainstBaseURL: false)! + var urlWebSocketWithSid: URL { + var com = URLComponents(url: urlWebSocket, resolvingAgainstBaseURL: false)! com.percentEncodedQuery = com.percentEncodedQuery! + (sid == "" ? "" : "&sid=\(sid.urlEncode()!)") - return com.URL! + return com.url! } - func createBinaryDataForSend(data: NSData) -> Either { + func createBinaryDataForSend(using data: Data) -> Either { if websocket { - var byteArray = [UInt8](count: 1, repeatedValue: 0x4) + var byteArray = [UInt8](repeating: 0x4, count: 1) let mutData = NSMutableData(bytes: &byteArray, length: 1) - mutData.appendData(data) + mutData.append(data) - return .Left(mutData) + return .left(mutData as Data) } else { - let str = "b4" + data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0)) + let str = "b4" + data.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0)) - return .Right(str) + return .right(str) } } - func doubleEncodeUTF8(string: String) -> String { - if let latin1 = string.dataUsingEncoding(NSUTF8StringEncoding), - utf8 = NSString(data: latin1, encoding: NSISOLatin1StringEncoding) { + func doubleEncodeUTF8(_ string: String) -> String { + if let latin1 = string.data(using: String.Encoding.utf8), + let utf8 = NSString(data: latin1, encoding: String.Encoding.isoLatin1.rawValue) { return utf8 as String } else { return string } } - func fixDoubleUTF8(string: String) -> String { - if let utf8 = string.dataUsingEncoding(NSISOLatin1StringEncoding), - latin1 = NSString(data: utf8, encoding: NSUTF8StringEncoding) { + func fixDoubleUTF8(_ string: String) -> String { + if let utf8 = string.data(using: String.Encoding.isoLatin1), + let latin1 = NSString(data: utf8, encoding: String.Encoding.utf8.rawValue) { return latin1 as String } else { return string @@ -109,7 +109,7 @@ extension SocketEngineSpec { } /// Send an engine message (4) - func send(msg: String, withData datas: [NSData]) { - write(msg, withType: .Message, withData: datas) + func send(_ msg: String, withData datas: [Data]) { + write(msg, withType: .message, withData: datas) } } diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketEngineWebsocket.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketEngineWebsocket.swift index e1b0ba8..3c37b2b 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketEngineWebsocket.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketEngineWebsocket.swift @@ -27,36 +27,36 @@ import Foundation /// Protocol that is used to implement socket.io WebSocket support public protocol SocketEngineWebsocket : SocketEngineSpec, WebSocketDelegate { - func sendWebSocketMessage(str: String, withType type: SocketEnginePacketType, withData datas: [NSData]) + func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data]) } // WebSocket methods extension SocketEngineWebsocket { func probeWebSocket() { if ws?.isConnected ?? false { - sendWebSocketMessage("probe", withType: .Ping, withData: []) + sendWebSocketMessage("probe", withType: .ping, withData: []) } } /// Send message on WebSockets /// Only call on emitQueue - public func sendWebSocketMessage(str: String, withType type: SocketEnginePacketType, withData datas: [NSData]) { - DefaultSocketLogger.Logger.log("Sending ws: %@ as type: %@", type: "SocketEngine", args: str, type.rawValue) - - ws?.writeString("\(type.rawValue)\(str)") - - for data in datas { - if case let .Left(bin) = createBinaryDataForSend(data) { - ws?.writeData(bin) - } + public func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data]) { + DefaultSocketLogger.Logger.log("Sending ws: %@ as type: %@", type: "SocketEngine", args: str, type.rawValue) + + ws?.write(string: "\(type.rawValue)\(str)") + + for data in datas { + if case let .left(bin) = createBinaryDataForSend(using: data) { + ws?.write(data: bin) } + } } public func websocketDidReceiveMessage(socket: WebSocket, text: String) { parseEngineMessage(text, fromPolling: false) } - public func websocketDidReceiveData(socket: WebSocket, data: NSData) { + public func websocketDidReceiveData(socket: WebSocket, data: Data) { parseEngineData(data) } } diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketEventHandler.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketEventHandler.swift index 41774a9..5497f7f 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketEventHandler.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketEventHandler.swift @@ -26,10 +26,10 @@ import Foundation struct SocketEventHandler { let event: String - let id: NSUUID + let id: UUID let callback: NormalCallback - func executeCallback(items: [AnyObject], withAck ack: Int, withSocket socket: SocketIOClient) { + func executeCallback(with items: [Any], withAck ack: Int, withSocket socket: SocketIOClient) { callback(items, SocketAckEmitter(socket: socket, ackNum: ack)) } } diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketExtensions.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketExtensions.swift new file mode 100644 index 0000000..bf5280a --- /dev/null +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketExtensions.swift @@ -0,0 +1,127 @@ +// +// SocketExtensions.swift +// Socket.IO-Client-Swift +// +// Created by Erik Little on 7/1/2016. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +enum JSONError : Error { + case notArray + case notNSDictionary +} + +extension Array { + func toJSON() throws -> Data { + return try JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions(rawValue: 0)) + } +} + +extension CharacterSet { + static var allowedURLCharacterSet: CharacterSet { + return CharacterSet(charactersIn: "!*'();:@&=+$,/?%#[]\" {}").inverted + } +} + +extension NSDictionary { + private static func keyValueToSocketIOClientOption(key: String, value: Any) -> SocketIOClientOption? { + switch (key, value) { + case let ("connectParams", params as [String: Any]): + return .connectParams(params) + case let ("cookies", cookies as [HTTPCookie]): + return .cookies(cookies) + case let ("doubleEncodeUTF8", encode as Bool): + return .doubleEncodeUTF8(encode) + case let ("extraHeaders", headers as [String: String]): + return .extraHeaders(headers) + case let ("forceNew", force as Bool): + return .forceNew(force) + case let ("forcePolling", force as Bool): + return .forcePolling(force) + case let ("forceWebsockets", force as Bool): + return .forceWebsockets(force) + case let ("handleQueue", queue as DispatchQueue): + return .handleQueue(queue) + case let ("log", log as Bool): + return .log(log) + case let ("logger", logger as SocketLogger): + return .logger(logger) + case let ("nsp", nsp as String): + return .nsp(nsp) + case let ("path", path as String): + return .path(path) + case let ("reconnects", reconnects as Bool): + return .reconnects(reconnects) + case let ("reconnectAttempts", attempts as Int): + return .reconnectAttempts(attempts) + case let ("reconnectWait", wait as Int): + return .reconnectWait(wait) + case let ("secure", secure as Bool): + return .secure(secure) + case let ("security", security as SSLSecurity): + return .security(security) + case let ("selfSigned", selfSigned as Bool): + return .selfSigned(selfSigned) + case let ("sessionDelegate", delegate as URLSessionDelegate): + return .sessionDelegate(delegate) + case let ("voipEnabled", enable as Bool): + return .voipEnabled(enable) + default: + return nil + } + } + + func toSocketConfiguration() -> SocketIOClientConfiguration { + var options = [] as SocketIOClientConfiguration + + for (rawKey, value) in self { + if let key = rawKey as? String, let opt = NSDictionary.keyValueToSocketIOClientOption(key: key, value: value) { + options.insert(opt) + } + } + + return options + } +} + +extension String { + func toArray() throws -> [Any] { + guard let stringData = data(using: .utf8, allowLossyConversion: false) else { return [] } + guard let array = try JSONSerialization.jsonObject(with: stringData, options: .mutableContainers) as? [Any] else { + throw JSONError.notArray + } + + return array + } + + func toNSDictionary() throws -> NSDictionary { + guard let binData = data(using: .utf8, allowLossyConversion: false) else { return [:] } + guard let json = try JSONSerialization.jsonObject(with: binData, options: .allowFragments) as? NSDictionary else { + throw JSONError.notNSDictionary + } + + return json + } + + func urlEncode() -> String? { + return addingPercentEncoding(withAllowedCharacters: .allowedURLCharacterSet) + } +} diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClient.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClient.swift index 02cda38..22d1e52 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClient.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClient.swift @@ -25,13 +25,13 @@ import Foundation public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable { - public let socketURL: NSURL + public let socketURL: URL public private(set) var engine: SocketEngineSpec? - public private(set) var status = SocketIOClientStatus.NotConnected { + public private(set) var status = SocketIOClientStatus.notConnected { didSet { switch status { - case .Connected: + case .connected: reconnecting = false currentReconnectAttempt = 0 default: @@ -42,100 +42,105 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable public var forceNew = false public var nsp = "/" - public var options: Set + public var config: SocketIOClientConfiguration public var reconnects = true public var reconnectWait = 10 - public var sid: String? { - return nsp + "#" + (engine?.sid ?? "") - } - private let emitQueue = dispatch_queue_create("com.socketio.emitQueue", DISPATCH_QUEUE_SERIAL) private let logType = "SocketIOClient" - private let parseQueue = dispatch_queue_create("com.socketio.parseQueue", DISPATCH_QUEUE_SERIAL) + private let parseQueue = DispatchQueue(label: "com.socketio.parseQueue") private var anyHandler: ((SocketAnyEvent) -> Void)? private var currentReconnectAttempt = 0 private var handlers = [SocketEventHandler]() - private var ackHandlers = SocketAckManager() private var reconnecting = false + private let ackSemaphore = DispatchSemaphore(value: 1) private(set) var currentAck = -1 - private(set) var handleQueue = dispatch_get_main_queue() + private(set) var handleQueue = DispatchQueue.main private(set) var reconnectAttempts = -1 + + let ackQueue = DispatchQueue(label: "com.socketio.ackQueue") + let emitQueue = DispatchQueue(label: "com.socketio.emitQueue") + var ackHandlers = SocketAckManager() var waitingPackets = [SocketPacket]() + public var sid: String? { + return engine?.sid + } + /// Type safe way to create a new SocketIOClient. opts can be omitted - public init(socketURL: NSURL, options: Set = []) { - self.options = options + public init(socketURL: URL, config: SocketIOClientConfiguration = []) { + self.config = config self.socketURL = socketURL if socketURL.absoluteString.hasPrefix("https://") { - self.options.insertIgnore(.Secure(true)) + self.config.insert(.secure(true)) } - for option in options { + for option in config { switch option { - case let .Reconnects(reconnects): + case let .reconnects(reconnects): self.reconnects = reconnects - case let .ReconnectAttempts(attempts): + case let .reconnectAttempts(attempts): reconnectAttempts = attempts - case let .ReconnectWait(wait): + case let .reconnectWait(wait): reconnectWait = abs(wait) - case let .Nsp(nsp): + case let .nsp(nsp): self.nsp = nsp - case let .Log(log): + case let .log(log): DefaultSocketLogger.Logger.log = log - case let .Logger(logger): + case let .logger(logger): DefaultSocketLogger.Logger = logger - case let .HandleQueue(queue): + case let .handleQueue(queue): handleQueue = queue - case let .ForceNew(force): + case let .forceNew(force): forceNew = force default: continue } } - - self.options.insertIgnore(.Path("/socket.io/")) + + self.config.insert(.path("/socket.io/"), replacing: false) super.init() } /// Not so type safe way to create a SocketIOClient, meant for Objective-C compatiblity. /// If using Swift it's recommended to use `init(socketURL: NSURL, options: Set)` - public convenience init(socketURL: NSURL, options: NSDictionary?) { - self.init(socketURL: socketURL, options: options?.toSocketOptionsSet() ?? []) + public convenience init(socketURL: NSURL, config: NSDictionary?) { + self.init(socketURL: socketURL as URL, config: config?.toSocketConfiguration() ?? []) } deinit { DefaultSocketLogger.Logger.log("Client is being released", type: logType) - engine?.disconnect("Client Deinit") + engine?.disconnect(reason: "Client Deinit") } private func addEngine() -> SocketEngineSpec { - DefaultSocketLogger.Logger.log("Adding engine", type: logType) - - engine = SocketEngine(client: self, url: socketURL, options: options) + DefaultSocketLogger.Logger.log("Adding engine", type: logType, args: "") + + engine = SocketEngine(client: self, url: socketURL, config: config) return engine! } /// Connect to the server. public func connect() { - connect(timeoutAfter: 0, withTimeoutHandler: nil) + connect(timeoutAfter: 0, withHandler: nil) } - /// Connect to the server. If we aren't connected after timeoutAfter, call handler - public func connect(timeoutAfter timeoutAfter: Int, withTimeoutHandler handler: (() -> Void)?) { + /// Connect to the server. If we aren't connected after timeoutAfter, call withHandler + /// 0 Never times out + public func connect(timeoutAfter: Int, withHandler handler: (() -> Void)?) { assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)") - guard status != .Connected else { + guard status != .connected else { DefaultSocketLogger.Logger.log("Tried connecting on an already connected socket", type: logType) return } - status = .Connecting + status = .connecting if engine == nil || forceNew { addEngine().connect() @@ -145,40 +150,32 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable guard timeoutAfter != 0 else { return } - let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeoutAfter) * Int64(NSEC_PER_SEC)) - - dispatch_after(time, handleQueue) {[weak self] in - if let this = self where this.status != .Connected && this.status != .Disconnected { - this.status = .Disconnected - this.engine?.disconnect("Connect timeout") + let time = DispatchTime.now() + Double(UInt64(timeoutAfter) * NSEC_PER_SEC) / Double(NSEC_PER_SEC) - handler?() - } + handleQueue.asyncAfter(deadline: time) {[weak self] in + guard let this = self, this.status != .connected && this.status != .disconnected else { return } + + this.status = .disconnected + this.engine?.disconnect(reason: "Connect timeout") + + handler?() } } - private func createOnAck(items: [AnyObject]) -> OnAckCallback { + private func nextAck() -> Int { + ackSemaphore.wait() + defer { ackSemaphore.signal() } currentAck += 1 + return currentAck + } - return {[weak self, ack = currentAck] timeout, callback in - if let this = self { - this.ackHandlers.addAck(ack, callback: callback) - this._emit(items, ack: ack) - - if timeout != 0 { - let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * NSEC_PER_SEC)) - - dispatch_after(time, this.handleQueue) { - this.ackHandlers.timeoutAck(ack) - } - } - } - } + private func createOnAck(_ items: [Any]) -> OnAckCallback { + return OnAckCallback(ackNumber: nextAck(), items: items, socket: self) } func didConnect() { DefaultSocketLogger.Logger.log("Socket connected", type: logType) - status = .Connected + status = .connected // Don't handle as internal because something crazy could happen where // we disconnect before it's handled @@ -186,58 +183,55 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable } func didDisconnect(reason: String) { - guard status != .Disconnected else { return } + guard status != .disconnected else { return } DefaultSocketLogger.Logger.log("Disconnected: %@", type: logType, args: reason) - status = .Disconnected - reconnects = false + reconnecting = false + status = .disconnected // Make sure the engine is actually dead. - engine?.disconnect(reason) + engine?.disconnect(reason: reason) handleEvent("disconnect", data: [reason], isInternalMessage: true) } - /// Disconnects the socket. Only reconnect the same socket if you know what you're doing. - /// Will turn off automatic reconnects. + /// Disconnects the socket. public func disconnect() { - assert(status != .NotConnected, "Tried closing a NotConnected client") - DefaultSocketLogger.Logger.log("Closing socket", type: logType) - reconnects = false - didDisconnect("Disconnect") + didDisconnect(reason: "Disconnect") } /// Send a message to the server - public func emit(event: String, _ items: AnyObject...) { - emit(event, withItems: items) + public func emit(_ event: String, _ items: SocketData...) { + emit(event, with: items) } /// Same as emit, but meant for Objective-C - public func emit(event: String, withItems items: [AnyObject]) { - guard status == .Connected else { + public func emit(_ event: String, with items: [Any]) { + guard status == .connected else { handleEvent("error", data: ["Tried emitting \(event) when not connected"], isInternalMessage: true) return } _emit([event] + items) + } /// Sends a message to the server, requesting an ack. Use the onAck method of SocketAckHandler to add /// an ack. - public func emitWithAck(event: String, _ items: AnyObject...) -> OnAckCallback { - return emitWithAck(event, withItems: items) + public func emitWithAck(_ event: String, _ items: SocketData...) -> OnAckCallback { + return emitWithAck(event, with: items) } /// Same as emitWithAck, but for Objective-C - public func emitWithAck(event: String, withItems items: [AnyObject]) -> OnAckCallback { + public func emitWithAck(_ event: String, with items: [Any]) -> OnAckCallback { return createOnAck([event] + items) } - private func _emit(data: [AnyObject], ack: Int? = nil) { - dispatch_async(emitQueue) { - guard self.status == .Connected else { + func _emit(_ data: [Any], ack: Int? = nil) { + emitQueue.async { + guard self.status == .connected else { self.handleEvent("error", data: ["Tried emitting when not connected"], isInternalMessage: true) return } @@ -252,31 +246,33 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable } // If the server wants to know that the client received data - func emitAck(ack: Int, withItems items: [AnyObject]) { - dispatch_async(emitQueue) { - if self.status == .Connected { - let packet = SocketPacket.packetFromEmit(items, id: ack ?? -1, nsp: self.nsp, ack: true) - let str = packet.packetString - - DefaultSocketLogger.Logger.log("Emitting Ack: %@", type: self.logType, args: str) - - self.engine?.send(str, withData: packet.binary) - } + func emitAck(_ ack: Int, with items: [Any]) { + emitQueue.async { + guard self.status == .connected else { return } + + let packet = SocketPacket.packetFromEmit(items, id: ack, nsp: self.nsp, ack: true) + let str = packet.packetString + + DefaultSocketLogger.Logger.log("Emitting Ack: %@", type: self.logType, args: str) + + self.engine?.send(str, withData: packet.binary) } } public func engineDidClose(reason: String) { - waitingPackets.removeAll() + parseQueue.async { + self.waitingPackets.removeAll() + } - if status != .Disconnected { - status = .NotConnected + if status != .disconnected { + status = .notConnected } - if status == .Disconnected || !reconnects { - didDisconnect(reason) + if status == .disconnected || !reconnects { + didDisconnect(reason: reason) } else if !reconnecting { reconnecting = true - tryReconnectWithReason(reason) + tryReconnect(reason: reason) } } @@ -286,29 +282,33 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable handleEvent("error", data: [reason], isInternalMessage: true) } + + public func engineDidOpen(reason: String) { + DefaultSocketLogger.Logger.log(reason, type: "SocketEngineClient") + } // Called when the socket gets an ack for something it sent - func handleAck(ack: Int, data: [AnyObject]) { - guard status == .Connected else { return } + func handleAck(_ ack: Int, data: [Any]) { + guard status == .connected else { return } - DefaultSocketLogger.Logger.log("Handling ack: %@ with data: %@", type: logType, args: ack, data ?? "") + DefaultSocketLogger.Logger.log("Handling ack: %@ with data: %@", type: logType, args: ack, data) - ackHandlers.executeAck(ack, items: data) + handleQueue.async() { + self.ackHandlers.executeAck(ack, with: data, onQueue: self.handleQueue) + } } /// Causes an event to be handled. Only use if you know what you're doing. - public func handleEvent(event: String, data: [AnyObject], isInternalMessage: Bool, withAck ack: Int = -1) { - guard status == .Connected || isInternalMessage else { - return - } + public func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int = -1) { + guard status == .connected || isInternalMessage else { return } - DefaultSocketLogger.Logger.log("Handling event: %@ with data: %@", type: logType, args: event, data ?? "") + DefaultSocketLogger.Logger.log("Handling event: %@ with data: %@", type: logType, args: event, data) - dispatch_async(handleQueue) { + handleQueue.async { self.anyHandler?(SocketAnyEvent(event: event, items: data)) for handler in self.handlers where handler.event == event { - handler.executeCallback(data, withAck: ack, withSocket: self) + handler.executeCallback(with: data, withAck: ack, withSocket: self) } } } @@ -322,7 +322,7 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable } /// Joins namespace - public func joinNamespace(namespace: String) { + public func joinNamespace(_ namespace: String) { nsp = namespace if nsp != "/" { @@ -332,25 +332,26 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable } /// Removes handler(s) based on name - public func off(event: String) { + public func off(_ event: String) { DefaultSocketLogger.Logger.log("Removing handler for event: %@", type: logType, args: event) - handlers = handlers.filter { $0.event != event } + handlers = handlers.filter({ $0.event != event }) } /// Removes a handler with the specified UUID gotten from an `on` or `once` - public func off(id id: NSUUID) { + public func off(id: UUID) { DefaultSocketLogger.Logger.log("Removing handler with id: %@", type: logType, args: id) - handlers = handlers.filter { $0.id != id } + handlers = handlers.filter({ $0.id != id }) } /// Adds a handler for an event. /// Returns: A unique id for the handler - public func on(event: String, callback: NormalCallback) -> NSUUID { + @discardableResult + public func on(_ event: String, callback: @escaping NormalCallback) -> UUID { DefaultSocketLogger.Logger.log("Adding handler for event: %@", type: logType, args: event) - let handler = SocketEventHandler(event: event, id: NSUUID(), callback: callback) + let handler = SocketEventHandler(event: event, id: UUID(), callback: callback) handlers.append(handler) return handler.id @@ -358,10 +359,11 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable /// Adds a single-use handler for an event. /// Returns: A unique id for the handler - public func once(event: String, callback: NormalCallback) -> NSUUID { + @discardableResult + public func once(_ event: String, callback: @escaping NormalCallback) -> UUID { DefaultSocketLogger.Logger.log("Adding once handler for event: %@", type: logType, args: event) - let id = NSUUID() + let id = UUID() let handler = SocketEventHandler(event: event, id: id) {[weak self] data, ack in guard let this = self else { return } @@ -375,83 +377,75 @@ public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable } /// Adds a handler that will be called on every event. - public func onAny(handler: (SocketAnyEvent) -> Void) { + public func onAny(_ handler: @escaping (SocketAnyEvent) -> Void) { anyHandler = handler } - public func parseEngineMessage(msg: String) { + public func parseEngineMessage(_ msg: String) { DefaultSocketLogger.Logger.log("Should parse message: %@", type: "SocketIOClient", args: msg) - dispatch_async(parseQueue) { - self.parseSocketMessage(msg) - } + parseQueue.async { self.parseSocketMessage(msg) } } - public func parseEngineBinaryData(data: NSData) { - dispatch_async(parseQueue) { - self.parseBinaryData(data) - } + public func parseEngineBinaryData(_ data: Data) { + parseQueue.async { self.parseBinaryData(data) } } /// Tries to reconnect to the server. public func reconnect() { guard !reconnecting else { return } - engine?.disconnect("manual reconnect") + engine?.disconnect(reason: "manual reconnect") } /// Removes all handlers. /// Can be used after disconnecting to break any potential remaining retain cycles. public func removeAllHandlers() { - handlers.removeAll(keepCapacity: false) + handlers.removeAll(keepingCapacity: false) } - private func tryReconnectWithReason(reason: String) { - if reconnecting { - DefaultSocketLogger.Logger.log("Starting reconnect", type: logType) - handleEvent("reconnect", data: [reason], isInternalMessage: true) - - _tryReconnect() - } + private func tryReconnect(reason: String) { + guard reconnecting else { return } + + DefaultSocketLogger.Logger.log("Starting reconnect", type: logType) + handleEvent("reconnect", data: [reason], isInternalMessage: true) + + _tryReconnect() } private func _tryReconnect() { - if !reconnecting { - return - } + guard reconnecting else { return } if reconnectAttempts != -1 && currentReconnectAttempt + 1 > reconnectAttempts || !reconnects { - return didDisconnect("Reconnect Failed") + return didDisconnect(reason: "Reconnect Failed") } DefaultSocketLogger.Logger.log("Trying to reconnect", type: logType) - handleEvent("reconnectAttempt", data: [reconnectAttempts - currentReconnectAttempt], - isInternalMessage: true) + handleEvent("reconnectAttempt", data: [(reconnectAttempts - currentReconnectAttempt)], isInternalMessage: true) currentReconnectAttempt += 1 connect() - let dispatchAfter = dispatch_time(DISPATCH_TIME_NOW, Int64(UInt64(reconnectWait) * NSEC_PER_SEC)) + let deadline = DispatchTime.now() + Double(Int64(UInt64(reconnectWait) * NSEC_PER_SEC)) / Double(NSEC_PER_SEC) - dispatch_after(dispatchAfter, dispatch_get_main_queue(), _tryReconnect) + DispatchQueue.main.asyncAfter(deadline: deadline, execute: _tryReconnect) } -} - -// Test extensions -extension SocketIOClient { + + // Test properties + var testHandlers: [SocketEventHandler] { return handlers } - + func setTestable() { - status = .Connected + status = .connected } - - func setTestEngine(engine: SocketEngineSpec?) { + + func setTestEngine(_ engine: SocketEngineSpec?) { self.engine = engine } - - func emitTest(event: String, _ data: AnyObject...) { - self._emit([event] + data) + + func emitTest(event: String, _ data: Any...) { + _emit([event] + data) } } diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClientConfiguration.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClientConfiguration.swift new file mode 100644 index 0000000..4fc45ba --- /dev/null +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClientConfiguration.swift @@ -0,0 +1,108 @@ +// +// SocketIOClientConfiguration.swift +// Socket.IO-Client-Swift +// +// Created by Erik Little on 8/13/16. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +public struct SocketIOClientConfiguration : ExpressibleByArrayLiteral, Collection, MutableCollection { + public typealias Element = SocketIOClientOption + public typealias Index = Array.Index + public typealias Generator = Array.Iterator + public typealias SubSequence = Array.SubSequence + + private var backingArray = [SocketIOClientOption]() + + public var startIndex: Index { + return backingArray.startIndex + } + + public var endIndex: Index { + return backingArray.endIndex + } + + public var isEmpty: Bool { + return backingArray.isEmpty + } + + public var count: Index.Stride { + return backingArray.count + } + + public var first: Generator.Element? { + return backingArray.first + } + + public subscript(position: Index) -> Generator.Element { + get { + return backingArray[position] + } + + set { + backingArray[position] = newValue + } + } + + public subscript(bounds: Range) -> SubSequence { + get { + return backingArray[bounds] + } + + set { + backingArray[bounds] = newValue + } + } + + public init(arrayLiteral elements: Element...) { + backingArray = elements + } + + public func generate() -> Generator { + return backingArray.makeIterator() + } + + public func index(after i: Index) -> Index { + return backingArray.index(after: i) + } + + public mutating func insert(_ element: Element, replacing replace: Bool = true) { + for i in 0.. SubSequence { + return backingArray.prefix(upTo: end) + } + + public func prefix(through position: Index) -> SubSequence { + return backingArray.prefix(through: position) + } + + public func suffix(from start: Index) -> SubSequence { + return backingArray.suffix(from: start) + } +} diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClientOption.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClientOption.swift index 93626f5..dd723f4 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClientOption.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClientOption.swift @@ -24,123 +24,124 @@ import Foundation -protocol ClientOption : CustomStringConvertible, Hashable { - func getSocketIOOptionValue() -> AnyObject +protocol ClientOption : CustomStringConvertible, Equatable { + func getSocketIOOptionValue() -> Any } public enum SocketIOClientOption : ClientOption { - case ConnectParams([String: AnyObject]) - case Cookies([NSHTTPCookie]) - case DoubleEncodeUTF8(Bool) - case ExtraHeaders([String: String]) - case ForceNew(Bool) - case ForcePolling(Bool) - case ForceWebsockets(Bool) - case HandleQueue(dispatch_queue_t) - case Log(Bool) - case Logger(SocketLogger) - case Nsp(String) - case Path(String) - case Reconnects(Bool) - case ReconnectAttempts(Int) - case ReconnectWait(Int) - case Secure(Bool) - case SelfSigned(Bool) - case SessionDelegate(NSURLSessionDelegate) - case VoipEnabled(Bool) + case connectParams([String: Any]) + case cookies([HTTPCookie]) + case doubleEncodeUTF8(Bool) + case extraHeaders([String: String]) + case forceNew(Bool) + case forcePolling(Bool) + case forceWebsockets(Bool) + case handleQueue(DispatchQueue) + case log(Bool) + case logger(SocketLogger) + case nsp(String) + case path(String) + case reconnects(Bool) + case reconnectAttempts(Int) + case reconnectWait(Int) + case secure(Bool) + case security(SSLSecurity) + case selfSigned(Bool) + case sessionDelegate(URLSessionDelegate) + case voipEnabled(Bool) public var description: String { let description: String switch self { - case .ConnectParams: + case .connectParams: description = "connectParams" - case .Cookies: + case .cookies: description = "cookies" - case .DoubleEncodeUTF8: + case .doubleEncodeUTF8: description = "doubleEncodeUTF8" - case .ExtraHeaders: + case .extraHeaders: description = "extraHeaders" - case .ForceNew: + case .forceNew: description = "forceNew" - case .ForcePolling: + case .forcePolling: description = "forcePolling" - case .ForceWebsockets: + case .forceWebsockets: description = "forceWebsockets" - case .HandleQueue: + case .handleQueue: description = "handleQueue" - case .Log: + case .log: description = "log" - case .Logger: + case .logger: description = "logger" - case .Nsp: + case .nsp: description = "nsp" - case .Path: + case .path: description = "path" - case .Reconnects: + case .reconnects: description = "reconnects" - case .ReconnectAttempts: + case .reconnectAttempts: description = "reconnectAttempts" - case .ReconnectWait: + case .reconnectWait: description = "reconnectWait" - case .Secure: + case .secure: description = "secure" - case .SelfSigned: + case .selfSigned: description = "selfSigned" - case .SessionDelegate: + case .security: + description = "security" + case .sessionDelegate: description = "sessionDelegate" - case .VoipEnabled: + case .voipEnabled: description = "voipEnabled" } return description } - public var hashValue: Int { - return description.hashValue - } - - func getSocketIOOptionValue() -> AnyObject { - let value: AnyObject + func getSocketIOOptionValue() -> Any { + let value: Any switch self { - case let .ConnectParams(params): + case let .connectParams(params): value = params - case let .Cookies(cookies): + case let .cookies(cookies): value = cookies - case let .DoubleEncodeUTF8(encode): + case let .doubleEncodeUTF8(encode): value = encode - case let .ExtraHeaders(headers): + case let .extraHeaders(headers): value = headers - case let .ForceNew(force): + case let .forceNew(force): value = force - case let .ForcePolling(force): + case let .forcePolling(force): value = force - case let .ForceWebsockets(force): + case let .forceWebsockets(force): value = force - case let .HandleQueue(queue): + case let .handleQueue(queue): value = queue - case let .Log(log): + case let .log(log): value = log - case let .Logger(logger): + case let .logger(logger): value = logger - case let .Nsp(nsp): + case let .nsp(nsp): value = nsp - case let .Path(path): + case let .path(path): value = path - case let .Reconnects(reconnects): + case let .reconnects(reconnects): value = reconnects - case let .ReconnectAttempts(attempts): + case let .reconnectAttempts(attempts): value = attempts - case let .ReconnectWait(wait): + case let .reconnectWait(wait): value = wait - case let .Secure(secure): + case let .secure(secure): value = secure - case let .SelfSigned(signed): + case let .security(security): + value = security + case let .selfSigned(signed): value = signed - case let .SessionDelegate(delegate): + case let .sessionDelegate(delegate): value = delegate - case let .VoipEnabled(enabled): + case let .voipEnabled(enabled): value = enabled } @@ -151,70 +152,3 @@ public enum SocketIOClientOption : ClientOption { public func ==(lhs: SocketIOClientOption, rhs: SocketIOClientOption) -> Bool { return lhs.description == rhs.description } - -extension Set where Element : ClientOption { - mutating func insertIgnore(element: Element) { - if !contains(element) { - insert(element) - } - } -} - -extension NSDictionary { - private static func keyValueToSocketIOClientOption(key: String, value: AnyObject) -> SocketIOClientOption? { - switch (key, value) { - case let ("connectParams", params as [String: AnyObject]): - return .ConnectParams(params) - case let ("cookies", cookies as [NSHTTPCookie]): - return .Cookies(cookies) - case let ("doubleEncodeUTF8", encode as Bool): - return .DoubleEncodeUTF8(encode) - case let ("extraHeaders", headers as [String: String]): - return .ExtraHeaders(headers) - case let ("forceNew", force as Bool): - return .ForceNew(force) - case let ("forcePolling", force as Bool): - return .ForcePolling(force) - case let ("forceWebsockets", force as Bool): - return .ForceWebsockets(force) - case let ("handleQueue", queue as dispatch_queue_t): - return .HandleQueue(queue) - case let ("log", log as Bool): - return .Log(log) - case let ("logger", logger as SocketLogger): - return .Logger(logger) - case let ("nsp", nsp as String): - return .Nsp(nsp) - case let ("path", path as String): - return .Path(path) - case let ("reconnects", reconnects as Bool): - return .Reconnects(reconnects) - case let ("reconnectAttempts", attempts as Int): - return .ReconnectAttempts(attempts) - case let ("reconnectWait", wait as Int): - return .ReconnectWait(wait) - case let ("secure", secure as Bool): - return .Secure(secure) - case let ("selfSigned", selfSigned as Bool): - return .SelfSigned(selfSigned) - case let ("sessionDelegate", delegate as NSURLSessionDelegate): - return .SessionDelegate(delegate) - case let ("voipEnabled", enable as Bool): - return .VoipEnabled(enable) - default: - return nil - } - } - - func toSocketOptionsSet() -> Set { - var options = Set() - - for (rawKey, value) in self { - if let key = rawKey as? String, opt = NSDictionary.keyValueToSocketIOClientOption(key, value: value) { - options.insertIgnore(opt) - } - } - - return options - } -} diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClientSpec.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClientSpec.swift index 8b33cf9..e91c840 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClientSpec.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClientSpec.swift @@ -29,9 +29,9 @@ protocol SocketIOClientSpec : class { func didConnect() func didDisconnect(reason: String) func didError(reason: String) - func handleAck(ack: Int, data: [AnyObject]) - func handleEvent(event: String, data: [AnyObject], isInternalMessage: Bool, withAck ack: Int) - func joinNamespace(namespace: String) + func handleAck(_ ack: Int, data: [Any]) + func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int) + func joinNamespace(_ namespace: String) } extension SocketIOClientSpec { diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClientStatus.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClientStatus.swift index 0a34c2f..27574e6 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClientStatus.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketIOClientStatus.swift @@ -28,5 +28,5 @@ import Foundation /// /// **Disconnected**: connected before @objc public enum SocketIOClientStatus : Int { - case NotConnected, Disconnected, Connecting, Connected + case notConnected, disconnected, connecting, connected } diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketLogger.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketLogger.swift index bff9d4e..640d344 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketLogger.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketLogger.swift @@ -29,28 +29,28 @@ public protocol SocketLogger : class { var log: Bool { get set } /// Normal log messages - func log(message: String, type: String, args: AnyObject...) + func log(_ message: String, type: String, args: Any...) /// Error Messages - func error(message: String, type: String, args: AnyObject...) + func error(_ message: String, type: String, args: Any...) } public extension SocketLogger { - func log(message: String, type: String, args: AnyObject...) { + func log(_ message: String, type: String, args: Any...) { abstractLog("LOG", message: message, type: type, args: args) } - func error(message: String, type: String, args: AnyObject...) { + func error(_ message: String, type: String, args: Any...) { abstractLog("ERROR", message: message, type: type, args: args) } - private func abstractLog(logType: String, message: String, type: String, args: [AnyObject]) { + private func abstractLog(_ logType: String, message: String, type: String, args: [Any]) { guard log else { return } - let newArgs = args.map({arg -> CVarArgType in String(arg)}) - let replaced = String(format: message, arguments: newArgs) + let newArgs = args.map({arg -> CVarArg in String(describing: arg)}) + let messageFormat = String(format: message, arguments: newArgs) - NSLog("%@ %@: %@", logType, type, replaced) + NSLog("\(logType) \(type): %@", messageFormat) } } diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketPacket.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketPacket.swift index 52de38a..d88ef4c 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketPacket.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketPacket.swift @@ -26,6 +26,10 @@ import Foundation struct SocketPacket { + enum PacketType: Int { + case connect, disconnect, event, ack, error, binaryEvent, binaryAck + } + private let placeholders: Int private static let logType = "SocketPacket" @@ -34,35 +38,31 @@ struct SocketPacket { let id: Int let type: PacketType - enum PacketType: Int { - case Connect, Disconnect, Event, Ack, Error, BinaryEvent, BinaryAck - } - - var args: [AnyObject] { - if type == .Event || type == .BinaryEvent && data.count != 0 { + var binary: [Data] + var data: [Any] + var args: [Any] { + if type == .event || type == .binaryEvent && data.count != 0 { return Array(data.dropFirst()) } else { return data } } - var binary: [NSData] - var data: [AnyObject] var description: String { return "SocketPacket {type: \(String(type.rawValue)); data: " + - "\(String(data)); id: \(id); placeholders: \(placeholders); nsp: \(nsp)}" + "\(String(describing: data)); id: \(id); placeholders: \(placeholders); nsp: \(nsp)}" } var event: String { - return String(data[0]) + return String(describing: data[0]) } var packetString: String { return createPacketString() } - init(type: SocketPacket.PacketType, data: [AnyObject] = [AnyObject](), id: Int = -1, - nsp: String, placeholders: Int = 0, binary: [NSData] = [NSData]()) { + init(type: PacketType, data: [Any] = [Any](), id: Int = -1, nsp: String, placeholders: Int = 0, + binary: [Data] = [Data]()) { self.data = data self.id = id self.nsp = nsp @@ -71,7 +71,7 @@ struct SocketPacket { self.binary = binary } - mutating func addData(data: NSData) -> Bool { + mutating func addData(_ data: Data) -> Bool { if placeholders == binary.count { return true } @@ -86,98 +86,31 @@ struct SocketPacket { } } - private func completeMessage(message: String) -> String { - let restOfMessage: String - + private func completeMessage(_ message: String) -> String { if data.count == 0 { return message + "[]" } - do { - let jsonSend = try NSJSONSerialization.dataWithJSONObject(data, - options: NSJSONWritingOptions(rawValue: 0)) - guard let jsonString = String(data: jsonSend, encoding: NSUTF8StringEncoding) else { - return "[]" - } - - restOfMessage = jsonString - } catch { + guard let jsonSend = try? data.toJSON(), let jsonString = String(data: jsonSend, encoding: .utf8) else { DefaultSocketLogger.Logger.error("Error creating JSON object in SocketPacket.completeMessage", - type: SocketPacket.logType) + type: SocketPacket.logType) - restOfMessage = "[]" - } - - return message + restOfMessage - } - - private func createAck() -> String { - let message: String - - if type == .Ack { - if nsp == "/" { - message = "3\(id)" - } else { - message = "3\(nsp),\(id)" - } - } else { - if nsp == "/" { - message = "6\(binary.count)-\(id)" - } else { - message = "6\(binary.count)-\(nsp),\(id)" - } - } - - return completeMessage(message) - } - - - private func createMessageForEvent() -> String { - let message: String - - if type == .Event { - if nsp == "/" { - if id == -1 { - message = "2" - } else { - message = "2\(id)" - } - } else { - if id == -1 { - message = "2\(nsp)," - } else { - message = "2\(nsp),\(id)" - } - } - } else { - if nsp == "/" { - if id == -1 { - message = "5\(binary.count)-" - } else { - message = "5\(binary.count)-\(id)" - } - } else { - if id == -1 { - message = "5\(binary.count)-\(nsp)," - } else { - message = "5\(binary.count)-\(nsp),\(id)" - } - } + return message + "[]" } - return completeMessage(message) + return message + jsonString } private func createPacketString() -> String { - let str: String - - if type == .Event || type == .BinaryEvent { - str = createMessageForEvent() - } else { - str = createAck() - } + let typeString = String(type.rawValue) + // Binary count? + let binaryCountString = typeString + (type == .binaryEvent || type == .binaryAck ? "\(String(binary.count))-" : "") + // Namespace? + let nspString = binaryCountString + (nsp != "/" ? "\(nsp)," : "") + // Ack number? + let idString = nspString + (id != -1 ? String(id) : "") - return str + return completeMessage(idString) } // Called when we have all the binary data for a packet @@ -187,20 +120,25 @@ struct SocketPacket { data = data.map(_fillInPlaceholders) } - // Helper method that looks for placeholder strings + // Helper method that looks for placeholders // If object is a collection it will recurse - // Returns the object if it is not a placeholder string or the corresponding + // Returns the object if it is not a placeholder or the corresponding // binary data - private func _fillInPlaceholders(object: AnyObject) -> AnyObject { + private func _fillInPlaceholders(_ object: Any) -> Any { switch object { - case let string as String where string["~~(\\d)"].groups() != nil: - return binary[Int(string["~~(\\d)"].groups()![1])!] - case let dict as NSDictionary: - return dict.reduce(NSMutableDictionary(), combine: {cur, keyValue in - cur[keyValue.0 as! NSCopying] = _fillInPlaceholders(keyValue.1) - return cur - }) - case let arr as [AnyObject]: + case let dict as JSON: + if dict["_placeholder"] as? Bool ?? false { + return binary[dict["num"] as! Int] + } else { + return dict.reduce(JSON(), {cur, keyValue in + var cur = cur + + cur[keyValue.0] = _fillInPlaceholders(keyValue.1) + + return cur + }) + } + case let arr as [Any]: return arr.map(_fillInPlaceholders) default: return object @@ -209,25 +147,25 @@ struct SocketPacket { } extension SocketPacket { - private static func findType(binCount: Int, ack: Bool) -> PacketType { + private static func findType(_ binCount: Int, ack: Bool) -> PacketType { switch binCount { case 0 where !ack: - return .Event + return .event case 0 where ack: - return .Ack + return .ack case _ where !ack: - return .BinaryEvent + return .binaryEvent case _ where ack: - return .BinaryAck + return .binaryAck default: - return .Error + return .error } } - static func packetFromEmit(items: [AnyObject], id: Int, nsp: String, ack: Bool) -> SocketPacket { + static func packetFromEmit(_ items: [Any], id: Int, nsp: String, ack: Bool) -> SocketPacket { let (parsedData, binary) = deconstructData(items) let packet = SocketPacket(type: findType(binary.count, ack: ack), data: parsedData, - id: id, nsp: nsp, placeholders: -1, binary: binary) + id: id, nsp: nsp, binary: binary) return packet } @@ -235,19 +173,23 @@ extension SocketPacket { private extension SocketPacket { // Recursive function that looks for NSData in collections - static func shred(data: AnyObject, inout binary: [NSData]) -> AnyObject { - let placeholder = ["_placeholder": true, "num": binary.count] + static func shred(_ data: Any, binary: inout [Data]) -> Any { + let placeholder = ["_placeholder": true, "num": binary.count] as JSON switch data { - case let bin as NSData: + case let bin as Data: binary.append(bin) + return placeholder - case let arr as [AnyObject]: + case let arr as [Any]: return arr.map({shred($0, binary: &binary)}) - case let dict as NSDictionary: - return dict.reduce(NSMutableDictionary(), combine: {cur, keyValue in - cur[keyValue.0 as! NSCopying] = shred(keyValue.1, binary: &binary) - return cur + case let dict as JSON: + return dict.reduce(JSON(), {cur, keyValue in + var mutCur = cur + + mutCur[keyValue.0] = shred(keyValue.1, binary: &binary) + + return mutCur }) default: return data @@ -256,8 +198,8 @@ private extension SocketPacket { // Removes binary data from emit data // Returns a type containing the de-binaryed data and the binary - static func deconstructData(data: [AnyObject]) -> ([AnyObject], [NSData]) { - var binary = [NSData]() + static func deconstructData(_ data: [Any]) -> ([Any], [Data]) { + var binary = [Data]() return (data.map({shred($0, binary: &binary)}), binary) } diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketParsable.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketParsable.swift index c74b160..7c9ce21 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketParsable.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketParsable.swift @@ -23,40 +23,38 @@ import Foundation protocol SocketParsable : SocketIOClientSpec { - func parseBinaryData(data: NSData) - func parseSocketMessage(message: String) + func parseBinaryData(_ data: Data) + func parseSocketMessage(_ message: String) } extension SocketParsable { - private func isCorrectNamespace(nsp: String) -> Bool { + private func isCorrectNamespace(_ nsp: String) -> Bool { return nsp == self.nsp } - private func handleConnect(p: SocketPacket) { - if p.nsp == "/" && nsp != "/" { + private func handleConnect(_ packetNamespace: String) { + if packetNamespace == "/" && nsp != "/" { joinNamespace(nsp) - } else if p.nsp != "/" && nsp == "/" { - didConnect() } else { didConnect() } } - private func handlePacket(pack: SocketPacket) { + private func handlePacket(_ pack: SocketPacket) { switch pack.type { - case .Event where isCorrectNamespace(pack.nsp): + case .event where isCorrectNamespace(pack.nsp): handleEvent(pack.event, data: pack.args, isInternalMessage: false, withAck: pack.id) - case .Ack where isCorrectNamespace(pack.nsp): + case .ack where isCorrectNamespace(pack.nsp): handleAck(pack.id, data: pack.data) - case .BinaryEvent where isCorrectNamespace(pack.nsp): + case .binaryEvent where isCorrectNamespace(pack.nsp): waitingPackets.append(pack) - case .BinaryAck where isCorrectNamespace(pack.nsp): + case .binaryAck where isCorrectNamespace(pack.nsp): waitingPackets.append(pack) - case .Connect: - handleConnect(pack) - case .Disconnect: - didDisconnect("Got Disconnect") - case .Error: + case .connect: + handleConnect(pack.nsp) + case .disconnect: + didDisconnect(reason: "Got Disconnect") + case .error: handleEvent("error", data: pack.data, isInternalMessage: true, withAck: pack.id) default: DefaultSocketLogger.Logger.log("Got invalid packet: %@", type: "SocketParser", args: pack.description) @@ -64,117 +62,105 @@ extension SocketParsable { } /// Parses a messsage from the engine. Returning either a string error or a complete SocketPacket - func parseString(message: String) -> Either { - var parser = SocketStringReader(message: message) + func parseString(_ message: String) -> Either { + var reader = SocketStringReader(message: message) - guard let type = SocketPacket.PacketType(rawValue: Int(parser.read(1)) ?? -1) else { - return .Left("Invalid packet type") + guard let type = Int(reader.read(count: 1)).flatMap({ SocketPacket.PacketType(rawValue: $0) }) else { + return .left("Invalid packet type") } - if !parser.hasNext { - return .Right(SocketPacket(type: type, nsp: "/")) + if !reader.hasNext { + return .right(SocketPacket(type: type, nsp: "/")) } var namespace = "/" var placeholders = -1 - if type == .BinaryEvent || type == .BinaryAck { - if let holders = Int(parser.readUntilStringOccurence("-")) { + if type == .binaryEvent || type == .binaryAck { + if let holders = Int(reader.readUntilOccurence(of: "-")) { placeholders = holders } else { - return .Left("Invalid packet") + return .left("Invalid packet") } } - if parser.currentCharacter == "/" { - namespace = parser.readUntilStringOccurence(",") ?? parser.readUntilEnd() + if reader.currentCharacter == "/" { + namespace = reader.readUntilOccurence(of: ",") } - if !parser.hasNext { - return .Right(SocketPacket(type: type, id: -1, - nsp: namespace ?? "/", placeholders: placeholders)) + if !reader.hasNext { + return .right(SocketPacket(type: type, nsp: namespace, placeholders: placeholders)) } var idString = "" - if type == .Error { - parser.advanceIndexBy(-1) - } - - while parser.hasNext && type != .Error { - if let int = Int(parser.read(1)) { - idString += String(int) - } else { - parser.advanceIndexBy(-2) - break + if type == .error { + reader.advance(by: -1) + } else { + while reader.hasNext { + if let int = Int(reader.read(count: 1)) { + idString += String(int) + } else { + reader.advance(by: -2) + break + } } } - let d = message[parser.currentIndex.advancedBy(1).. Either { - let stringData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) - + private func parseData(_ data: String) -> Either { do { - if let arr = try NSJSONSerialization.JSONObjectWithData(stringData!, - options: NSJSONReadingOptions.MutableContainers) as? [AnyObject] { - return .Right(arr) - } else { - return .Left("Expected data array") - } + return .right(try data.toArray()) } catch { - return .Left("Error parsing data for packet") + return .left("Error parsing data for packet") } } // Parses messages recieved - func parseSocketMessage(message: String) { + func parseSocketMessage(_ message: String) { guard !message.isEmpty else { return } DefaultSocketLogger.Logger.log("Parsing %@", type: "SocketParser", args: message) switch parseString(message) { - case let .Left(err): + case let .left(err): DefaultSocketLogger.Logger.error("\(err): %@", type: "SocketParser", args: message) - case let .Right(pack): + case let .right(pack): DefaultSocketLogger.Logger.log("Decoded packet as: %@", type: "SocketParser", args: pack.description) handlePacket(pack) } } - func parseBinaryData(data: NSData) { + func parseBinaryData(_ data: Data) { guard !waitingPackets.isEmpty else { DefaultSocketLogger.Logger.error("Got data when not remaking packet", type: "SocketParser") return } // Should execute event? - guard waitingPackets[waitingPackets.count - 1].addData(data) else { - return - } + guard waitingPackets[waitingPackets.count - 1].addData(data) else { return } let packet = waitingPackets.removeLast() - if packet.type != .BinaryAck { - handleEvent(packet.event, data: packet.args ?? [], - isInternalMessage: false, withAck: packet.id) + if packet.type != .binaryAck { + handleEvent(packet.event, data: packet.args, isInternalMessage: false, withAck: packet.id) } else { handleAck(packet.id, data: packet.args) } diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketStringReader.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketStringReader.swift index d1e2b59..8bdb4d4 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketStringReader.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketStringReader.swift @@ -38,31 +38,36 @@ struct SocketStringReader { currentIndex = message.startIndex } - mutating func advanceIndexBy(n: Int) { - currentIndex = currentIndex.advancedBy(n) + @discardableResult + mutating func advance(by: Int) -> String.Index { + currentIndex = message.characters.index(currentIndex, offsetBy: by) + + return currentIndex } - mutating func read(readLength: Int) -> String { - let readString = message[currentIndex.. String { + let readString = message[currentIndex.. String { + mutating func readUntilOccurence(of string: String) -> String { let substring = message[currentIndex.. String { - return read(currentIndex.distanceTo(message.endIndex)) + return read(count: message.characters.distance(from: currentIndex, to: message.endIndex)) } } diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SocketTypes.swift b/ios/RNSwiftSocketIO/SocketIOClient/SocketTypes.swift index b8840be..cc194a7 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/SocketTypes.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/SocketTypes.swift @@ -24,11 +24,29 @@ import Foundation -public typealias AckCallback = ([AnyObject]) -> Void -public typealias NormalCallback = ([AnyObject], SocketAckEmitter) -> Void -public typealias OnAckCallback = (timeoutAfter: UInt64, callback: AckCallback) -> Void +public protocol SocketData {} + +extension Array : SocketData {} +extension Bool : SocketData {} +extension Dictionary : SocketData {} +extension Double : SocketData {} +extension Int : SocketData {} +extension NSArray : SocketData {} +extension Data : SocketData {} +extension NSData : SocketData {} +extension NSDictionary : SocketData {} +extension NSString : SocketData {} +extension NSNull : SocketData {} +extension String : SocketData {} + +public typealias AckCallback = ([Any]) -> Void +public typealias NormalCallback = ([Any], SocketAckEmitter) -> Void + +typealias JSON = [String: Any] +typealias Probe = (msg: String, type: SocketEnginePacketType, data: [Data]) +typealias ProbeWaitQueue = [Probe] enum Either { - case Left(E) - case Right(V) + case left(E) + case right(V) } diff --git a/ios/RNSwiftSocketIO/SocketIOClient/String.swift b/ios/RNSwiftSocketIO/SocketIOClient/String.swift deleted file mode 100644 index 0e30e8c..0000000 --- a/ios/RNSwiftSocketIO/SocketIOClient/String.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// String.swift -// Socket.IO-Client-Swift -// -// Created by Yannick Loriot on 5/4/16. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import Foundation - -extension String { - func urlEncode() -> String? { - return stringByAddingPercentEncodingWithAllowedCharacters(.allowedURLCharacterSet) - } -} diff --git a/ios/RNSwiftSocketIO/SocketIOClient/SwiftRegex.swift b/ios/RNSwiftSocketIO/SocketIOClient/SwiftRegex.swift deleted file mode 100644 index b704afd..0000000 --- a/ios/RNSwiftSocketIO/SocketIOClient/SwiftRegex.swift +++ /dev/null @@ -1,195 +0,0 @@ -// -// SwiftRegex.swift -// SwiftRegex -// -// Created by John Holdsworth on 26/06/2014. -// Copyright (c) 2014 John Holdsworth. -// -// $Id: //depot/SwiftRegex/SwiftRegex.swift#37 $ -// -// This code is in the public domain from: -// https://github.com/johnno1962/SwiftRegex -// - -import Foundation - -infix operator <~ { associativity none precedence 130 } - -private let lock = dispatch_semaphore_create(1) -private var swiftRegexCache = [String: NSRegularExpression]() - -internal final class SwiftRegex : NSObject, BooleanType { - var target: String - var regex: NSRegularExpression - - init(target:String, pattern:String, options:NSRegularExpressionOptions?) { - self.target = target - - if dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC))) != 0 { - do { - let regex = try NSRegularExpression(pattern: pattern, options: - NSRegularExpressionOptions.DotMatchesLineSeparators) - self.regex = regex - } catch let error as NSError { - SwiftRegex.failure("Error in pattern: \(pattern) - \(error)") - self.regex = NSRegularExpression() - } - - super.init() - return - } - - if let regex = swiftRegexCache[pattern] { - self.regex = regex - } else { - do { - let regex = try NSRegularExpression(pattern: pattern, options: - NSRegularExpressionOptions.DotMatchesLineSeparators) - swiftRegexCache[pattern] = regex - self.regex = regex - } catch let error as NSError { - SwiftRegex.failure("Error in pattern: \(pattern) - \(error)") - self.regex = NSRegularExpression() - } - } - dispatch_semaphore_signal(lock) - super.init() - } - - private static func failure(message: String) { - fatalError("SwiftRegex: \(message)") - } - - private var targetRange: NSRange { - return NSRange(location: 0,length: target.utf16.count) - } - - private func substring(range: NSRange) -> String? { - if range.location != NSNotFound { - return (target as NSString).substringWithRange(range) - } else { - return nil - } - } - - func doesMatch(options: NSMatchingOptions!) -> Bool { - return range(options).location != NSNotFound - } - - func range(options: NSMatchingOptions) -> NSRange { - return regex.rangeOfFirstMatchInString(target as String, options: [], range: targetRange) - } - - func match(options: NSMatchingOptions) -> String? { - return substring(range(options)) - } - - func groups() -> [String]? { - return groupsForMatch(regex.firstMatchInString(target as String, options: - NSMatchingOptions.WithoutAnchoringBounds, range: targetRange)) - } - - private func groupsForMatch(match: NSTextCheckingResult?) -> [String]? { - guard let match = match else { - return nil - } - var groups = [String]() - for groupno in 0...regex.numberOfCaptureGroups { - if let group = substring(match.rangeAtIndex(groupno)) { - groups += [group] - } else { - groups += ["_"] // avoids bridging problems - } - } - return groups - } - - subscript(groupno: Int) -> String? { - get { - return groups()?[groupno] - } - - set(newValue) { - if newValue == nil { - return - } - - for match in Array(matchResults().reverse()) { - let replacement = regex.replacementStringForResult(match, - inString: target as String, offset: 0, template: newValue!) - let mut = NSMutableString(string: target) - mut.replaceCharactersInRange(match.rangeAtIndex(groupno), withString: replacement) - - target = mut as String - } - } - } - - func matchResults() -> [NSTextCheckingResult] { - let matches = regex.matchesInString(target as String, options: - NSMatchingOptions.WithoutAnchoringBounds, range: targetRange) - as [NSTextCheckingResult] - - return matches - } - - func ranges() -> [NSRange] { - return matchResults().map { $0.range } - } - - func matches() -> [String] { - return matchResults().map( { self.substring($0.range)!}) - } - - func allGroups() -> [[String]?] { - return matchResults().map { self.groupsForMatch($0) } - } - - func dictionary(options: NSMatchingOptions!) -> Dictionary { - var out = Dictionary() - for match in matchResults() { - out[substring(match.rangeAtIndex(1))!] = substring(match.rangeAtIndex(2))! - } - return out - } - - func substituteMatches(substitution: ((NSTextCheckingResult, UnsafeMutablePointer) -> String), - options:NSMatchingOptions) -> String { - let out = NSMutableString() - var pos = 0 - - regex.enumerateMatchesInString(target as String, options: options, range: targetRange ) {match, flags, stop in - let matchRange = match!.range - out.appendString( self.substring(NSRange(location:pos, length:matchRange.location-pos))!) - out.appendString( substitution(match!, stop) ) - pos = matchRange.location + matchRange.length - } - - out.appendString(substring(NSRange(location:pos, length:targetRange.length-pos))!) - - return out as String - } - - var boolValue: Bool { - return doesMatch(nil) - } -} - -extension String { - subscript(pattern: String, options: NSRegularExpressionOptions) -> SwiftRegex { - return SwiftRegex(target: self, pattern: pattern, options: options) - } -} - -extension String { - subscript(pattern: String) -> SwiftRegex { - return SwiftRegex(target: self, pattern: pattern, options: nil) - } -} - -func <~ (left: SwiftRegex, right: String) -> String { - return left.substituteMatches({match, stop in - return left.regex.replacementStringForResult( match, - inString: left.target as String, offset: 0, template: right ) - }, options: []) -} diff --git a/ios/RNSwiftSocketIO/SocketIOClient/WebSocket.swift b/ios/RNSwiftSocketIO/SocketIOClient/WebSocket.swift index 833eece..34152af 100644 --- a/ios/RNSwiftSocketIO/SocketIOClient/WebSocket.swift +++ b/ios/RNSwiftSocketIO/SocketIOClient/WebSocket.swift @@ -3,7 +3,7 @@ // Websocket.swift // // Created by Dalton Cherry on 7/16/14. -// Copyright (c) 2014-2015 Dalton Cherry. +// Copyright (c) 2014-2016 Dalton Cherry. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,60 +18,64 @@ // limitations under the License. // ////////////////////////////////////////////////////////////////////////////////////////////////// - import Foundation import CoreFoundation import Security +public let WebsocketDidConnectNotification = "WebsocketDidConnectNotification" +public let WebsocketDidDisconnectNotification = "WebsocketDidDisconnectNotification" +public let WebsocketDisconnectionErrorKeyName = "WebsocketDisconnectionErrorKeyName" + public protocol WebSocketDelegate: class { func websocketDidConnect(socket: WebSocket) func websocketDidDisconnect(socket: WebSocket, error: NSError?) func websocketDidReceiveMessage(socket: WebSocket, text: String) - func websocketDidReceiveData(socket: WebSocket, data: NSData) + func websocketDidReceiveData(socket: WebSocket, data: Data) } public protocol WebSocketPongDelegate: class { - func websocketDidReceivePong(socket: WebSocket) + func websocketDidReceivePong(socket: WebSocket, data: Data?) } -public class WebSocket : NSObject, NSStreamDelegate { +open class WebSocket : NSObject, StreamDelegate { enum OpCode : UInt8 { - case ContinueFrame = 0x0 - case TextFrame = 0x1 - case BinaryFrame = 0x2 - //3-7 are reserved. - case ConnectionClose = 0x8 - case Ping = 0x9 - case Pong = 0xA - //B-F reserved. + case continueFrame = 0x0 + case textFrame = 0x1 + case binaryFrame = 0x2 + // 3-7 are reserved. + case connectionClose = 0x8 + case ping = 0x9 + case pong = 0xA + // B-F reserved. } public enum CloseCode : UInt16 { - case Normal = 1000 - case GoingAway = 1001 - case ProtocolError = 1002 - case ProtocolUnhandledType = 1003 + case normal = 1000 + case goingAway = 1001 + case protocolError = 1002 + case protocolUnhandledType = 1003 // 1004 reserved. - case NoStatusReceived = 1005 + case noStatusReceived = 1005 //1006 reserved. - case Encoding = 1007 - case PolicyViolated = 1008 - case MessageTooBig = 1009 + case encoding = 1007 + case policyViolated = 1008 + case messageTooBig = 1009 } public static let ErrorDomain = "WebSocket" - enum InternalErrorCode : UInt16 { + enum InternalErrorCode: UInt16 { // 0-999 WebSocket status codes not used - case OutputStreamWriteError = 1 + case outputStreamWriteError = 1 } - //Where the callback is executed. It defaults to the main UI thread queue. - public var queue = dispatch_get_main_queue() + // Where the callback is executed. It defaults to the main UI thread queue. + public var callbackQueue = DispatchQueue.main - var optionalProtocols : [String]? - //Constant Values. + var optionalProtocols: [String]? + + // MARK: - Constants let headerWSUpgradeName = "Upgrade" let headerWSUpgradeValue = "websocket" let headerWSHostName = "Host" @@ -90,136 +94,164 @@ public class WebSocket : NSObject, NSStreamDelegate { let MaskMask: UInt8 = 0x80 let PayloadLenMask: UInt8 = 0x7F let MaxFrameSize: Int = 32 + let httpSwitchProtocolCode = 101 + let supportedSSLSchemes = ["wss", "https"] class WSResponse { var isFin = false - var code: OpCode = .ContinueFrame + var code: OpCode = .continueFrame var bytesLeft = 0 var frameCount = 0 var buffer: NSMutableData? } + // MARK: - Delegates + /// Responds to callback about new messages coming in over the WebSocket + /// and also connection/disconnect messages. public weak var delegate: WebSocketDelegate? + + /// Receives a callback for each pong message recived. public weak var pongDelegate: WebSocketPongDelegate? + + + // MARK: - Block based API. public var onConnect: ((Void) -> Void)? public var onDisconnect: ((NSError?) -> Void)? public var onText: ((String) -> Void)? - public var onData: ((NSData) -> Void)? - public var onPong: ((Void) -> Void)? + public var onData: ((Data) -> Void)? + public var onPong: ((Data?) -> Void)? + public var headers = [String: String]() public var voipEnabled = false - public var selfSignedSSL = false - public var security: SSLSecurity? + public var disableSSLCertValidation = false + public var security: SSLTrustValidator? public var enabledSSLCipherSuites: [SSLCipherSuite]? public var origin: String? - public var isConnected :Bool { + public var timeout = 5 + public var isConnected: Bool { return connected } - public var currentURL: NSURL {return url} - private var url: NSURL - private var inputStream: NSInputStream? - private var outputStream: NSOutputStream? + + public var currentURL: URL { return url } + + // MARK: - Private + private var url: URL + private var inputStream: InputStream? + private var outputStream: OutputStream? private var connected = false - private var isCreated = false - private var writeQueue = NSOperationQueue() + private var isConnecting = false + private var writeQueue = OperationQueue() private var readStack = [WSResponse]() - private var inputQueue = [NSData]() - private var fragBuffer: NSData? + private var inputQueue = [Data]() + private var fragBuffer: Data? private var certValidated = false private var didDisconnect = false private var readyToWrite = false private let mutex = NSLock() + private let notificationCenter = NotificationCenter.default private var canDispatch: Bool { mutex.lock() let canWork = readyToWrite mutex.unlock() return canWork } - //the shared processing queue used for all websocket - private static let sharedWorkQueue = dispatch_queue_create("com.vluxe.starscream.websocket", DISPATCH_QUEUE_SERIAL) + /// The shared processing queue used for all WebSocket. + private static let sharedWorkQueue = DispatchQueue(label: "com.vluxe.starscream.websocket", attributes: []) - //used for setting protocols. - public init(url: NSURL, protocols: [String]? = nil) { + /// Used for setting protocols. + public init(url: URL, protocols: [String]? = nil) { self.url = url self.origin = url.absoluteString + if let hostUrl = URL (string: "/", relativeTo: url) { + var origin = hostUrl.absoluteString + origin.remove(at: origin.index(before: origin.endIndex)) + self.origin = origin + } writeQueue.maxConcurrentOperationCount = 1 optionalProtocols = protocols } - ///Connect to the websocket server on a background thread - public func connect() { - guard !isCreated else { return } + // Used for specifically setting the QOS for the write queue. + public convenience init(url: URL, writeQueueQOS: QualityOfService, protocols: [String]? = nil) { + self.init(url: url, protocols: protocols) + writeQueue.qualityOfService = writeQueueQOS + } + + /** + Connect to the WebSocket server on a background thread. + */ + open func connect() { + guard !isConnecting else { return } didDisconnect = false - isCreated = true + isConnecting = true createHTTPRequest() - isCreated = false } /** Disconnect from the server. I send a Close control frame to the server, then expect the server to respond with a Close control frame and close the socket from its end. I notify my delegate once the socket has been closed. - If you supply a non-nil `forceTimeout`, I wait at most that long (in seconds) for the server to close the socket. After the timeout expires, I close the socket and notify my delegate. - If you supply a zero (or negative) `forceTimeout`, I immediately close the socket (without sending a Close control frame) and notify my delegate. - - Parameter forceTimeout: Maximum time to wait for the server to close the socket. + - Parameter closeCode: The code to send on disconnect. The default is the normal close code for cleanly disconnecting a webSocket. */ - public func disconnect(forceTimeout forceTimeout: NSTimeInterval? = nil) { + open func disconnect(forceTimeout: TimeInterval? = nil, closeCode: UInt16 = CloseCode.normal.rawValue) { + guard isConnected else { return } switch forceTimeout { - case .Some(let seconds) where seconds > 0: - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC))), queue) { [weak self] in + case .some(let seconds) where seconds > 0: + let milliseconds = Int(seconds * 1_000) + callbackQueue.asyncAfter(deadline: .now() + .milliseconds(milliseconds)) { [weak self] in self?.disconnectStream(nil) } fallthrough - case .None: - writeError(CloseCode.Normal.rawValue) - + case .none: + writeError(closeCode) default: - self.disconnectStream(nil) + disconnectStream(nil) break } } /** Write a string to the websocket. This sends it as a text frame. - If you supply a non-nil completion block, I will perform it when the write completes. - - parameter str: The string to write. + - parameter string: The string to write. - parameter completion: The (optional) completion handler. */ - public func writeString(str: String, completion: (() -> ())? = nil) { + open func write(string: String, completion: (() -> ())? = nil) { guard isConnected else { return } - dequeueWrite(str.dataUsingEncoding(NSUTF8StringEncoding)!, code: .TextFrame, writeCompletion: completion) + dequeueWrite(string.data(using: String.Encoding.utf8)!, code: .textFrame, writeCompletion: completion) } /** Write binary data to the websocket. This sends it as a binary frame. - If you supply a non-nil completion block, I will perform it when the write completes. - parameter data: The data to write. - parameter completion: The (optional) completion handler. */ - public func writeData(data: NSData, completion: (() -> ())? = nil) { + open func write(data: Data, completion: (() -> ())? = nil) { guard isConnected else { return } - dequeueWrite(data, code: .BinaryFrame, writeCompletion: completion) + dequeueWrite(data, code: .binaryFrame, writeCompletion: completion) } - //write a ping to the websocket. This sends it as a control frame. - //yodel a sound to the planet. This sends it as an astroid. http://youtu.be/Eu5ZJELRiJ8?t=42s - public func writePing(data: NSData, completion: (() -> ())? = nil) { + /** + Write a ping to the websocket. This sends it as a control frame. + Yodel a sound to the planet. This sends it as an astroid. http://youtu.be/Eu5ZJELRiJ8?t=42s + */ + open func write(ping: Data, completion: (() -> ())? = nil) { guard isConnected else { return } - dequeueWrite(data, code: .Ping, writeCompletion: completion) + dequeueWrite(ping, code: .ping, writeCompletion: completion) } - //private method that starts the connection + /** + Private method that starts the connection. + */ private func createHTTPRequest() { - - let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET", - url, kCFHTTPVersion1_1).takeRetainedValue() + let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET" as CFString, + url as CFURL, kCFHTTPVersion1_1).takeRetainedValue() var port = url.port if port == nil { - if ["wss", "https"].contains(url.scheme) { + if supportedSSLSchemes.contains(url.scheme!) { port = 443 } else { port = 80 @@ -228,7 +260,7 @@ public class WebSocket : NSObject, NSStreamDelegate { addHeader(urlRequest, key: headerWSUpgradeName, val: headerWSUpgradeValue) addHeader(urlRequest, key: headerWSConnectionName, val: headerWSConnectionValue) if let protocols = optionalProtocols { - addHeader(urlRequest, key: headerWSProtocolName, val: protocols.joinWithSeparator(",")) + addHeader(urlRequest, key: headerWSProtocolName, val: protocols.joined(separator: ",")) } addHeader(urlRequest, key: headerWSVersionName, val: headerWSVersionValue) addHeader(urlRequest, key: headerWSKeyName, val: generateWebSocketKey()) @@ -236,79 +268,89 @@ public class WebSocket : NSObject, NSStreamDelegate { addHeader(urlRequest, key: headerOriginName, val: origin) } addHeader(urlRequest, key: headerWSHostName, val: "\(url.host!):\(port!)") - for (key,value) in headers { + for (key, value) in headers { addHeader(urlRequest, key: key, val: value) } if let cfHTTPMessage = CFHTTPMessageCopySerializedMessage(urlRequest) { let serializedRequest = cfHTTPMessage.takeRetainedValue() - initStreamsWithData(serializedRequest, Int(port!)) + initStreamsWithData(serializedRequest as Data, Int(port!)) } } - //Add a header to the CFHTTPMessage by using the NSString bridges to CFString - private func addHeader(urlRequest: CFHTTPMessage, key: NSString, val: NSString) { - CFHTTPMessageSetHeaderFieldValue(urlRequest, key, val) + /** + Add a header to the CFHTTPMessage by using the NSString bridges to CFString + */ + private func addHeader(_ urlRequest: CFHTTPMessage, key: String, val: String) { + CFHTTPMessageSetHeaderFieldValue(urlRequest, key as CFString, val as CFString) } - //generate a websocket key as needed in rfc + /** + Generate a WebSocket key as needed in RFC. + */ private func generateWebSocketKey() -> String { var key = "" let seed = 16 for _ in 0..? var writeStream: Unmanaged? - let h: NSString = url.host! + let h = url.host! as NSString CFStreamCreatePairWithSocketToHost(nil, h, UInt32(port), &readStream, &writeStream) inputStream = readStream!.takeRetainedValue() outputStream = writeStream!.takeRetainedValue() guard let inStream = inputStream, let outStream = outputStream else { return } inStream.delegate = self outStream.delegate = self - if ["wss", "https"].contains(url.scheme) { - inStream.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey) - outStream.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey) + if supportedSSLSchemes.contains(url.scheme!) { + certValidated = false + inStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey) + outStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey) + if disableSSLCertValidation { + let settings: [NSObject: NSObject] = [kCFStreamSSLValidatesCertificateChain: NSNumber(value: false), kCFStreamSSLPeerName: kCFNull] + inStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey) + outStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey) + } + if let cipherSuites = self.enabledSSLCipherSuites { + if let sslContextIn = CFReadStreamCopyProperty(inputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext?, + let sslContextOut = CFWriteStreamCopyProperty(outputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext? { + let resIn = SSLSetEnabledCiphers(sslContextIn, cipherSuites, cipherSuites.count) + let resOut = SSLSetEnabledCiphers(sslContextOut, cipherSuites, cipherSuites.count) + if resIn != errSecSuccess { + let error = self.errorWithDetail("Error setting ingoing cypher suites", code: UInt16(resIn)) + disconnectStream(error) + return + } + if resOut != errSecSuccess { + let error = self.errorWithDetail("Error setting outgoing cypher suites", code: UInt16(resOut)) + disconnectStream(error) + return + } + } + } } else { certValidated = true //not a https session, so no need to check SSL pinning } if voipEnabled { - inStream.setProperty(NSStreamNetworkServiceTypeVoIP, forKey: NSStreamNetworkServiceType) - outStream.setProperty(NSStreamNetworkServiceTypeVoIP, forKey: NSStreamNetworkServiceType) - } - if selfSignedSSL { - let settings: [NSObject: NSObject] = [kCFStreamSSLValidatesCertificateChain: NSNumber(bool:false), kCFStreamSSLPeerName: kCFNull] - inStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as String) - outStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as String) - } - if let cipherSuites = self.enabledSSLCipherSuites { - if let sslContextIn = CFReadStreamCopyProperty(inputStream, kCFStreamPropertySSLContext) as! SSLContextRef?, - sslContextOut = CFWriteStreamCopyProperty(outputStream, kCFStreamPropertySSLContext) as! SSLContextRef? { - let resIn = SSLSetEnabledCiphers(sslContextIn, cipherSuites, cipherSuites.count) - let resOut = SSLSetEnabledCiphers(sslContextOut, cipherSuites, cipherSuites.count) - if resIn != errSecSuccess { - let error = self.errorWithDetail("Error setting ingoing cypher suites", code: UInt16(resIn)) - disconnectStream(error) - return - } - if resOut != errSecSuccess { - let error = self.errorWithDetail("Error setting outgoing cypher suites", code: UInt16(resOut)) - disconnectStream(error) - return - } - } + inStream.setProperty(StreamNetworkServiceTypeValue.voIP as AnyObject, forKey: Stream.PropertyKey.networkServiceType) + outStream.setProperty(StreamNetworkServiceTypeValue.voIP as AnyObject, forKey: Stream.PropertyKey.networkServiceType) } + CFReadStreamSetDispatchQueue(inStream, WebSocket.sharedWorkQueue) CFWriteStreamSetDispatchQueue(outStream, WebSocket.sharedWorkQueue) inStream.open() @@ -318,60 +360,78 @@ public class WebSocket : NSObject, NSStreamDelegate { self.readyToWrite = true self.mutex.unlock() - let bytes = UnsafePointer(data.bytes) - var timeout = 5000000 //wait 5 seconds before giving up - writeQueue.addOperationWithBlock { [weak self] in - while !outStream.hasSpaceAvailable { - usleep(100) //wait until the socket is ready - timeout -= 100 - if timeout < 0 { - self?.cleanupStream() + let bytes = UnsafeRawPointer((data as NSData).bytes).assumingMemoryBound(to: UInt8.self) + var out = timeout * 1_000_000 // wait 5 seconds before giving up + let operation = BlockOperation() + operation.addExecutionBlock { [weak self, weak operation] in + guard let sOperation = operation else { return } + while !outStream.hasSpaceAvailable && !sOperation.isCancelled { + usleep(100) // wait until the socket is ready + guard !sOperation.isCancelled else { return } + out -= 100 + if out < 0 { + WebSocket.sharedWorkQueue.async { + self?.cleanupStream() + } self?.doDisconnect(self?.errorWithDetail("write wait timed out", code: 2)) return } else if outStream.streamError != nil { - return //disconnectStream will be called. + return // disconnectStream will be called. } } - outStream.write(bytes, maxLength: data.length) - } - } - //delegate for the stream methods. Processes incoming bytes - public func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) { - - if let sec = security where !certValidated && [.HasBytesAvailable, .HasSpaceAvailable].contains(eventCode) { - let possibleTrust: AnyObject? = aStream.propertyForKey(kCFStreamPropertySSLPeerTrust as String) - if let trust: AnyObject = possibleTrust { - let domain: AnyObject? = aStream.propertyForKey(kCFStreamSSLPeerName as String) - if sec.isValid(trust as! SecTrustRef, domain: domain as! String?) { - certValidated = true - } else { - let error = errorWithDetail("Invalid SSL certificate", code: 1) - disconnectStream(error) + guard !sOperation.isCancelled, let s = self else { return } + // Do the pinning now if needed + if let sec = s.security, !s.certValidated { + let trust = outStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust + let domain = outStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String + s.certValidated = sec.isValid(trust, domain: domain) + if !s.certValidated { + WebSocket.sharedWorkQueue.async { + let error = s.errorWithDetail("Invalid SSL certificate", code: 1) + s.disconnectStream(error) + } return } } + outStream.write(bytes, maxLength: data.count) } - if eventCode == .HasBytesAvailable { + writeQueue.addOperation(operation) + } + + /** + Delegate for the stream methods. Processes incoming bytes + */ + open func stream(_ aStream: Stream, handle eventCode: Stream.Event) { + if eventCode == .hasBytesAvailable { if aStream == inputStream { processInputStream() } - } else if eventCode == .ErrorOccurred { - disconnectStream(aStream.streamError) - } else if eventCode == .EndEncountered { + } else if eventCode == .errorOccurred { + disconnectStream(aStream.streamError as NSError?) + } else if eventCode == .endEncountered { disconnectStream(nil) } } - //disconnect the stream object - private func disconnectStream(error: NSError?) { + + /** + Disconnect the stream object and notifies the delegate. + */ + private func disconnectStream(_ error: NSError?, runDelegate: Bool = true) { if error == nil { writeQueue.waitUntilAllOperationsAreFinished() } else { writeQueue.cancelAllOperations() } cleanupStream() - doDisconnect(error) + connected = false + if runDelegate { + doDisconnect(error) + } } + /** + cleanup the streams. + */ private func cleanupStream() { outputStream?.delegate = nil inputStream?.delegate = nil @@ -385,68 +445,73 @@ public class WebSocket : NSObject, NSStreamDelegate { } outputStream = nil inputStream = nil + fragBuffer = nil } - ///handles the incoming bytes and sending them to the proper processing method + /** + Handles the incoming bytes and sending them to the proper processing method. + */ private func processInputStream() { let buf = NSMutableData(capacity: BUFFER_MAX) - let buffer = UnsafeMutablePointer(buf!.bytes) + let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self) let length = inputStream!.read(buffer, maxLength: BUFFER_MAX) - guard length > 0 else { return } var process = false if inputQueue.count == 0 { process = true } - inputQueue.append(NSData(bytes: buffer, length: length)) + inputQueue.append(Data(bytes: buffer, count: length)) if process { dequeueInput() } } - ///dequeue the incoming input so it is processed in order + + /** + Dequeue the incoming input so it is processed in order. + */ private func dequeueInput() { - guard !inputQueue.isEmpty else { return } - - let data = inputQueue[0] - var work = data - if let fragBuffer = fragBuffer { - let combine = NSMutableData(data: fragBuffer) - combine.appendData(data) - work = combine - self.fragBuffer = nil - } - let buffer = UnsafePointer(work.bytes) - let length = work.length - if !connected { - processTCPHandshake(buffer, bufferLen: length) - } else { - processRawMessage(buffer, bufferLen: length) + while !inputQueue.isEmpty { + autoreleasepool { + let data = inputQueue[0] + var work = data + if let buffer = fragBuffer { + var combine = NSData(data: buffer) as Data + combine.append(data) + work = combine + fragBuffer = nil + } + let buffer = UnsafeRawPointer((work as NSData).bytes).assumingMemoryBound(to: UInt8.self) + let length = work.count + if !connected { + processTCPHandshake(buffer, bufferLen: length) + } else { + processRawMessagesInBuffer(buffer, bufferLen: length) + } + inputQueue = inputQueue.filter{ $0 != data } + } } - inputQueue = inputQueue.filter{$0 != data} - dequeueInput() } - //handle checking the inital connection status - private func processTCPHandshake(buffer: UnsafePointer, bufferLen: Int) { + /** + Handle checking the inital connection status + */ + private func processTCPHandshake(_ buffer: UnsafePointer, bufferLen: Int) { let code = processHTTP(buffer, bufferLen: bufferLen) switch code { case 0: - connected = true - guard canDispatch else {return} - dispatch_async(queue) { [weak self] in - guard let s = self else { return } - s.onConnect?() - s.delegate?.websocketDidConnect(s) - } + break case -1: - fragBuffer = NSData(bytes: buffer, length: bufferLen) - break //do nothing, we are going to collect more data + fragBuffer = Data(bytes: buffer, count: bufferLen) + break // do nothing, we are going to collect more data default: doDisconnect(errorWithDetail("Invalid HTTP upgrade", code: UInt16(code))) } } - ///Finds the HTTP Packet in the TCP stream, by looking for the CRLF. - private func processHTTP(buffer: UnsafePointer, bufferLen: Int) -> Int { + + /** + Finds the HTTP Packet in the TCP stream, by looking for the CRLF. + */ + private func processHTTP(_ buffer: UnsafePointer, bufferLen: Int) -> Int { let CRLFBytes = [UInt8(ascii: "\r"), UInt8(ascii: "\n"), UInt8(ascii: "\r"), UInt8(ascii: "\n")] var k = 0 var totalSize = 0 @@ -466,27 +531,40 @@ public class WebSocket : NSObject, NSStreamDelegate { if code != 0 { return code } + isConnecting = false + connected = true + didDisconnect = false + if canDispatch { + callbackQueue.async { [weak self] in + guard let s = self else { return } + s.onConnect?() + s.delegate?.websocketDidConnect(socket: s) + s.notificationCenter.post(name: NSNotification.Name(WebsocketDidConnectNotification), object: self) + } + } totalSize += 1 //skip the last \n let restSize = bufferLen - totalSize if restSize > 0 { - processRawMessage((buffer+totalSize),bufferLen: restSize) + processRawMessagesInBuffer(buffer + totalSize, bufferLen: restSize) } return 0 //success } - return -1 //was unable to find the full TCP header + return -1 // Was unable to find the full TCP header. } - ///validates the HTTP is a 101 as per the RFC spec - private func validateResponse(buffer: UnsafePointer, bufferLen: Int) -> Int { + /** + Validates the HTTP is a 101 as per the RFC spec. + */ + private func validateResponse(_ buffer: UnsafePointer, bufferLen: Int) -> Int { let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false).takeRetainedValue() CFHTTPMessageAppendBytes(response, buffer, bufferLen) let code = CFHTTPMessageGetResponseStatusCode(response) - if code != 101 { + if code != httpSwitchProtocolCode { return code } if let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) { let headers = cfHeaders.takeRetainedValue() as NSDictionary - if let acceptKey = headers[headerWSAcceptName] as? NSString { + if let acceptKey = headers[headerWSAcceptName as NSString] as? NSString { if acceptKey.length > 0 { return 0 } @@ -495,13 +573,17 @@ public class WebSocket : NSObject, NSStreamDelegate { return -1 } - ///read a 16 bit big endian value from a buffer - private static func readUint16(buffer: UnsafePointer, offset: Int) -> UInt16 { + /** + Read a 16 bit big endian value from a buffer + */ + private static func readUint16(_ buffer: UnsafePointer, offset: Int) -> UInt16 { return (UInt16(buffer[offset + 0]) << 8) | UInt16(buffer[offset + 1]) } - ///read a 64 bit big endian value from a buffer - private static func readUint64(buffer: UnsafePointer, offset: Int) -> UInt64 { + /** + Read a 64 bit big endian value from a buffer + */ + private static func readUint64(_ buffer: UnsafePointer, offset: Int) -> UInt64 { var value = UInt64(0) for i in 0...7 { value = (value << 8) | UInt64(buffer[offset + i]) @@ -509,27 +591,35 @@ public class WebSocket : NSObject, NSStreamDelegate { return value } - ///write a 16 bit big endian value to a buffer - private static func writeUint16(buffer: UnsafeMutablePointer, offset: Int, value: UInt16) { + /** + Write a 16-bit big endian value to a buffer. + */ + private static func writeUint16(_ buffer: UnsafeMutablePointer, offset: Int, value: UInt16) { buffer[offset + 0] = UInt8(value >> 8) buffer[offset + 1] = UInt8(value & 0xff) } - ///write a 64 bit big endian value to a buffer - private static func writeUint64(buffer: UnsafeMutablePointer, offset: Int, value: UInt64) { + /** + Write a 64-bit big endian value to a buffer. + */ + private static func writeUint64(_ buffer: UnsafeMutablePointer, offset: Int, value: UInt64) { for i in 0...7 { buffer[offset + i] = UInt8((value >> (8*UInt64(7 - i))) & 0xff) } } - ///process the websocket data - private func processRawMessage(buffer: UnsafePointer, bufferLen: Int) { + /** + Process one message at the start of `buffer`. Return another buffer (sharing storage) that contains the leftover contents of `buffer` that I didn't process. + */ + private func processOneRawMessage(inBuffer buffer: UnsafeBufferPointer) -> UnsafeBufferPointer { let response = readStack.last - if response != nil && bufferLen < 2 { - fragBuffer = NSData(bytes: buffer, length: bufferLen) - return + guard let baseAddress = buffer.baseAddress else {return emptyBuffer} + let bufferLen = buffer.count + if response != nil && bufferLen < 2 { + fragBuffer = Data(buffer: buffer) + return emptyBuffer } - if let response = response where response.bytesLeft > 0 { + if let response = response, response.bytesLeft > 0 { var len = response.bytesLeft var extra = bufferLen - response.bytesLeft if response.bytesLeft > bufferLen { @@ -537,124 +627,118 @@ public class WebSocket : NSObject, NSStreamDelegate { extra = 0 } response.bytesLeft -= len - response.buffer?.appendData(NSData(bytes: buffer, length: len)) - processResponse(response) - let offset = bufferLen - extra - if extra > 0 { - processExtra((buffer+offset), bufferLen: extra) - } - return + response.buffer?.append(Data(bytes: baseAddress, count: len)) + _ = processResponse(response) + return buffer.fromOffset(bufferLen - extra) } else { - let isFin = (FinMask & buffer[0]) - let receivedOpcode = OpCode(rawValue: (OpCodeMask & buffer[0])) - let isMasked = (MaskMask & buffer[1]) - let payloadLen = (PayloadLenMask & buffer[1]) + let isFin = (FinMask & baseAddress[0]) + let receivedOpcodeRawValue = (OpCodeMask & baseAddress[0]) + let receivedOpcode = OpCode(rawValue: receivedOpcodeRawValue) + let isMasked = (MaskMask & baseAddress[1]) + let payloadLen = (PayloadLenMask & baseAddress[1]) var offset = 2 - if (isMasked > 0 || (RSVMask & buffer[0]) > 0) && receivedOpcode != .Pong { - let errCode = CloseCode.ProtocolError.rawValue + if (isMasked > 0 || (RSVMask & baseAddress[0]) > 0) && receivedOpcode != .pong { + let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("masked and rsv data is not currently supported", code: errCode)) writeError(errCode) - return + return emptyBuffer } - let isControlFrame = (receivedOpcode == .ConnectionClose || receivedOpcode == .Ping) - if !isControlFrame && (receivedOpcode != .BinaryFrame && receivedOpcode != .ContinueFrame && - receivedOpcode != .TextFrame && receivedOpcode != .Pong) { - let errCode = CloseCode.ProtocolError.rawValue - doDisconnect(errorWithDetail("unknown opcode: \(receivedOpcode)", code: errCode)) + let isControlFrame = (receivedOpcode == .connectionClose || receivedOpcode == .ping) + if !isControlFrame && (receivedOpcode != .binaryFrame && receivedOpcode != .continueFrame && + receivedOpcode != .textFrame && receivedOpcode != .pong) { + let errCode = CloseCode.protocolError.rawValue + doDisconnect(errorWithDetail("unknown opcode: \(receivedOpcodeRawValue)", code: errCode)) writeError(errCode) - return + return emptyBuffer } if isControlFrame && isFin == 0 { - let errCode = CloseCode.ProtocolError.rawValue + let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("control frames can't be fragmented", code: errCode)) writeError(errCode) - return + return emptyBuffer } - if receivedOpcode == .ConnectionClose { - var code = CloseCode.Normal.rawValue + var closeCode = CloseCode.normal.rawValue + if receivedOpcode == .connectionClose { if payloadLen == 1 { - code = CloseCode.ProtocolError.rawValue + closeCode = CloseCode.protocolError.rawValue } else if payloadLen > 1 { - code = WebSocket.readUint16(buffer, offset: offset) - if code < 1000 || (code > 1003 && code < 1007) || (code > 1011 && code < 3000) { - code = CloseCode.ProtocolError.rawValue + closeCode = WebSocket.readUint16(baseAddress, offset: offset) + if closeCode < 1000 || (closeCode > 1003 && closeCode < 1007) || (closeCode > 1011 && closeCode < 3000) { + closeCode = CloseCode.protocolError.rawValue } - offset += 2 } - if payloadLen > 2 { - let len = Int(payloadLen-2) - if len > 0 { - let bytes = UnsafePointer((buffer+offset)) - let str: NSString? = NSString(data: NSData(bytes: bytes, length: len), encoding: NSUTF8StringEncoding) - if str == nil { - code = CloseCode.ProtocolError.rawValue - } - } + if payloadLen < 2 { + doDisconnect(errorWithDetail("connection closed by server", code: closeCode)) + writeError(closeCode) + return emptyBuffer } - doDisconnect(errorWithDetail("connection closed by server", code: code)) - writeError(code) - return - } - if isControlFrame && payloadLen > 125 { - writeError(CloseCode.ProtocolError.rawValue) - return + } else if isControlFrame && payloadLen > 125 { + writeError(CloseCode.protocolError.rawValue) + return emptyBuffer } var dataLength = UInt64(payloadLen) if dataLength == 127 { - dataLength = WebSocket.readUint64(buffer, offset: offset) - offset += sizeof(UInt64) + dataLength = WebSocket.readUint64(baseAddress, offset: offset) + offset += MemoryLayout.size } else if dataLength == 126 { - dataLength = UInt64(WebSocket.readUint16(buffer, offset: offset)) - offset += sizeof(UInt16) + dataLength = UInt64(WebSocket.readUint16(baseAddress, offset: offset)) + offset += MemoryLayout.size } if bufferLen < offset || UInt64(bufferLen - offset) < dataLength { - fragBuffer = NSData(bytes: buffer, length: bufferLen) - return + fragBuffer = Data(bytes: baseAddress, count: bufferLen) + return emptyBuffer } var len = dataLength if dataLength > UInt64(bufferLen) { len = UInt64(bufferLen-offset) } - let data: NSData - if len < 0 { - len = 0 - data = NSData() - } else { - data = NSData(bytes: UnsafePointer((buffer+offset)), length: Int(len)) + if receivedOpcode == .connectionClose && len > 0 { + let size = MemoryLayout.size + offset += size + len -= UInt64(size) } - if receivedOpcode == .Pong { + let data = Data(bytes: baseAddress+offset, count: Int(len)) + + if receivedOpcode == .connectionClose { + var closeReason = "connection closed by server" + if let customCloseReason = String(data: data, encoding: .utf8) { + closeReason = customCloseReason + } else { + closeCode = CloseCode.protocolError.rawValue + } + doDisconnect(errorWithDetail(closeReason, code: closeCode)) + writeError(closeCode) + return emptyBuffer + } + if receivedOpcode == .pong { if canDispatch { - dispatch_async(queue) { [weak self] in + callbackQueue.async { [weak self] in guard let s = self else { return } - s.onPong?() - s.pongDelegate?.websocketDidReceivePong(s) + let pongData: Data? = data.count > 0 ? data : nil + s.onPong?(pongData) + s.pongDelegate?.websocketDidReceivePong(socket: s, data: pongData) } } - let step = Int(offset+numericCast(len)) - let extra = bufferLen-step - if extra > 0 { - processRawMessage((buffer+step), bufferLen: extra) - } - return + return buffer.fromOffset(offset + Int(len)) } var response = readStack.last if isControlFrame { - response = nil //don't append pings + response = nil // Don't append pings. } - if isFin == 0 && receivedOpcode == .ContinueFrame && response == nil { - let errCode = CloseCode.ProtocolError.rawValue + if isFin == 0 && receivedOpcode == .continueFrame && response == nil { + let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("continue frame before a binary or text frame", code: errCode)) writeError(errCode) - return + return emptyBuffer } var isNew = false if response == nil { - if receivedOpcode == .ContinueFrame { - let errCode = CloseCode.ProtocolError.rawValue + if receivedOpcode == .continueFrame { + let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("first frame can't be a continue frame", - code: errCode)) + code: errCode)) writeError(errCode) - return + return emptyBuffer } isNew = true response = WSResponse() @@ -662,16 +746,16 @@ public class WebSocket : NSObject, NSStreamDelegate { response!.bytesLeft = Int(dataLength) response!.buffer = NSMutableData(data: data) } else { - if receivedOpcode == .ContinueFrame { + if receivedOpcode == .continueFrame { response!.bytesLeft = Int(dataLength) } else { - let errCode = CloseCode.ProtocolError.rawValue + let errCode = CloseCode.protocolError.rawValue doDisconnect(errorWithDetail("second and beyond of fragment message must be a continue frame", - code: errCode)) + code: errCode)) writeError(errCode) - return + return emptyBuffer } - response!.buffer!.appendData(data) + response!.buffer!.append(data) } if let response = response { response.bytesLeft -= Int(len) @@ -680,53 +764,55 @@ public class WebSocket : NSObject, NSStreamDelegate { if isNew { readStack.append(response) } - processResponse(response) + _ = processResponse(response) } - let step = Int(offset+numericCast(len)) - let extra = bufferLen-step - if extra > 0 { - processExtra((buffer+step), bufferLen: extra) - } + let step = Int(offset + numericCast(len)) + return buffer.fromOffset(step) } - } - ///process the extra of a buffer - private func processExtra(buffer: UnsafePointer, bufferLen: Int) { - if bufferLen < 2 { - fragBuffer = NSData(bytes: buffer, length: bufferLen) - } else { - processRawMessage(buffer, bufferLen: bufferLen) + /** + Process all messages in the buffer if possible. + */ + private func processRawMessagesInBuffer(_ pointer: UnsafePointer, bufferLen: Int) { + var buffer = UnsafeBufferPointer(start: pointer, count: bufferLen) + repeat { + buffer = processOneRawMessage(inBuffer: buffer) + } while buffer.count >= 2 + if buffer.count > 0 { + fragBuffer = Data(buffer: buffer) } } - ///process the finished response of a buffer - private func processResponse(response: WSResponse) -> Bool { + /** + Process the finished response of a buffer. + */ + private func processResponse(_ response: WSResponse) -> Bool { if response.isFin && response.bytesLeft <= 0 { - if response.code == .Ping { - let data = response.buffer! //local copy so it is perverse for writing - dequeueWrite(data, code: OpCode.Pong) - } else if response.code == .TextFrame { - let str: NSString? = NSString(data: response.buffer!, encoding: NSUTF8StringEncoding) + if response.code == .ping { + let data = response.buffer! // local copy so it is perverse for writing + dequeueWrite(data as Data, code: .pong) + } else if response.code == .textFrame { + let str: NSString? = NSString(data: response.buffer! as Data, encoding: String.Encoding.utf8.rawValue) if str == nil { - writeError(CloseCode.Encoding.rawValue) + writeError(CloseCode.encoding.rawValue) return false } if canDispatch { - dispatch_async(queue) { [weak self] in + callbackQueue.async { [weak self] in guard let s = self else { return } s.onText?(str! as String) - s.delegate?.websocketDidReceiveMessage(s, text: str! as String) + s.delegate?.websocketDidReceiveMessage(socket: s, text: str! as String) } } - } else if response.code == .BinaryFrame { + } else if response.code == .binaryFrame { if canDispatch { - let data = response.buffer! //local copy so it is perverse for writing - dispatch_async(queue) { [weak self] in + let data = response.buffer! // local copy so it is perverse for writing + callbackQueue.async { [weak self] in guard let s = self else { return } - s.onData?(data) - s.delegate?.websocketDidReceiveData(s, data: data) + s.onData?(data as Data) + s.delegate?.websocketDidReceiveData(socket: s, data: data as Data) } } } @@ -736,72 +822,80 @@ public class WebSocket : NSObject, NSStreamDelegate { return false } - ///Create an error - private func errorWithDetail(detail: String, code: UInt16) -> NSError { + /** + Create an error + */ + private func errorWithDetail(_ detail: String, code: UInt16) -> NSError { var details = [String: String]() details[NSLocalizedDescriptionKey] = detail return NSError(domain: WebSocket.ErrorDomain, code: Int(code), userInfo: details) } - ///write a an error to the socket - private func writeError(code: UInt16) { - let buf = NSMutableData(capacity: sizeof(UInt16)) - let buffer = UnsafeMutablePointer(buf!.bytes) + /** + Write an error to the socket + */ + private func writeError(_ code: UInt16) { + let buf = NSMutableData(capacity: MemoryLayout.size) + let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self) WebSocket.writeUint16(buffer, offset: 0, value: code) - dequeueWrite(NSData(bytes: buffer, length: sizeof(UInt16)), code: .ConnectionClose) + dequeueWrite(Data(bytes: buffer, count: MemoryLayout.size), code: .connectionClose) } - ///used to write things to the stream - private func dequeueWrite(data: NSData, code: OpCode, writeCompletion: (() -> ())? = nil) { - writeQueue.addOperationWithBlock { [weak self] in + + /** + Used to write things to the stream + */ + private func dequeueWrite(_ data: Data, code: OpCode, writeCompletion: (() -> ())? = nil) { + let operation = BlockOperation() + operation.addExecutionBlock { [weak self, weak operation] in //stream isn't ready, let's wait guard let s = self else { return } + guard let sOperation = operation else { return } var offset = 2 - let bytes = UnsafeMutablePointer(data.bytes) - let dataLength = data.length + let dataLength = data.count let frame = NSMutableData(capacity: dataLength + s.MaxFrameSize) - let buffer = UnsafeMutablePointer(frame!.mutableBytes) + let buffer = UnsafeMutableRawPointer(frame!.mutableBytes).assumingMemoryBound(to: UInt8.self) buffer[0] = s.FinMask | code.rawValue if dataLength < 126 { buffer[1] = CUnsignedChar(dataLength) } else if dataLength <= Int(UInt16.max) { buffer[1] = 126 WebSocket.writeUint16(buffer, offset: offset, value: UInt16(dataLength)) - offset += sizeof(UInt16) + offset += MemoryLayout.size } else { buffer[1] = 127 WebSocket.writeUint64(buffer, offset: offset, value: UInt64(dataLength)) - offset += sizeof(UInt64) + offset += MemoryLayout.size } buffer[1] |= s.MaskMask let maskKey = UnsafeMutablePointer(buffer + offset) - SecRandomCopyBytes(kSecRandomDefault, Int(sizeof(UInt32)), maskKey) - offset += sizeof(UInt32) + _ = SecRandomCopyBytes(kSecRandomDefault, Int(MemoryLayout.size), maskKey) + offset += MemoryLayout.size for i in 0...size] offset += 1 } var total = 0 - while true { + while !sOperation.isCancelled { guard let outStream = s.outputStream else { break } - let writeBuffer = UnsafePointer(frame!.bytes+total) + let writeBuffer = UnsafeRawPointer(frame!.bytes+total).assumingMemoryBound(to: UInt8.self) let len = outStream.write(writeBuffer, maxLength: offset-total) if len < 0 { - var error: NSError? + var error: Error? if let streamError = outStream.streamError { error = streamError } else { - let errCode = InternalErrorCode.OutputStreamWriteError.rawValue + let errCode = InternalErrorCode.outputStreamWriteError.rawValue error = s.errorWithDetail("output stream error during write", code: errCode) } - s.doDisconnect(error) + s.doDisconnect(error as NSError?) break } else { total += len } if total >= offset { - if let queue = self?.queue, callback = writeCompletion { - dispatch_async(queue) { + if let queue = self?.callbackQueue, let callback = writeCompletion { + queue.async { callback() } } @@ -809,262 +903,53 @@ public class WebSocket : NSObject, NSStreamDelegate { break } } - } + writeQueue.addOperation(operation) } - ///used to preform the disconnect delegate - private func doDisconnect(error: NSError?) { + /** + Used to preform the disconnect delegate + */ + private func doDisconnect(_ error: NSError?) { guard !didDisconnect else { return } didDisconnect = true + isConnecting = false connected = false guard canDispatch else {return} - dispatch_async(queue) { [weak self] in + callbackQueue.async { [weak self] in guard let s = self else { return } s.onDisconnect?(error) - s.delegate?.websocketDidDisconnect(s, error: error) + s.delegate?.websocketDidDisconnect(socket: s, error: error) + let userInfo = error.map{ [WebsocketDisconnectionErrorKeyName: $0] } + s.notificationCenter.post(name: NSNotification.Name(WebsocketDidDisconnectNotification), object: self, userInfo: userInfo) } } + // MARK: - Deinit deinit { mutex.lock() readyToWrite = false mutex.unlock() cleanupStream() + writeQueue.cancelAllOperations() } } -public class SSLCert { - var certData: NSData? - var key: SecKeyRef? +private extension Data { - /** - Designated init for certificates - - - parameter data: is the binary data of the certificate - - - returns: a representation security object to be used with - */ - public init(data: NSData) { - self.certData = data + init(buffer: UnsafeBufferPointer) { + self.init(bytes: buffer.baseAddress!, count: buffer.count) } - /** - Designated init for public keys - - - parameter key: is the public key to be used - - - returns: a representation security object to be used with - */ - public init(key: SecKeyRef) { - self.key = key - } } -public class SSLSecurity { - public var validatedDN = true //should the domain name be validated? - - var isReady = false //is the key processing done? - var certificates: [NSData]? //the certificates - var pubKeys: [SecKeyRef]? //the public keys - var usePublicKeys = false //use public keys or certificate validation? - - /** - Use certs from main app bundle - - - parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation - - - returns: a representation security object to be used with - */ - public convenience init(usePublicKeys: Bool = false) { - let paths = NSBundle.mainBundle().pathsForResourcesOfType("cer", inDirectory: ".") - - let certs = paths.reduce([SSLCert]()) { (certs: [SSLCert], path: String) -> [SSLCert] in - var certs = certs - if let data = NSData(contentsOfFile: path) { - certs.append(SSLCert(data: data)) - } - return certs - } - - self.init(certs: certs, usePublicKeys: usePublicKeys) - } - - /** - Designated init - - - parameter keys: is the certificates or public keys to use - - parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation - - - returns: a representation security object to be used with - */ - public init(certs: [SSLCert], usePublicKeys: Bool) { - self.usePublicKeys = usePublicKeys - - if self.usePublicKeys { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)) { - let pubKeys = certs.reduce([SecKeyRef]()) { (pubKeys: [SecKeyRef], cert: SSLCert) -> [SecKeyRef] in - var pubKeys = pubKeys - if let data = cert.certData where cert.key == nil { - cert.key = self.extractPublicKey(data) - } - if let key = cert.key { - pubKeys.append(key) - } - return pubKeys - } - - self.pubKeys = pubKeys - self.isReady = true - } - } else { - let certificates = certs.reduce([NSData]()) { (certificates: [NSData], cert: SSLCert) -> [NSData] in - var certificates = certificates - if let data = cert.certData { - certificates.append(data) - } - return certificates - } - self.certificates = certificates - self.isReady = true - } - } +private extension UnsafeBufferPointer { - /** - Valid the trust and domain name. - - - parameter trust: is the serverTrust to validate - - parameter domain: is the CN domain to validate - - - returns: if the key was successfully validated - */ - public func isValid(trust: SecTrustRef, domain: String?) -> Bool { - - var tries = 0 - while(!self.isReady) { - usleep(1000) - tries += 1 - if tries > 5 { - return false //doesn't appear it is going to ever be ready... - } - } - var policy: SecPolicyRef - if self.validatedDN { - policy = SecPolicyCreateSSL(true, domain) - } else { - policy = SecPolicyCreateBasicX509() - } - SecTrustSetPolicies(trust,policy) - if self.usePublicKeys { - if let keys = self.pubKeys { - let serverPubKeys = publicKeyChainForTrust(trust) - for serverKey in serverPubKeys as [AnyObject] { - for key in keys as [AnyObject] { - if serverKey.isEqual(key) { - return true - } - } - } - } - } else if let certs = self.certificates { - let serverCerts = certificateChainForTrust(trust) - var collect = [SecCertificate]() - for cert in certs { - collect.append(SecCertificateCreateWithData(nil,cert)!) - } - SecTrustSetAnchorCertificates(trust,collect) - var result: SecTrustResultType = 0 - SecTrustEvaluate(trust,&result) - let r = Int(result) - if r == kSecTrustResultUnspecified || r == kSecTrustResultProceed { - var trustedCount = 0 - for serverCert in serverCerts { - for cert in certs { - if cert == serverCert { - trustedCount += 1 - break - } - } - } - if trustedCount == serverCerts.count { - return true - } - } - } - return false + func fromOffset(_ offset: Int) -> UnsafeBufferPointer { + return UnsafeBufferPointer(start: baseAddress?.advanced(by: offset), count: count - offset) } - /** - Get the public key from a certificate data - - - parameter data: is the certificate to pull the public key from - - - returns: a public key - */ - func extractPublicKey(data: NSData) -> SecKeyRef? { - guard let cert = SecCertificateCreateWithData(nil, data) else { return nil } - - return extractPublicKeyFromCert(cert, policy: SecPolicyCreateBasicX509()) - } - - /** - Get the public key from a certificate - - - parameter data: is the certificate to pull the public key from - - - returns: a public key - */ - func extractPublicKeyFromCert(cert: SecCertificate, policy: SecPolicy) -> SecKeyRef? { - var possibleTrust: SecTrust? - SecTrustCreateWithCertificates(cert, policy, &possibleTrust) - - guard let trust = possibleTrust else { return nil } - - var result: SecTrustResultType = 0 - SecTrustEvaluate(trust, &result) - return SecTrustCopyPublicKey(trust) - } - - /** - Get the certificate chain for the trust - - - parameter trust: is the trust to lookup the certificate chain for - - - returns: the certificate chain for the trust - */ - func certificateChainForTrust(trust: SecTrustRef) -> [NSData] { - let certificates = (0.. [NSData] in - var certificates = certificates - let cert = SecTrustGetCertificateAtIndex(trust, index) - certificates.append(SecCertificateCopyData(cert!)) - return certificates - } - - return certificates - } - - /** - Get the public key chain for the trust - - - parameter trust: is the trust to lookup the certificate chain and extract the public keys - - - returns: the public keys from the certifcate chain for the trust - */ - func publicKeyChainForTrust(trust: SecTrustRef) -> [SecKeyRef] { - let policy = SecPolicyCreateBasicX509() - let keys = (0.. [SecKeyRef] in - var keys = keys - let cert = SecTrustGetCertificateAtIndex(trust, index) - if let key = extractPublicKeyFromCert(cert!, policy: policy) { - keys.append(key) - } - - return keys - } - - return keys - } - - -} \ No newline at end of file +} + +private let emptyBuffer = UnsafeBufferPointer(start: nil, count: 0) From c8c3b3280862a59e83ad1ce68ad2457d2b5c3ddf Mon Sep 17 00:00:00 2001 From: Lucas de castro Date: Thu, 11 May 2017 09:28:29 -0300 Subject: [PATCH 8/8] Add callback parameter --- .../com/gcrabtree/rctsocketio/SocketIoModule.java | 12 ++++++++++-- .../rctsocketio/SocketIoReadableNativeMap.java | 1 + index.js | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java b/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java index ae5ee80..cf902f2 100644 --- a/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java +++ b/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoModule.java @@ -15,12 +15,14 @@ import com.facebook.react.bridge.ReadableNativeMap; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.Callback; import org.json.JSONObject; import java.net.URISyntaxException; import java.util.HashMap; +import io.socket.client.Ack; import io.socket.client.IO; import io.socket.client.Socket; import io.socket.emitter.Emitter; @@ -63,12 +65,18 @@ public void initialize(String connection, ReadableMap options) { * Emit event to server * @param event The name of the event. * @param items The data to pass through the SocketIo engine to the server endpoint. + * @param Ack callback */ @ReactMethod - public void emit(String event, ReadableMap items) { + public void emit(String event, ReadableMap items, final Callback ack) { HashMap map = SocketIoReadableNativeMap.toHashMap((ReadableNativeMap) items); if (mSocket != null) { - mSocket.emit(event, new JSONObject(map)); + mSocket.emit(event, new JSONObject(map), new Ack() { + @Override + public void call(Object... args) { + ack.invoke(SocketIoJSONUtil.objectsFromJSON(args)); + } + }); } else { Log.e(TAG, "Cannot execute emit. mSocket is null. Initialize socket first!!!"); diff --git a/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoReadableNativeMap.java b/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoReadableNativeMap.java index 5e455f6..972016a 100644 --- a/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoReadableNativeMap.java +++ b/android/src/main/java/com/gcrabtree/rctsocketio/SocketIoReadableNativeMap.java @@ -2,6 +2,7 @@ import android.util.Log; +import com.facebook.jni.HybridData; import com.facebook.react.bridge.ReadableMapKeySetIterator; import com.facebook.react.bridge.ReadableNativeMap; diff --git a/index.js b/index.js index ff7f5fb..980e609 100644 --- a/index.js +++ b/index.js @@ -63,8 +63,8 @@ class Socket { this.onAnyHandler = handler; } - emit (event, data) { - this.sockets.emit(event, data); + emit (event, data, ack = () => console.log(`ACK ${event}`)) { + this.sockets.emit(event, data, ack); } joinNamespace (namespace) {