Merge branch 'development'
* development: (31 commits) Add note about namespace methods Add note in changelog to checkout the migration guide Add more to the migration guide don't return optional from defaultSocket Add link to 12to13 and style Start working on a migration guide Fix #855 refactor SocketIOClient.handlePacket bump version Do we really need to update carthage? Make sure to turn off logging in setup Don't use NSDictionary in definitions Fix tests Fix changelog update starscream versions nsp should be readonly now Add SocketManager for multiplexing namespaces Change sentPing/gotPong to ping/pong since those are already reserved Fix comment Add ability to track ping/pongs ...
This commit is contained in:
commit
176986d22a
10
.travis.yml
10
.travis.yml
@ -7,12 +7,12 @@ branches:
|
||||
- master
|
||||
- development
|
||||
before_install:
|
||||
- brew update
|
||||
# - brew update
|
||||
# - brew outdated xctool || brew upgrade xctool
|
||||
- brew outdated carthage || brew upgrade carthage
|
||||
# - brew outdated carthage || brew upgrade carthage
|
||||
- carthage update --platform macosx
|
||||
script:
|
||||
- xcodebuild -project Socket.IO-Client-Swift.xcodeproj -scheme SocketIO-Mac build test -quiet
|
||||
# - xcodebuild -project Socket.IO-Client-Swift.xcodeproj -scheme SocketIO-Mac build-for-testing -quiet
|
||||
# - xctool -project Socket.IO-Client-Swift.xcodeproj -scheme SocketIO-Mac run-tests --parallelize
|
||||
- xcodebuild -project Socket.IO-Client-Swift.xcodeproj -scheme SocketIO build test -quiet
|
||||
# - xcodebuild -project Socket.IO-Client-Swift.xcodeproj -scheme SocketIO build-for-testing -quiet
|
||||
# - xctool -project Socket.IO-Client-Swift.xcodeproj -scheme SocketIO run-tests --parallelize
|
||||
- swift test
|
||||
|
||||
20
CHANGELOG.md
Normal file
20
CHANGELOG.md
Normal file
@ -0,0 +1,20 @@
|
||||
# v13.0.0
|
||||
|
||||
Checkout out the migration guide in Usage Docs for a more detailed guide on how to migrate to this version.
|
||||
|
||||
What's new:
|
||||
---
|
||||
|
||||
- Adds a new `SocketManager` class that multiplexes multiple namespaces through a single engine.
|
||||
- Adds `.sentPing` and `.gotPong` client events for tracking ping/pongs.
|
||||
- watchOS support.
|
||||
|
||||
Important API changes
|
||||
---
|
||||
|
||||
- Many properties that were previously on `SocketIOClient` have been moved to the `SocketManager`.
|
||||
- `SocketIOClientOption.nsp` has been removed. Use `SocketManager.socket(forNamespace:)` to create/get a socket attached to a specific namespace.
|
||||
- Adds `.sentPing` and `.gotPong` client events for tracking ping/pongs.
|
||||
- Makes the framework a single target.
|
||||
- Updates Starscream to 3.0
|
||||
|
||||
2
Cartfile
2
Cartfile
@ -1 +1 @@
|
||||
github "daltoniam/Starscream" ~> 2.0
|
||||
github "daltoniam/Starscream" ~> 3.0
|
||||
|
||||
@ -1 +1,3 @@
|
||||
github "daltoniam/Starscream" "2.1.1"
|
||||
github "daltoniam/common-crypto-spm" "1.1.0"
|
||||
github "daltoniam/zlib-spm" "1.1.0"
|
||||
github "daltoniam/Starscream" "3.0.2"
|
||||
|
||||
@ -24,8 +24,8 @@
|
||||
"repositoryURL": "https://github.com/daltoniam/Starscream",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "21678c9426dde2a77152a0d5982cdb952baf0455",
|
||||
"version": "2.1.1"
|
||||
"revision": "44ce58956fae7db22fb0106cb4ce3dbef3d06d00",
|
||||
"version": "3.0.2"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -8,7 +8,7 @@ let package = Package(
|
||||
.library(name: "SocketIO", targets: ["SocketIO"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/daltoniam/Starscream", .upToNextMajor(from: "2.1.1")),
|
||||
.package(url: "https://github.com/daltoniam/Starscream", .upToNextMinor(from: "3.0.0")),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "SocketIO", dependencies: ["Starscream"]),
|
||||
|
||||
27
README.md
27
README.md
@ -7,20 +7,21 @@ Socket.IO-client for iOS/OS X.
|
||||
```swift
|
||||
import SocketIO
|
||||
|
||||
let socket = SocketIOClient(socketURL: URL(string: "http://localhost:8080")!, config: [.log(true), .compress])
|
||||
let manager = SocketManager(socketURL: URL(string: "http://localhost:8080")!, config: [.log(true), .compress])
|
||||
let socket = manager.defaultSocket
|
||||
|
||||
socket.on(clientEvent: .connect) {data, ack in
|
||||
print("socket connected")
|
||||
}
|
||||
|
||||
socket.on("currentAmount") {data, ack in
|
||||
if let cur = data[0] as? Double {
|
||||
socket.emitWithAck("canUpdate", cur).timingOut(after: 0) {data in
|
||||
socket.emit("update", ["amount": cur + 2.50])
|
||||
}
|
||||
guard let cur = data[0] as? Double else { return }
|
||||
|
||||
ack.with("Got your currentAmount", "dude")
|
||||
socket.emitWithAck("canUpdate", cur).timingOut(after: 0) {data in
|
||||
socket.emit("update", ["amount": cur + 2.50])
|
||||
}
|
||||
|
||||
ack.with("Got your currentAmount", "dude")
|
||||
}
|
||||
|
||||
socket.connect()
|
||||
@ -29,8 +30,10 @@ socket.connect()
|
||||
## Objective-C Example
|
||||
```objective-c
|
||||
@import SocketIO;
|
||||
|
||||
NSURL* url = [[NSURL alloc] initWithString:@"http://localhost:8080"];
|
||||
SocketIOClient* socket = [[SocketIOClient alloc] initWithSocketURL:url config:@{@"log": @YES, @"compress": @YES}];
|
||||
SocketManager* manager = [[SocketManager alloc] initWithSocketURL:url config:@{@"log": @YES, @"compress": @YES}];
|
||||
SocketIOClient* socket = manager.defaultSocket;
|
||||
|
||||
[socket on:@"connect" callback:^(NSArray* data, SocketAckEmitter* ack) {
|
||||
NSLog(@"socket connected");
|
||||
@ -60,6 +63,9 @@ SocketIOClient* socket = [[SocketIOClient alloc] initWithSocketURL:url config:@{
|
||||
## FAQS
|
||||
Checkout the [FAQs](https://nuclearace.github.io/Socket.IO-Client-Swift/faq.html) for commonly asked questions.
|
||||
|
||||
Checkout the [12to13](https://nuclearace.github.io/Socket.IO-Client-Swift/12to13.html) guide for migrating to v13.
|
||||
|
||||
|
||||
## Installation
|
||||
Requires Swift 4/Xcode 9.x
|
||||
|
||||
@ -80,7 +86,7 @@ let package = Package(
|
||||
.executable(name: "socket.io-test", targets: ["YourTargetName"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/socketio/socket.io-client-swift", .upToNextMajor(from: "12.1.0"))
|
||||
.package(url: "https://github.com/socketio/socket.io-client-swift", .upToNextMinor(from: "13.0.0"))
|
||||
],
|
||||
targets: [
|
||||
.target(name: "YourTargetName", dependencies: ["SocketIO"], path: "./Path/To/Your/Sources")
|
||||
@ -93,7 +99,7 @@ Then import `import SocketIO`.
|
||||
### Carthage
|
||||
Add this line to your `Cartfile`:
|
||||
```
|
||||
github "socketio/socket.io-client-swift" ~> 12.1.3 # Or latest version
|
||||
github "socketio/socket.io-client-swift" ~> 13.0.0 # Or latest version
|
||||
```
|
||||
|
||||
Run `carthage update --platform ios,macosx`.
|
||||
@ -107,7 +113,7 @@ Create `Podfile` and add `pod 'Socket.IO-Client-Swift'`:
|
||||
use_frameworks!
|
||||
|
||||
target 'YourApp' do
|
||||
pod 'Socket.IO-Client-Swift', '~> 12.1.3' # Or latest version
|
||||
pod 'Socket.IO-Client-Swift', '~> 13.0.0' # Or latest version
|
||||
end
|
||||
```
|
||||
|
||||
@ -134,6 +140,7 @@ Objective-C:
|
||||
# [Docs](https://nuclearace.github.io/Socket.IO-Client-Swift/index.html)
|
||||
|
||||
- [Client](https://nuclearace.github.io/Socket.IO-Client-Swift/Classes/SocketIOClient.html)
|
||||
- [Manager](https://nuclearace.github.io/Socket.IO-Client-Swift/Classes/SocketManager.html)
|
||||
- [Engine](https://nuclearace.github.io/Socket.IO-Client-Swift/Classes/SocketEngine.html)
|
||||
- [Options](https://nuclearace.github.io/Socket.IO-Client-Swift/Enums/SocketIOClientOption.html)
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "Socket.IO-Client-Swift"
|
||||
s.module_name = "SocketIO"
|
||||
s.version = "12.1.3"
|
||||
s.version = "13.0.0"
|
||||
s.summary = "Socket.IO-client for iOS and OS X"
|
||||
s.description = <<-DESC
|
||||
Socket.IO-client for iOS and OS X.
|
||||
@ -14,15 +14,16 @@ Pod::Spec.new do |s|
|
||||
s.ios.deployment_target = '8.0'
|
||||
s.osx.deployment_target = '10.10'
|
||||
s.tvos.deployment_target = '9.0'
|
||||
s.watchos.deployment_target = '2.0'
|
||||
s.requires_arc = true
|
||||
s.source = {
|
||||
:git => "https://github.com/socketio/socket.io-client-swift.git",
|
||||
:tag => 'v12.1.3',
|
||||
:tag => 'v13.0.0',
|
||||
:submodules => true
|
||||
}
|
||||
s.pod_target_xcconfig = {
|
||||
'SWIFT_VERSION' => '4.0'
|
||||
}
|
||||
s.source_files = "Source/SocketIO/**/*.swift", "Source/SocketIO/*.swift"
|
||||
s.dependency "Starscream", "~> 2.1.1"
|
||||
s.dependency "Starscream", "~> 3.0.2"
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,115 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0900"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "572EF2181B51F16C00EEBB58"
|
||||
BuildableName = "SocketIO.framework"
|
||||
BlueprintName = "SocketIO-iOS"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "NO"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "572EF2231B51F16C00EEBB58"
|
||||
BuildableName = "SocketIO-iOSTests.xctest"
|
||||
BlueprintName = "SocketIO-iOSTests"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "572EF2231B51F16C00EEBB58"
|
||||
BuildableName = "SocketIO-iOSTests.xctest"
|
||||
BlueprintName = "SocketIO-iOSTests"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "572EF2181B51F16C00EEBB58"
|
||||
BuildableName = "SocketIO.framework"
|
||||
BlueprintName = "SocketIO-iOS"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "572EF2181B51F16C00EEBB58"
|
||||
BuildableName = "SocketIO.framework"
|
||||
BlueprintName = "SocketIO-iOS"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "572EF2181B51F16C00EEBB58"
|
||||
BuildableName = "SocketIO.framework"
|
||||
BlueprintName = "SocketIO-iOS"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@ -1,115 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0900"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "576349FA1BD9B46A00E19CD7"
|
||||
BuildableName = "SocketIO.framework"
|
||||
BlueprintName = "SocketIO-tvOS"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "NO"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "57634A181BD9B46D00E19CD7"
|
||||
BuildableName = "SocketIO-tvOSTests.xctest"
|
||||
BlueprintName = "SocketIO-tvOSTests"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "57634A181BD9B46D00E19CD7"
|
||||
BuildableName = "SocketIO-tvOSTests.xctest"
|
||||
BlueprintName = "SocketIO-tvOSTests"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "576349FA1BD9B46A00E19CD7"
|
||||
BuildableName = "SocketIO.framework"
|
||||
BlueprintName = "SocketIO-tvOS"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "576349FA1BD9B46A00E19CD7"
|
||||
BuildableName = "SocketIO.framework"
|
||||
BlueprintName = "SocketIO-tvOS"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "576349FA1BD9B46A00E19CD7"
|
||||
BuildableName = "SocketIO.framework"
|
||||
BlueprintName = "SocketIO-tvOS"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@ -16,7 +16,7 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "572EF2371B51F18A00EEBB58"
|
||||
BuildableName = "SocketIO.framework"
|
||||
BlueprintName = "SocketIO-Mac"
|
||||
BlueprintName = "SocketIO"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
@ -29,8 +29,8 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "572EF2411B51F18A00EEBB58"
|
||||
BuildableName = "SocketIO-MacTests.xctest"
|
||||
BlueprintName = "SocketIO-MacTests"
|
||||
BuildableName = "SocketIO-Tests.xctest"
|
||||
BlueprintName = "SocketIO-Tests"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
@ -48,8 +48,8 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "572EF2411B51F18A00EEBB58"
|
||||
BuildableName = "SocketIO-MacTests.xctest"
|
||||
BlueprintName = "SocketIO-MacTests"
|
||||
BuildableName = "SocketIO-Tests.xctest"
|
||||
BlueprintName = "SocketIO-Tests"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
@ -59,7 +59,7 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "572EF2371B51F18A00EEBB58"
|
||||
BuildableName = "SocketIO.framework"
|
||||
BlueprintName = "SocketIO-Mac"
|
||||
BlueprintName = "SocketIO"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
@ -82,7 +82,7 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "572EF2371B51F18A00EEBB58"
|
||||
BuildableName = "SocketIO.framework"
|
||||
BlueprintName = "SocketIO-Mac"
|
||||
BlueprintName = "SocketIO"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
@ -100,7 +100,7 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "572EF2371B51F18A00EEBB58"
|
||||
BuildableName = "SocketIO.framework"
|
||||
BlueprintName = "SocketIO-Mac"
|
||||
BlueprintName = "SocketIO"
|
||||
ReferencedContainer = "container:Socket.IO-Client-Swift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -1,19 +0,0 @@
|
||||
//
|
||||
// SocketIO-iOS.h
|
||||
// SocketIO-iOS
|
||||
//
|
||||
// Created by Nacho Soto on 7/11/15.
|
||||
//
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
//! Project version number for SocketIO-iOS.
|
||||
FOUNDATION_EXPORT double SocketIO_iOSVersionNumber;
|
||||
|
||||
//! Project version string for SocketIO-iOS.
|
||||
FOUNDATION_EXPORT const unsigned char SocketIO_iOSVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <SocketIO_iOS/PublicHeader.h>
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
//
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
//! Project version number for SocketIO-Mac.
|
||||
FOUNDATION_EXPORT double SocketIO_MacVersionNumber;
|
||||
@ -120,8 +120,10 @@ public final class OnAckCallback : NSObject {
|
||||
|
||||
guard seconds != 0 else { return }
|
||||
|
||||
socket.handleQueue.asyncAfter(deadline: DispatchTime.now() + seconds) {
|
||||
socket.ackHandlers.timeoutAck(self.ackNumber, onQueue: socket.handleQueue)
|
||||
socket.manager?.handleQueue.asyncAfter(deadline: DispatchTime.now() + seconds) {[weak socket] in
|
||||
guard let socket = socket, let manager = socket.manager else { return }
|
||||
|
||||
socket.ackHandlers.timeoutAck(self.ackNumber, onQueue: manager.handleQueue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,108 +25,52 @@
|
||||
import Dispatch
|
||||
import Foundation
|
||||
|
||||
/// The main class for SocketIOClientSwift.
|
||||
/// Represents a socket.io-client.
|
||||
///
|
||||
/// **NOTE**: The client is not thread/queue safe, all interaction with the socket should be done on the `handleQueue`
|
||||
/// Clients are created through a `SocketManager`, which owns the `SocketEngineSpec` that controls the connection to the server.
|
||||
///
|
||||
/// Represents a socket.io-client. Most interaction with socket.io will be through this class.
|
||||
open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, SocketParsable {
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// // Create a socket for the /swift namespace
|
||||
/// let socket = manager.socket(forNamespace: "/swift")
|
||||
///
|
||||
/// // Add some handlers and connect
|
||||
/// ```
|
||||
///
|
||||
/// **NOTE**: The client is not thread/queue safe, all interaction with the socket should be done on the `manager.handleQueue`
|
||||
///
|
||||
open class SocketIOClient : NSObject, SocketIOClientSpec {
|
||||
// MARK: Properties
|
||||
|
||||
private static let logType = "SocketIOClient"
|
||||
|
||||
/// If `true` then every time `connect` is called, a new engine will be created.
|
||||
@objc
|
||||
public var forceNew = false
|
||||
|
||||
/// The queue that all interaction with the client should occur on. This is the queue that event handlers are
|
||||
/// called on.
|
||||
@objc
|
||||
public var handleQueue = DispatchQueue.main
|
||||
|
||||
/// The namespace that this socket is currently connected to.
|
||||
///
|
||||
/// **Must** start with a `/`.
|
||||
@objc
|
||||
public var nsp = "/"
|
||||
|
||||
/// The configuration for this client.
|
||||
///
|
||||
/// **This cannot be set after calling one of the connect methods**.
|
||||
public var config: SocketIOClientConfiguration {
|
||||
get {
|
||||
return _config
|
||||
}
|
||||
|
||||
set {
|
||||
guard status == .notConnected else {
|
||||
DefaultSocketLogger.Logger.error("Tried setting config after calling connect",
|
||||
type: SocketIOClient.logType)
|
||||
return
|
||||
}
|
||||
|
||||
_config = newValue
|
||||
|
||||
if socketURL.absoluteString.hasPrefix("https://") {
|
||||
_config.insert(.secure(true))
|
||||
}
|
||||
|
||||
_config.insert(.path("/socket.io/"), replacing: false)
|
||||
setConfigs()
|
||||
}
|
||||
}
|
||||
|
||||
/// If `true`, this client will try and reconnect on any disconnects.
|
||||
@objc
|
||||
public var reconnects = true
|
||||
|
||||
/// The number of seconds to wait before attempting to reconnect.
|
||||
@objc
|
||||
public var reconnectWait = 10
|
||||
public let nsp: String
|
||||
|
||||
/// The session id of this client.
|
||||
@objc
|
||||
public var sid: String? {
|
||||
return engine?.sid
|
||||
public var sid: String {
|
||||
guard let engine = manager?.engine else { return "" }
|
||||
|
||||
return nsp == "/" ? engine.sid : "\(nsp)#\(engine.sid)"
|
||||
}
|
||||
|
||||
/// The URL of the socket.io server.
|
||||
///
|
||||
/// If changed after calling `init`, `forceNew` must be set to `true`, or it will only connect to the url set in the
|
||||
/// init.
|
||||
@objc
|
||||
public var socketURL: URL
|
||||
|
||||
/// A list of packets that are waiting for binary data.
|
||||
///
|
||||
/// The way that socket.io works all data should be sent directly after each packet.
|
||||
/// So this should ideally be an array of one packet waiting for data.
|
||||
///
|
||||
/// **This should not be modified directly.**
|
||||
public var waitingPackets = [SocketPacket]()
|
||||
|
||||
/// A handler that will be called on any event.
|
||||
public private(set) var anyHandler: ((SocketAnyEvent) -> ())?
|
||||
|
||||
/// The engine for this client.
|
||||
@objc
|
||||
public internal(set) var engine: SocketEngineSpec?
|
||||
|
||||
/// The array of handlers for this socket.
|
||||
public private(set) var handlers = [SocketEventHandler]()
|
||||
|
||||
/// The manager for this socket.
|
||||
@objc
|
||||
public private(set) weak var manager: SocketManagerSpec?
|
||||
|
||||
/// The status of this client.
|
||||
@objc
|
||||
public private(set) var status = SocketIOClientStatus.notConnected {
|
||||
public private(set) var status = SocketIOStatus.notConnected {
|
||||
didSet {
|
||||
switch status {
|
||||
case .connected:
|
||||
reconnecting = false
|
||||
currentReconnectAttempt = 0
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
handleClientEvent(.statusChange, data: [status])
|
||||
}
|
||||
}
|
||||
@ -134,60 +78,29 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
var ackHandlers = SocketAckManager()
|
||||
|
||||
private(set) var currentAck = -1
|
||||
private(set) var reconnectAttempts = -1
|
||||
|
||||
private var _config: SocketIOClientConfiguration
|
||||
private var currentReconnectAttempt = 0
|
||||
private var reconnecting = false
|
||||
private lazy var logType = "SocketIOClient{\(nsp)}"
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
/// Type safe way to create a new SocketIOClient. `opts` can be omitted.
|
||||
///
|
||||
/// - parameter manager: The manager for this socket.
|
||||
/// - parameter socketURL: The url of the socket.io server.
|
||||
/// - parameter config: The config for this socket.
|
||||
public init(socketURL: URL, config: SocketIOClientConfiguration = []) {
|
||||
self._config = config
|
||||
self.socketURL = socketURL
|
||||
|
||||
if socketURL.absoluteString.hasPrefix("https://") {
|
||||
self._config.insert(.secure(true))
|
||||
}
|
||||
|
||||
self._config.insert(.path("/socket.io/"), replacing: false)
|
||||
@objc
|
||||
public init(manager: SocketManagerSpec, nsp: String) {
|
||||
self.manager = manager
|
||||
self.nsp = nsp
|
||||
|
||||
super.init()
|
||||
|
||||
setConfigs()
|
||||
}
|
||||
|
||||
/// 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<SocketIOClientOption>)`
|
||||
///
|
||||
/// - parameter socketURL: The url of the socket.io server.
|
||||
/// - parameter config: The config for this socket.
|
||||
@objc
|
||||
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: SocketIOClient.logType)
|
||||
engine?.disconnect(reason: "Client Deinit")
|
||||
DefaultSocketLogger.Logger.log("Client is being released", type: logType)
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
private func addEngine() {
|
||||
DefaultSocketLogger.Logger.log("Adding engine", type: SocketIOClient.logType)
|
||||
|
||||
engine?.engineQueue.sync {
|
||||
self.engine?.client = nil
|
||||
}
|
||||
|
||||
engine = SocketEngine(client: self, url: socketURL, config: config)
|
||||
}
|
||||
|
||||
/// Connect to the server. The same as calling `connect(timeoutAfter:withHandler:)` with a timeout of 0.
|
||||
///
|
||||
/// Only call after adding your event listeners, unless you know what you're doing.
|
||||
@ -207,27 +120,22 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
open func connect(timeoutAfter: Double, withHandler handler: (() -> ())?) {
|
||||
assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)")
|
||||
|
||||
guard status != .connected else {
|
||||
DefaultSocketLogger.Logger.log("Tried connecting on an already connected socket",
|
||||
type: SocketIOClient.logType)
|
||||
guard let manager = self.manager, status != .connected else {
|
||||
DefaultSocketLogger.Logger.log("Tried connecting on an already connected socket", type: logType)
|
||||
return
|
||||
}
|
||||
|
||||
status = .connecting
|
||||
|
||||
if engine == nil || forceNew {
|
||||
addEngine()
|
||||
}
|
||||
|
||||
engine?.connect()
|
||||
manager.connectSocket(self)
|
||||
|
||||
guard timeoutAfter != 0 else { return }
|
||||
|
||||
handleQueue.asyncAfter(deadline: DispatchTime.now() + timeoutAfter) {[weak self] in
|
||||
manager.handleQueue.asyncAfter(deadline: DispatchTime.now() + timeoutAfter) {[weak self] in
|
||||
guard let this = self, this.status == .connecting || this.status == .notConnected else { return }
|
||||
|
||||
this.status = .disconnected
|
||||
this.engine?.disconnect(reason: "Connect timeout")
|
||||
this.leaveNamespace()
|
||||
|
||||
handler?()
|
||||
}
|
||||
@ -246,7 +154,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
open func didConnect(toNamespace namespace: String) {
|
||||
guard status != .connected else { return }
|
||||
|
||||
DefaultSocketLogger.Logger.log("Socket connected", type: SocketIOClient.logType)
|
||||
DefaultSocketLogger.Logger.log("Socket connected", type: logType)
|
||||
|
||||
status = .connected
|
||||
|
||||
@ -259,21 +167,22 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
open func didDisconnect(reason: String) {
|
||||
guard status != .disconnected else { return }
|
||||
|
||||
DefaultSocketLogger.Logger.log("Disconnected: \(reason)", type: SocketIOClient.logType)
|
||||
DefaultSocketLogger.Logger.log("Disconnected: \(reason)", type: logType)
|
||||
|
||||
reconnecting = false
|
||||
status = .disconnected
|
||||
|
||||
// Make sure the engine is actually dead.
|
||||
engine?.disconnect(reason: reason)
|
||||
handleClientEvent(.disconnect, data: [reason])
|
||||
}
|
||||
|
||||
/// Disconnects the socket.
|
||||
///
|
||||
/// This will cause the socket to leave the namespace it is associated to, as well as remove itself from the
|
||||
/// `manager`.
|
||||
@objc
|
||||
open func disconnect() {
|
||||
DefaultSocketLogger.Logger.log("Closing socket", type: SocketIOClient.logType)
|
||||
DefaultSocketLogger.Logger.log("Closing socket", type: logType)
|
||||
|
||||
leaveNamespace()
|
||||
didDisconnect(reason: "Disconnect")
|
||||
}
|
||||
|
||||
@ -289,7 +198,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
try emit(event, with: items.map({ try $0.socketRepresentation() }))
|
||||
} catch let err {
|
||||
DefaultSocketLogger.Logger.error("Error creating socketRepresentation for emit: \(event), \(items)",
|
||||
type: SocketIOClient.logType)
|
||||
type: logType)
|
||||
|
||||
handleClientEvent(.error, data: [event, items, err])
|
||||
}
|
||||
@ -333,7 +242,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
return emitWithAck(event, with: try items.map({ try $0.socketRepresentation() }))
|
||||
} catch let err {
|
||||
DefaultSocketLogger.Logger.error("Error creating socketRepresentation for emit: \(event), \(items)",
|
||||
type: SocketIOClient.logType)
|
||||
type: logType)
|
||||
|
||||
handleClientEvent(.error, data: [event, items, err])
|
||||
|
||||
@ -371,9 +280,9 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
let packet = SocketPacket.packetFromEmit(data, id: ack ?? -1, nsp: nsp, ack: false)
|
||||
let str = packet.packetString
|
||||
|
||||
DefaultSocketLogger.Logger.log("Emitting: \(str)", type: SocketIOClient.logType)
|
||||
DefaultSocketLogger.Logger.log("Emitting: \(str)", type: logType)
|
||||
|
||||
engine?.send(str, withData: packet.binary)
|
||||
manager?.engine?.send(str, withData: packet.binary)
|
||||
}
|
||||
|
||||
/// Call when you wish to tell the server that you've received the event for `ack`.
|
||||
@ -388,69 +297,9 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
let packet = SocketPacket.packetFromEmit(items, id: ack, nsp: nsp, ack: true)
|
||||
let str = packet.packetString
|
||||
|
||||
DefaultSocketLogger.Logger.log("Emitting Ack: \(str)", type: SocketIOClient.logType)
|
||||
DefaultSocketLogger.Logger.log("Emitting Ack: \(str)", type: logType)
|
||||
|
||||
engine?.send(str, withData: packet.binary)
|
||||
}
|
||||
|
||||
/// Called when the engine closes.
|
||||
///
|
||||
/// - parameter reason: The reason that the engine closed.
|
||||
open func engineDidClose(reason: String) {
|
||||
handleQueue.async {
|
||||
self._engineDidClose(reason: reason)
|
||||
}
|
||||
}
|
||||
|
||||
private func _engineDidClose(reason: String) {
|
||||
waitingPackets.removeAll()
|
||||
|
||||
if status != .disconnected {
|
||||
status = .notConnected
|
||||
}
|
||||
|
||||
if status == .disconnected || !reconnects {
|
||||
didDisconnect(reason: reason)
|
||||
} else if !reconnecting {
|
||||
reconnecting = true
|
||||
tryReconnect(reason: reason)
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when the engine errors.
|
||||
///
|
||||
/// - parameter reason: The reason the engine errored.
|
||||
open func engineDidError(reason: String) {
|
||||
handleQueue.async {
|
||||
self._engineDidError(reason: reason)
|
||||
}
|
||||
}
|
||||
|
||||
private func _engineDidError(reason: String) {
|
||||
DefaultSocketLogger.Logger.error("\(reason)", type: SocketIOClient.logType)
|
||||
|
||||
handleClientEvent(.error, data: [reason])
|
||||
}
|
||||
|
||||
/// Called when the engine opens.
|
||||
///
|
||||
/// - parameter reason: The reason the engine opened.
|
||||
open func engineDidOpen(reason: String) {
|
||||
handleQueue.async {
|
||||
self._engineDidOpen(reason: reason)
|
||||
}
|
||||
}
|
||||
|
||||
private func _engineDidOpen(reason: String) {
|
||||
DefaultSocketLogger.Logger.log("Engine opened \(reason)", type: SocketIOClient.logType)
|
||||
|
||||
guard nsp != "/" else {
|
||||
didConnect(toNamespace: "/")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
joinNamespace(nsp)
|
||||
manager?.engine?.send(str, withData: packet.binary)
|
||||
}
|
||||
|
||||
/// Called when socket.io has acked one of our emits. Causes the corresponding ack callback to be called.
|
||||
@ -459,11 +308,19 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
/// - parameter data: The data sent back with this ack.
|
||||
@objc
|
||||
open func handleAck(_ ack: Int, data: [Any]) {
|
||||
guard status == .connected else { return }
|
||||
guard status == .connected, let manager = self.manager else { return }
|
||||
|
||||
DefaultSocketLogger.Logger.log("Handling ack: \(ack) with data: \(data)", type: SocketIOClient.logType)
|
||||
DefaultSocketLogger.Logger.log("Handling ack: \(ack) with data: \(data)", type: logType)
|
||||
|
||||
ackHandlers.executeAck(ack, with: data, onQueue: handleQueue)
|
||||
ackHandlers.executeAck(ack, with: data, onQueue: manager.handleQueue)
|
||||
}
|
||||
|
||||
/// Called on socket.io specific events.
|
||||
///
|
||||
/// - parameter event: The `SocketClientEvent`.
|
||||
/// - parameter data: The data for this event.
|
||||
open func handleClientEvent(_ event: SocketClientEvent, data: [Any]) {
|
||||
handleEvent(event.rawValue, data: data, isInternalMessage: true)
|
||||
}
|
||||
|
||||
/// Called when we get an event from socket.io.
|
||||
@ -476,7 +333,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
open func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int = -1) {
|
||||
guard status == .connected || isInternalMessage else { return }
|
||||
|
||||
DefaultSocketLogger.Logger.log("Handling event: \(event) with data: \(data)", type: SocketIOClient.logType)
|
||||
DefaultSocketLogger.Logger.log("Handling event: \(event) with data: \(data)", type: logType)
|
||||
|
||||
anyHandler?(SocketAnyEvent(event: event, items: data))
|
||||
|
||||
@ -485,36 +342,45 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
}
|
||||
}
|
||||
|
||||
/// Called on socket.io specific events.
|
||||
/// Causes a client to handle a socket.io packet. The namespace for the packet must match the namespace of the
|
||||
/// socket.
|
||||
///
|
||||
/// - parameter event: The `SocketClientEvent`.
|
||||
/// - parameter data: The data for this event.
|
||||
open func handleClientEvent(_ event: SocketClientEvent, data: [Any]) {
|
||||
handleEvent(event.rawValue, data: data, isInternalMessage: true)
|
||||
/// - parameter packet: The packet to handle.
|
||||
open func handlePacket(_ packet: SocketPacket) {
|
||||
guard packet.nsp == nsp else { return }
|
||||
|
||||
switch packet.type {
|
||||
case .event, .binaryEvent:
|
||||
handleEvent(packet.event, data: packet.args, isInternalMessage: false, withAck: packet.id)
|
||||
case .ack, .binaryAck:
|
||||
handleAck(packet.id, data: packet.data)
|
||||
case .connect:
|
||||
didConnect(toNamespace: nsp)
|
||||
case .disconnect:
|
||||
didDisconnect(reason: "Got Disconnect")
|
||||
case .error:
|
||||
handleEvent("error", data: packet.data, isInternalMessage: true, withAck: packet.id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Call when you wish to leave a namespace and return to the default namespace.
|
||||
/// Call when you wish to leave a namespace and disconnect this socket.
|
||||
@objc
|
||||
open func leaveNamespace() {
|
||||
guard nsp != "/" else { return }
|
||||
|
||||
engine?.send("1\(nsp)", withData: [])
|
||||
nsp = "/"
|
||||
status = .disconnected
|
||||
|
||||
manager?.disconnectSocket(self)
|
||||
}
|
||||
|
||||
/// Joins `namespace`.
|
||||
///
|
||||
/// **Do not use this to join the default namespace.** Instead call `leaveNamespace`.
|
||||
///
|
||||
/// - parameter namespace: The namespace to join.
|
||||
/// Joins `nsp`.
|
||||
@objc
|
||||
open func joinNamespace(_ namespace: String) {
|
||||
guard namespace != "/" else { return }
|
||||
open func joinNamespace() {
|
||||
guard nsp != "/" else { return }
|
||||
|
||||
DefaultSocketLogger.Logger.log("Joining namespace \(namespace)", type: SocketIOClient.logType)
|
||||
DefaultSocketLogger.Logger.log("Joining namespace \(nsp)", type: logType)
|
||||
|
||||
nsp = namespace
|
||||
engine?.send("0\(nsp)", withData: [])
|
||||
manager?.engine?.send("0\(nsp)", withData: [])
|
||||
}
|
||||
|
||||
/// Removes handler(s) for a client event.
|
||||
@ -533,7 +399,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
/// - parameter event: The event to remove handlers for.
|
||||
@objc
|
||||
open func off(_ event: String) {
|
||||
DefaultSocketLogger.Logger.log("Removing handler for event: \(event)", type: SocketIOClient.logType)
|
||||
DefaultSocketLogger.Logger.log("Removing handler for event: \(event)", type: logType)
|
||||
|
||||
handlers = handlers.filter({ $0.event != event })
|
||||
}
|
||||
@ -545,7 +411,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
/// - parameter id: The UUID of the handler you wish to remove.
|
||||
@objc
|
||||
open func off(id: UUID) {
|
||||
DefaultSocketLogger.Logger.log("Removing handler with id: \(id)", type: SocketIOClient.logType)
|
||||
DefaultSocketLogger.Logger.log("Removing handler with id: \(id)", type: logType)
|
||||
|
||||
handlers = handlers.filter({ $0.id != id })
|
||||
}
|
||||
@ -558,7 +424,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
@objc
|
||||
@discardableResult
|
||||
open func on(_ event: String, callback: @escaping NormalCallback) -> UUID {
|
||||
DefaultSocketLogger.Logger.log("Adding handler for event: \(event)", type: SocketIOClient.logType)
|
||||
DefaultSocketLogger.Logger.log("Adding handler for event: \(event)", type: logType)
|
||||
|
||||
let handler = SocketEventHandler(event: event, id: UUID(), callback: callback)
|
||||
handlers.append(handler)
|
||||
@ -581,12 +447,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
/// - returns: A unique id for the handler that can be used to remove it.
|
||||
@discardableResult
|
||||
open func on(clientEvent event: SocketClientEvent, callback: @escaping NormalCallback) -> UUID {
|
||||
DefaultSocketLogger.Logger.log("Adding handler for event: \(event)", type: SocketIOClient.logType)
|
||||
|
||||
let handler = SocketEventHandler(event: event.rawValue, id: UUID(), callback: callback)
|
||||
handlers.append(handler)
|
||||
|
||||
return handler.id
|
||||
return on(event.rawValue, callback: callback)
|
||||
}
|
||||
|
||||
/// Adds a single-use handler for a client event.
|
||||
@ -607,7 +468,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
@objc
|
||||
@discardableResult
|
||||
open func once(_ event: String, callback: @escaping NormalCallback) -> UUID {
|
||||
DefaultSocketLogger.Logger.log("Adding once handler for event: \(event)", type: SocketIOClient.logType)
|
||||
DefaultSocketLogger.Logger.log("Adding once handler for event: \(event)", type: logType)
|
||||
|
||||
let id = UUID()
|
||||
|
||||
@ -630,31 +491,10 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
anyHandler = handler
|
||||
}
|
||||
|
||||
/// Called when the engine has a message that must be parsed.
|
||||
///
|
||||
/// - parameter msg: The message that needs parsing.
|
||||
public func parseEngineMessage(_ msg: String) {
|
||||
DefaultSocketLogger.Logger.log("Should parse message: \(msg)", type: SocketIOClient.logType)
|
||||
|
||||
handleQueue.async { self.parseSocketMessage(msg) }
|
||||
}
|
||||
|
||||
/// Called when the engine receives binary data.
|
||||
///
|
||||
/// - parameter data: The data the engine received.
|
||||
public func parseEngineBinaryData(_ data: Data) {
|
||||
handleQueue.async { self.parseBinaryData(data) }
|
||||
}
|
||||
|
||||
/// Tries to reconnect to the server.
|
||||
///
|
||||
/// This will cause a `disconnect` event to be emitted, as well as an `reconnectAttempt` event.
|
||||
@objc
|
||||
open func reconnect() {
|
||||
guard !reconnecting else { return }
|
||||
|
||||
engine?.disconnect(reason: "manual reconnect")
|
||||
}
|
||||
@available(*, unavailable, message: "Call the manager's reconnect method")
|
||||
open func reconnect() { }
|
||||
|
||||
/// Removes all handlers.
|
||||
///
|
||||
@ -664,54 +504,15 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
handlers.removeAll(keepingCapacity: false)
|
||||
}
|
||||
|
||||
private func tryReconnect(reason: String) {
|
||||
guard reconnecting else { return }
|
||||
/// Puts the socket back into the connecting state.
|
||||
/// Called when the manager detects a broken connection, or when a manual reconnect is triggered.
|
||||
///
|
||||
/// - parameter reason: The reason this socket is reconnecting.
|
||||
@objc
|
||||
open func setReconnecting(reason: String) {
|
||||
status = .connecting
|
||||
|
||||
DefaultSocketLogger.Logger.log("Starting reconnect", type: SocketIOClient.logType)
|
||||
handleClientEvent(.reconnect, data: [reason])
|
||||
|
||||
_tryReconnect()
|
||||
}
|
||||
|
||||
private func _tryReconnect() {
|
||||
guard reconnects && reconnecting && status != .disconnected else { return }
|
||||
|
||||
if reconnectAttempts != -1 && currentReconnectAttempt + 1 > reconnectAttempts {
|
||||
return didDisconnect(reason: "Reconnect Failed")
|
||||
}
|
||||
|
||||
DefaultSocketLogger.Logger.log("Trying to reconnect", type: SocketIOClient.logType)
|
||||
handleClientEvent(.reconnectAttempt, data: [(reconnectAttempts - currentReconnectAttempt)])
|
||||
|
||||
currentReconnectAttempt += 1
|
||||
connect()
|
||||
|
||||
handleQueue.asyncAfter(deadline: DispatchTime.now() + Double(reconnectWait), execute: _tryReconnect)
|
||||
}
|
||||
|
||||
private func setConfigs() {
|
||||
for option in config {
|
||||
switch option {
|
||||
case let .reconnects(reconnects):
|
||||
self.reconnects = reconnects
|
||||
case let .reconnectAttempts(attempts):
|
||||
reconnectAttempts = attempts
|
||||
case let .reconnectWait(wait):
|
||||
reconnectWait = abs(wait)
|
||||
case let .nsp(nsp):
|
||||
self.nsp = nsp
|
||||
case let .log(log):
|
||||
DefaultSocketLogger.Logger.log = log
|
||||
case let .logger(logger):
|
||||
DefaultSocketLogger.Logger = logger
|
||||
case let .handleQueue(queue):
|
||||
handleQueue = queue
|
||||
case let .forceNew(force):
|
||||
forceNew = force
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test properties
|
||||
@ -724,14 +525,10 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So
|
||||
status = .connected
|
||||
}
|
||||
|
||||
func setTestStatus(_ status: SocketIOClientStatus) {
|
||||
func setTestStatus(_ status: SocketIOStatus) {
|
||||
self.status = status
|
||||
}
|
||||
|
||||
func setTestEngine(_ engine: SocketEngineSpec?) {
|
||||
self.engine = engine
|
||||
}
|
||||
|
||||
func emitTest(event: String, _ data: Any...) {
|
||||
emit([event] + data)
|
||||
}
|
||||
|
||||
@ -125,5 +125,12 @@ public struct SocketIOClientConfiguration : ExpressibleByArrayLiteral, Collectio
|
||||
|
||||
backingArray.append(element)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Declares that a type can set configs from a `SocketIOClientConfiguration`.
|
||||
public protocol ConfigSettable {
|
||||
/// Called when an `ConfigSettable` should set/update its configs from a given configuration.
|
||||
///
|
||||
/// - parameter config: The `SocketIOClientConfiguration` that should be used to set/update configs.
|
||||
mutating func setConfigs(_ config: SocketIOClientConfiguration)
|
||||
}
|
||||
|
||||
@ -40,10 +40,6 @@ public enum SocketIOClientOption : ClientOption {
|
||||
/// An array of cookies that will be sent during the initial connection.
|
||||
case cookies([HTTPCookie])
|
||||
|
||||
/// Deprecated
|
||||
@available(*, deprecated, message: "No longer needed in socket.io 2.0+")
|
||||
case doubleEncodeUTF8(Bool)
|
||||
|
||||
/// Any extra HTTP headers that should be sent during the initial connection.
|
||||
case extraHeaders([String: String])
|
||||
|
||||
@ -67,10 +63,6 @@ public enum SocketIOClientOption : ClientOption {
|
||||
/// Used to pass in a custom logger.
|
||||
case logger(SocketLogger)
|
||||
|
||||
/// The namespace that this client should connect to. Can be changed during use using the `joinNamespace`
|
||||
/// and `leaveNamespace` methods on `SocketIOClient`.
|
||||
case nsp(String)
|
||||
|
||||
/// A custom path to socket.io. Only use this if the socket.io server is configured to look for this path.
|
||||
case path(String)
|
||||
|
||||
@ -96,11 +88,6 @@ public enum SocketIOClientOption : ClientOption {
|
||||
/// Sets an NSURLSessionDelegate for the underlying engine. Useful if you need to handle self-signed certs.
|
||||
case sessionDelegate(URLSessionDelegate)
|
||||
|
||||
/// If passed `true`, the WebSocket transport will try and use voip logic to keep network connections open in
|
||||
/// the background. **This option is experimental as socket.io shouldn't be used for background communication.**
|
||||
@available(*, deprecated, message: "No longer has any effect, and will be removed in v11.0")
|
||||
case voipEnabled(Bool)
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// The description of this option.
|
||||
@ -114,8 +101,6 @@ public enum SocketIOClientOption : ClientOption {
|
||||
description = "connectParams"
|
||||
case .cookies:
|
||||
description = "cookies"
|
||||
case .doubleEncodeUTF8:
|
||||
description = "doubleEncodeUTF8"
|
||||
case .extraHeaders:
|
||||
description = "extraHeaders"
|
||||
case .forceNew:
|
||||
@ -130,8 +115,6 @@ public enum SocketIOClientOption : ClientOption {
|
||||
description = "log"
|
||||
case .logger:
|
||||
description = "logger"
|
||||
case .nsp:
|
||||
description = "nsp"
|
||||
case .path:
|
||||
description = "path"
|
||||
case .reconnects:
|
||||
@ -148,8 +131,6 @@ public enum SocketIOClientOption : ClientOption {
|
||||
description = "security"
|
||||
case .sessionDelegate:
|
||||
description = "sessionDelegate"
|
||||
case .voipEnabled:
|
||||
description = "voipEnabled"
|
||||
}
|
||||
|
||||
return description
|
||||
@ -165,8 +146,6 @@ public enum SocketIOClientOption : ClientOption {
|
||||
value = params
|
||||
case let .cookies(cookies):
|
||||
value = cookies
|
||||
case let .doubleEncodeUTF8(encode):
|
||||
value = encode
|
||||
case let .extraHeaders(headers):
|
||||
value = headers
|
||||
case let .forceNew(force):
|
||||
@ -181,8 +160,6 @@ public enum SocketIOClientOption : ClientOption {
|
||||
value = log
|
||||
case let .logger(logger):
|
||||
value = logger
|
||||
case let .nsp(nsp):
|
||||
value = nsp
|
||||
case let .path(path):
|
||||
value = path
|
||||
case let .reconnects(reconnects):
|
||||
@ -199,8 +176,6 @@ public enum SocketIOClientOption : ClientOption {
|
||||
value = signed
|
||||
case let .sessionDelegate(delegate):
|
||||
value = delegate
|
||||
case let .voipEnabled(enabled):
|
||||
value = enabled
|
||||
}
|
||||
|
||||
return value
|
||||
|
||||
@ -32,22 +32,19 @@ public protocol SocketIOClientSpec : class {
|
||||
/// A handler that will be called on any event.
|
||||
var anyHandler: ((SocketAnyEvent) -> ())? { get }
|
||||
|
||||
/// The configuration for this client.
|
||||
var config: SocketIOClientConfiguration { get set }
|
||||
|
||||
/// The queue that all interaction with the client must be on.
|
||||
var handleQueue: DispatchQueue { get set }
|
||||
|
||||
/// The array of handlers for this socket.
|
||||
var handlers: [SocketEventHandler] { get }
|
||||
|
||||
/// The manager for this socket.
|
||||
var manager: SocketManagerSpec? { get }
|
||||
|
||||
/// The namespace that this socket is currently connected to.
|
||||
///
|
||||
/// **Must** start with a `/`.
|
||||
var nsp: String { get set }
|
||||
var nsp: String { get }
|
||||
|
||||
/// The status of this client.
|
||||
var status: SocketIOClientStatus { get }
|
||||
var status: SocketIOStatus { get }
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
@ -126,6 +123,12 @@ public protocol SocketIOClientSpec : class {
|
||||
/// - parameter data: The data sent back with this ack.
|
||||
func handleAck(_ ack: Int, data: [Any])
|
||||
|
||||
/// Called on socket.io specific events.
|
||||
///
|
||||
/// - parameter event: The `SocketClientEvent`.
|
||||
/// - parameter data: The data for this event.
|
||||
func handleClientEvent(_ event: SocketClientEvent, data: [Any])
|
||||
|
||||
/// Called when we get an event from socket.io.
|
||||
///
|
||||
/// - parameter event: The name of the event.
|
||||
@ -134,21 +137,17 @@ public protocol SocketIOClientSpec : class {
|
||||
/// - parameter withAck: If > 0 then this event expects to get an ack back from the client.
|
||||
func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int)
|
||||
|
||||
/// Called on socket.io specific events.
|
||||
/// Causes a client to handle a socket.io packet. The namespace for the packet must match the namespace of the
|
||||
/// socket.
|
||||
///
|
||||
/// - parameter event: The `SocketClientEvent`.
|
||||
/// - parameter data: The data for this event.
|
||||
func handleClientEvent(_ event: SocketClientEvent, data: [Any])
|
||||
/// - parameter packet: The packet to handle.
|
||||
func handlePacket(_ packet: SocketPacket)
|
||||
|
||||
/// Call when you wish to leave a namespace and return to the default namespace.
|
||||
/// Call when you wish to leave a namespace and disconnect this socket.
|
||||
func leaveNamespace()
|
||||
|
||||
/// Joins `namespace`.
|
||||
///
|
||||
/// **Do not use this to join the default namespace.** Instead call `leaveNamespace`.
|
||||
///
|
||||
/// - parameter namespace: The namespace to join.
|
||||
func joinNamespace(_ namespace: String)
|
||||
/// Joins `nsp`.
|
||||
func joinNamespace()
|
||||
|
||||
/// Removes handler(s) for a client event.
|
||||
///
|
||||
@ -212,13 +211,16 @@ public protocol SocketIOClientSpec : class {
|
||||
/// - parameter handler: The callback that will execute whenever an event is received.
|
||||
func onAny(_ handler: @escaping (SocketAnyEvent) -> ())
|
||||
|
||||
/// Tries to reconnect to the server.
|
||||
func reconnect()
|
||||
|
||||
/// Removes all handlers.
|
||||
///
|
||||
/// Can be used after disconnecting to break any potential remaining retain cycles.
|
||||
func removeAllHandlers()
|
||||
|
||||
/// Puts the socket back into the connecting state.
|
||||
/// Called when the manager detects a broken connection, or when a manual reconnect is triggered.
|
||||
///
|
||||
/// parameter reason: The reason this socket is going reconnecting.
|
||||
func setReconnecting(reason: String)
|
||||
}
|
||||
|
||||
public extension SocketIOClientSpec {
|
||||
@ -245,18 +247,80 @@ public enum SocketClientEvent : String {
|
||||
/// ```
|
||||
case connect
|
||||
|
||||
/// Called when the socket has disconnected and will not attempt to try to reconnect.
|
||||
/// Emitted when the socket has disconnected and will not attempt to try to reconnect.
|
||||
///
|
||||
/// Usage:
|
||||
///
|
||||
/// ```swift
|
||||
/// socket.on(clientEvent: .disconnect) {data, ack in
|
||||
/// // Some cleanup logic
|
||||
/// }
|
||||
/// ```
|
||||
case disconnect
|
||||
|
||||
/// Called when an error occurs.
|
||||
/// Emitted when an error occurs.
|
||||
///
|
||||
/// Usage:
|
||||
///
|
||||
/// ```swift
|
||||
/// socket.on(clientEvent: .error) {data, ack in
|
||||
/// // Some logging
|
||||
/// }
|
||||
/// ```
|
||||
case error
|
||||
|
||||
/// Called when the client begins the reconnection process.
|
||||
/// Emitted whenever the engine sends a ping.
|
||||
///
|
||||
/// Usage:
|
||||
///
|
||||
/// ```swift
|
||||
/// socket.on(clientEvent: .ping) {_, _ in
|
||||
/// // Maybe keep track of latency?
|
||||
/// }
|
||||
/// ```
|
||||
case ping
|
||||
|
||||
/// Emitted whenever the engine gets a pong.
|
||||
///
|
||||
/// Usage:
|
||||
///
|
||||
/// ```swift
|
||||
/// socket.on(clientEvent: .pong) {_, _ in
|
||||
/// // Maybe keep track of latency?
|
||||
/// }
|
||||
/// ```
|
||||
case pong
|
||||
|
||||
/// Emitted when the client begins the reconnection process.
|
||||
///
|
||||
/// Usage:
|
||||
///
|
||||
/// ```swift
|
||||
/// socket.on(clientEvent: .reconnect) {data, ack in
|
||||
/// // Some reconnect event logic
|
||||
/// }
|
||||
/// ```
|
||||
case reconnect
|
||||
|
||||
/// Called each time the client tries to reconnect to the server.
|
||||
/// Emitted each time the client tries to reconnect to the server.
|
||||
///
|
||||
/// Usage:
|
||||
///
|
||||
/// ```swift
|
||||
/// socket.on(clientEvent: .reconnectAttempt) {data, ack in
|
||||
/// // Some reconnect attempt logging
|
||||
/// }
|
||||
/// ```
|
||||
case reconnectAttempt
|
||||
|
||||
/// Called every time there is a change in the client's status.
|
||||
/// Emitted every time there is a change in the client's status.
|
||||
///
|
||||
/// Usage:
|
||||
///
|
||||
/// ```swift
|
||||
/// socket.on(clientEvent: .statusChange) {data, ack in
|
||||
/// // Some status changing logging
|
||||
/// }
|
||||
/// ```
|
||||
case statusChange
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// SocketIOClientStatus.swift
|
||||
// SocketIOStatus.swift
|
||||
// Socket.IO-Client-Swift
|
||||
//
|
||||
// Created by Erik Little on 8/14/15.
|
||||
@ -24,17 +24,36 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Represents the state of the client.
|
||||
@objc public enum SocketIOClientStatus : Int {
|
||||
/// The client has never been connected. Or the client has been reset.
|
||||
/// Represents state of a manager or client.
|
||||
@objc
|
||||
public enum SocketIOStatus : Int, CustomStringConvertible {
|
||||
// MARK: Cases
|
||||
|
||||
/// The client/manager has never been connected. Or the client has been reset.
|
||||
case notConnected
|
||||
|
||||
/// The client was once connected, but not anymore.
|
||||
/// The client/manager was once connected, but not anymore.
|
||||
case disconnected
|
||||
|
||||
/// The client is in the process of connecting.
|
||||
/// The client/manager is in the process of connecting.
|
||||
case connecting
|
||||
|
||||
/// The client is currently connected.
|
||||
/// The client/manager is currently connected.
|
||||
case connected
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
/// - returns: True if this client/manager is connected/connecting to a server.
|
||||
public var active: Bool {
|
||||
return self == .connected || self == .connecting
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .connected: return "connected"
|
||||
case .connecting: return "connecting"
|
||||
case .disconnected: return "disconnected"
|
||||
case .notConnected: return "notConnected"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -28,7 +28,8 @@ import Starscream
|
||||
|
||||
/// The class that handles the engine.io protocol and transports.
|
||||
/// See `SocketEnginePollable` and `SocketEngineWebsocket` for transport specific methods.
|
||||
public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, SocketEngineWebsocket {
|
||||
public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, SocketEngineWebsocket,
|
||||
ConfigSettable {
|
||||
// MARK: Properties
|
||||
|
||||
private static let logType = "SocketEngine"
|
||||
@ -147,41 +148,11 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll
|
||||
public init(client: SocketEngineClient, url: URL, config: SocketIOClientConfiguration) {
|
||||
self.client = client
|
||||
self.url = url
|
||||
for option in config {
|
||||
switch option {
|
||||
case let .connectParams(params):
|
||||
connectParams = params
|
||||
case let .cookies(cookies):
|
||||
self.cookies = cookies
|
||||
case let .extraHeaders(headers):
|
||||
extraHeaders = headers
|
||||
case let .sessionDelegate(delegate):
|
||||
sessionDelegate = delegate
|
||||
case let .forcePolling(force):
|
||||
forcePolling = force
|
||||
case let .forceWebsockets(force):
|
||||
forceWebsockets = force
|
||||
case let .path(path):
|
||||
socketPath = path
|
||||
|
||||
if !socketPath.hasSuffix("/") {
|
||||
socketPath += "/"
|
||||
}
|
||||
case let .secure(secure):
|
||||
self.secure = secure
|
||||
case let .selfSigned(selfSigned):
|
||||
self.selfSigned = selfSigned
|
||||
case let .security(security):
|
||||
self.security = security
|
||||
case .compress:
|
||||
self.compress = true
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
setConfigs(config)
|
||||
|
||||
sessionDelegate = sessionDelegate ?? self
|
||||
|
||||
(urlPolling, urlWebSocket) = createURLs()
|
||||
@ -192,7 +163,7 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll
|
||||
/// - parameter client: The client for this engine.
|
||||
/// - parameter url: The url for this engine.
|
||||
/// - parameter options: The options for this engine.
|
||||
public convenience init(client: SocketEngineClient, url: URL, options: NSDictionary?) {
|
||||
public convenience init(client: SocketEngineClient, url: URL, options: [String: Any]?) {
|
||||
self.init(client: client, url: url, config: options?.toSocketConfiguration() ?? [])
|
||||
}
|
||||
|
||||
@ -310,22 +281,22 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll
|
||||
|
||||
private func createWebSocketAndConnect() {
|
||||
ws?.delegate = nil // TODO this seems a bit defensive, is this really needed?
|
||||
ws = WebSocket(url: urlWebSocketWithSid)
|
||||
var req = URLRequest(url: urlWebSocketWithSid)
|
||||
|
||||
if cookies != nil {
|
||||
let headers = HTTPCookie.requestHeaderFields(with: cookies!)
|
||||
for (key, value) in headers {
|
||||
ws?.headers[key] = value
|
||||
for (headerName, value) in headers {
|
||||
req.setValue(value, forHTTPHeaderField: headerName)
|
||||
}
|
||||
}
|
||||
|
||||
if extraHeaders != nil {
|
||||
for (headerName, value) in extraHeaders! {
|
||||
ws?.headers[headerName] = value
|
||||
req.setValue(value, forHTTPHeaderField: headerName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ws = WebSocket(request: req)
|
||||
ws?.callbackQueue = engineQueue
|
||||
ws?.enableCompression = compress
|
||||
ws?.delegate = self
|
||||
@ -485,6 +456,8 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll
|
||||
if message == "3probe" {
|
||||
upgradeTransport()
|
||||
}
|
||||
|
||||
client?.engineDidReceivePong()
|
||||
}
|
||||
|
||||
/// Parses raw binary received from engine.io.
|
||||
@ -569,6 +542,46 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll
|
||||
|
||||
this.sendPing()
|
||||
}
|
||||
|
||||
client?.engineDidSendPing()
|
||||
}
|
||||
|
||||
/// Called when the engine should set/update its configs from a given configuration.
|
||||
///
|
||||
/// parameter config: The `SocketIOClientConfiguration` that should be used to set/update configs.
|
||||
open func setConfigs(_ config: SocketIOClientConfiguration) {
|
||||
for option in config {
|
||||
switch option {
|
||||
case let .connectParams(params):
|
||||
connectParams = params
|
||||
case let .cookies(cookies):
|
||||
self.cookies = cookies
|
||||
case let .extraHeaders(headers):
|
||||
extraHeaders = headers
|
||||
case let .sessionDelegate(delegate):
|
||||
sessionDelegate = delegate
|
||||
case let .forcePolling(force):
|
||||
forcePolling = force
|
||||
case let .forceWebsockets(force):
|
||||
forceWebsockets = force
|
||||
case let .path(path):
|
||||
socketPath = path
|
||||
|
||||
if !socketPath.hasSuffix("/") {
|
||||
socketPath += "/"
|
||||
}
|
||||
case let .secure(secure):
|
||||
self.secure = secure
|
||||
case let .selfSigned(selfSigned):
|
||||
self.selfSigned = selfSigned
|
||||
case let .security(security):
|
||||
self.security = security
|
||||
case .compress:
|
||||
self.compress = true
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Moves from long-polling to websockets
|
||||
@ -608,7 +621,7 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll
|
||||
// MARK: Starscream delegate conformance
|
||||
|
||||
/// Delegate method for connection.
|
||||
public func websocketDidConnect(socket: WebSocket) {
|
||||
public func websocketDidConnect(socket: WebSocketClient) {
|
||||
if !forceWebsockets {
|
||||
probing = true
|
||||
probeWebSocket()
|
||||
@ -620,7 +633,7 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll
|
||||
}
|
||||
|
||||
/// Delegate method for disconnection.
|
||||
public func websocketDidDisconnect(socket: WebSocket, error: NSError?) {
|
||||
public func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
|
||||
probing = false
|
||||
|
||||
if closed {
|
||||
@ -644,6 +657,12 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll
|
||||
client?.engineDidClose(reason: "Socket Disconnected")
|
||||
}
|
||||
}
|
||||
|
||||
// Test Properties
|
||||
|
||||
func setConnected(_ value: Bool) {
|
||||
connected = value
|
||||
}
|
||||
}
|
||||
|
||||
extension SocketEngine {
|
||||
|
||||
@ -44,6 +44,12 @@ import Foundation
|
||||
/// - parameter reason: The reason the engine opened.
|
||||
func engineDidOpen(reason: String)
|
||||
|
||||
/// Called when the engine receives a pong message.
|
||||
func engineDidReceivePong()
|
||||
|
||||
/// Called when the engine sends a ping to the server.
|
||||
func engineDidSendPing()
|
||||
|
||||
/// Called when the engine has a message that must be parsed.
|
||||
///
|
||||
/// - parameter msg: The message that needs parsing.
|
||||
|
||||
@ -34,6 +34,9 @@ import Starscream
|
||||
/// `true` if this engine is closed.
|
||||
var closed: Bool { get }
|
||||
|
||||
/// If `true` the engine will attempt to use WebSocket compression.
|
||||
var compress: Bool { get }
|
||||
|
||||
/// `true` if this engine is connected. Connected means that the initial poll connect has succeeded.
|
||||
var connected: Bool { get }
|
||||
|
||||
@ -87,7 +90,7 @@ import Starscream
|
||||
/// - parameter client: The client for this engine.
|
||||
/// - parameter url: The url for this engine.
|
||||
/// - parameter options: The options for this engine.
|
||||
init(client: SocketEngineClient, url: URL, options: NSDictionary?)
|
||||
init(client: SocketEngineClient, url: URL, options: [String: Any]?)
|
||||
|
||||
/// Starts the connection to the server.
|
||||
func connect()
|
||||
|
||||
@ -68,12 +68,12 @@ extension SocketEngineWebsocket {
|
||||
// MARK: Starscream delegate methods
|
||||
|
||||
/// Delegate method for when a message is received.
|
||||
public func websocketDidReceiveMessage(socket: WebSocket, text: String) {
|
||||
public func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
|
||||
parseEngineMessage(text)
|
||||
}
|
||||
|
||||
/// Delegate method for when binary is received.
|
||||
public func websocketDidReceiveData(socket: WebSocket, data: Data) {
|
||||
public func websocketDidReceiveData(socket: WebSocketClient, data: Data) {
|
||||
parseEngineData(data)
|
||||
}
|
||||
}
|
||||
|
||||
519
Source/SocketIO/Manager/SocketManager.swift
Normal file
519
Source/SocketIO/Manager/SocketManager.swift
Normal file
@ -0,0 +1,519 @@
|
||||
//
|
||||
// Created by Erik Little on 10/14/17.
|
||||
//
|
||||
// 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 Dispatch
|
||||
import Foundation
|
||||
|
||||
///
|
||||
/// A manager for a socket.io connection.
|
||||
///
|
||||
/// A `SocketManager` is responsible for multiplexing multiple namespaces through a single `SocketEngineSpec`.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```swift
|
||||
/// let manager = SocketManager(socketURL: URL(string:"http://localhost:8080/")!)
|
||||
/// let defaultNamespaceSocket = manager.defaultSocket
|
||||
/// let swiftSocket = manager.socket(forNamespace: "/swift")
|
||||
///
|
||||
/// // defaultNamespaceSocket and swiftSocket both share a single connection to the server
|
||||
/// ```
|
||||
///
|
||||
/// Sockets created through the manager are retained by the manager. So at the very least, a single strong reference
|
||||
/// to the manager must be maintained to keep sockets alive.
|
||||
///
|
||||
/// To disconnect a socket and remove it from the manager, either call `SocketIOClient.disconnect()` on the socket,
|
||||
/// or call one of the `disconnectSocket` methods on this class.
|
||||
///
|
||||
open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDataBufferable, ConfigSettable {
|
||||
private static let logType = "SocketManager"
|
||||
|
||||
// MARK Properties
|
||||
|
||||
/// The socket associated with the default namespace ("/").
|
||||
public var defaultSocket: SocketIOClient {
|
||||
return socket(forNamespace: "/")
|
||||
}
|
||||
|
||||
/// The URL of the socket.io server.
|
||||
///
|
||||
/// If changed after calling `init`, `forceNew` must be set to `true`, or it will only connect to the url set in the
|
||||
/// init.
|
||||
public let socketURL: URL
|
||||
|
||||
/// The configuration for this client.
|
||||
///
|
||||
/// **Some configs will not take affect until after a reconnect if set after calling a connect method**.
|
||||
public var config: SocketIOClientConfiguration {
|
||||
get {
|
||||
return _config
|
||||
}
|
||||
|
||||
set {
|
||||
if status.active {
|
||||
DefaultSocketLogger.Logger.log("Setting configs on active manager. Some configs may not be applied until reconnect",
|
||||
type: SocketManager.logType)
|
||||
}
|
||||
|
||||
setConfigs(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// The engine for this manager.
|
||||
public var engine: SocketEngineSpec?
|
||||
|
||||
/// If `true` then every time `connect` is called, a new engine will be created.
|
||||
public var forceNew = false
|
||||
|
||||
/// The queue that all interaction with the client should occur on. This is the queue that event handlers are
|
||||
/// called on.
|
||||
///
|
||||
/// **This should be a serial queue! Concurrent queues are not supported and might cause crashes and races**.
|
||||
public var handleQueue = DispatchQueue.main
|
||||
|
||||
/// The sockets in this manager indexed by namespace.
|
||||
public var nsps = [String: SocketIOClient]()
|
||||
|
||||
/// If `true`, this client will try and reconnect on any disconnects.
|
||||
public var reconnects = true
|
||||
|
||||
/// The number of seconds to wait before attempting to reconnect.
|
||||
public var reconnectWait = 10
|
||||
|
||||
/// The status of this manager.
|
||||
public private(set) var status: SocketIOStatus = .notConnected {
|
||||
didSet {
|
||||
switch status {
|
||||
case .connected:
|
||||
reconnecting = false
|
||||
currentReconnectAttempt = 0
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of packets that are waiting for binary data.
|
||||
///
|
||||
/// The way that socket.io works all data should be sent directly after each packet.
|
||||
/// So this should ideally be an array of one packet waiting for data.
|
||||
///
|
||||
/// **This should not be modified directly.**
|
||||
public var waitingPackets = [SocketPacket]()
|
||||
|
||||
private(set) var reconnectAttempts = -1
|
||||
|
||||
private var _config: SocketIOClientConfiguration
|
||||
private var currentReconnectAttempt = 0
|
||||
private var reconnecting = false
|
||||
|
||||
/// Type safe way to create a new SocketIOClient. `opts` can be omitted.
|
||||
///
|
||||
/// - parameter socketURL: The url of the socket.io server.
|
||||
/// - parameter config: The config for this socket.
|
||||
public init(socketURL: URL, config: SocketIOClientConfiguration = []) {
|
||||
self._config = config
|
||||
self.socketURL = socketURL
|
||||
|
||||
if socketURL.absoluteString.hasPrefix("https://") {
|
||||
self._config.insert(.secure(true))
|
||||
}
|
||||
|
||||
self._config.insert(.path("/socket.io/"), replacing: false)
|
||||
|
||||
super.init()
|
||||
|
||||
setConfigs(_config)
|
||||
}
|
||||
|
||||
/// 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<SocketIOClientOption>)`
|
||||
///
|
||||
/// - parameter socketURL: The url of the socket.io server.
|
||||
/// - parameter config: The config for this socket.
|
||||
@objc
|
||||
public convenience init(socketURL: URL, config: [String: Any]?) {
|
||||
self.init(socketURL: socketURL, config: config?.toSocketConfiguration() ?? [])
|
||||
}
|
||||
|
||||
deinit {
|
||||
DefaultSocketLogger.Logger.log("Manager is being released", type: SocketManager.logType)
|
||||
|
||||
engine?.disconnect(reason: "Manager Deinit")
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
private func addEngine() {
|
||||
DefaultSocketLogger.Logger.log("Adding engine", type: SocketManager.logType)
|
||||
|
||||
engine?.engineQueue.sync {
|
||||
self.engine?.client = nil
|
||||
}
|
||||
|
||||
engine = SocketEngine(client: self, url: socketURL, config: config)
|
||||
}
|
||||
|
||||
/// Connects the underlying transport and the default namespace socket.
|
||||
///
|
||||
/// Override if you wish to attach a custom `SocketEngineSpec`.
|
||||
open func connect() {
|
||||
guard !status.active else {
|
||||
DefaultSocketLogger.Logger.log("Tried connecting an already active socket", type: SocketManager.logType)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if engine == nil || forceNew {
|
||||
addEngine()
|
||||
}
|
||||
|
||||
status = .connecting
|
||||
|
||||
engine?.connect()
|
||||
}
|
||||
|
||||
/// Connects a socket through this manager's engine.
|
||||
///
|
||||
/// - parameter socket: The socket who we should connect through this manager.
|
||||
open func connectSocket(_ socket: SocketIOClient) {
|
||||
guard status == .connected else {
|
||||
DefaultSocketLogger.Logger.log("Tried connecting socket when engine isn't open. Connecting",
|
||||
type: SocketManager.logType)
|
||||
|
||||
connect()
|
||||
return
|
||||
}
|
||||
|
||||
engine?.send("0\(socket.nsp)", withData: [])
|
||||
}
|
||||
|
||||
/// Called when the manager has disconnected from socket.io.
|
||||
///
|
||||
/// - parameter reason: The reason for the disconnection.
|
||||
open func didDisconnect(reason: String) {
|
||||
forAll {socket in
|
||||
socket.didDisconnect(reason: reason)
|
||||
}
|
||||
}
|
||||
|
||||
/// Disconnects the manager and all associated sockets.
|
||||
open func disconnect() {
|
||||
DefaultSocketLogger.Logger.log("Manager closing", type: SocketManager.logType)
|
||||
|
||||
status = .disconnected
|
||||
|
||||
engine?.disconnect(reason: "Disconnect")
|
||||
}
|
||||
|
||||
/// Disconnects the given socket.
|
||||
///
|
||||
/// This will remove the socket for the manager's control, and make the socket instance useless and ready for
|
||||
/// releasing.
|
||||
///
|
||||
/// - parameter socket: The socket to disconnect.
|
||||
open func disconnectSocket(_ socket: SocketIOClient) {
|
||||
// Make sure we remove socket from nsps
|
||||
nsps.removeValue(forKey: socket.nsp)
|
||||
|
||||
engine?.send("1\(socket.nsp)", withData: [])
|
||||
socket.didDisconnect(reason: "Namespace leave")
|
||||
}
|
||||
|
||||
/// Disconnects the socket associated with `forNamespace`.
|
||||
///
|
||||
/// This will remove the socket for the manager's control, and make the socket instance useless and ready for
|
||||
/// releasing.
|
||||
///
|
||||
/// - parameter forNamespace: The namespace to disconnect from.
|
||||
open func disconnectSocket(forNamespace nsp: String) {
|
||||
guard let socket = nsps.removeValue(forKey: nsp) else {
|
||||
DefaultSocketLogger.Logger.log("Could not find socket for \(nsp) to disconnect",
|
||||
type: SocketManager.logType)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
disconnectSocket(socket)
|
||||
}
|
||||
|
||||
/// Sends a client event to all sockets in `nsps`
|
||||
///
|
||||
/// - parameter clientEvent: The event to emit.
|
||||
open func emitAll(clientEvent event: SocketClientEvent, data: [Any]) {
|
||||
forAll {socket in
|
||||
socket.handleClientEvent(event, data: data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends an event to the server on all namespaces in this manager.
|
||||
///
|
||||
/// - parameter event: The event to send.
|
||||
/// - parameter items: The data to send with this event.
|
||||
open func emitAll(_ event: String, _ items: SocketData...) {
|
||||
guard let emitData = try? items.map({ try $0.socketRepresentation() }) else {
|
||||
DefaultSocketLogger.Logger.error("Error creating socketRepresentation for emit: \(event), \(items)",
|
||||
type: SocketManager.logType)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
emitAll(event, withItems: emitData)
|
||||
}
|
||||
|
||||
/// Sends an event to the server on all namespaces in this manager.
|
||||
///
|
||||
/// Same as `emitAll(_:_:)`, but meant for Objective-C.
|
||||
///
|
||||
/// - parameter event: The event to send.
|
||||
/// - parameter withItems: The data to send with this event.
|
||||
open func emitAll(_ event: String, withItems items: [Any]) {
|
||||
forAll {socket in
|
||||
socket.emit(event, with: items)
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when the engine closes.
|
||||
///
|
||||
/// - parameter reason: The reason that the engine closed.
|
||||
open func engineDidClose(reason: String) {
|
||||
handleQueue.async {
|
||||
self._engineDidClose(reason: reason)
|
||||
}
|
||||
}
|
||||
|
||||
private func _engineDidClose(reason: String) {
|
||||
waitingPackets.removeAll()
|
||||
|
||||
if status != .disconnected {
|
||||
status = .notConnected
|
||||
}
|
||||
|
||||
if status == .disconnected || !reconnects {
|
||||
didDisconnect(reason: reason)
|
||||
} else if !reconnecting {
|
||||
reconnecting = true
|
||||
tryReconnect(reason: reason)
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when the engine errors.
|
||||
///
|
||||
/// - parameter reason: The reason the engine errored.
|
||||
open func engineDidError(reason: String) {
|
||||
handleQueue.async {
|
||||
self._engineDidError(reason: reason)
|
||||
}
|
||||
}
|
||||
|
||||
private func _engineDidError(reason: String) {
|
||||
DefaultSocketLogger.Logger.error("\(reason)", type: SocketManager.logType)
|
||||
|
||||
emitAll(clientEvent: .error, data: [reason])
|
||||
}
|
||||
|
||||
/// Called when the engine opens.
|
||||
///
|
||||
/// - parameter reason: The reason the engine opened.
|
||||
open func engineDidOpen(reason: String) {
|
||||
handleQueue.async {
|
||||
self._engineDidOpen(reason: reason)
|
||||
}
|
||||
}
|
||||
|
||||
private func _engineDidOpen(reason: String) {
|
||||
DefaultSocketLogger.Logger.log("Engine opened \(reason)", type: SocketManager.logType)
|
||||
|
||||
status = .connected
|
||||
nsps["/"]?.didConnect(toNamespace: "/")
|
||||
|
||||
for (nsp, socket) in nsps where nsp != "/" && socket.status == .connecting {
|
||||
connectSocket(socket)
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when the engine receives a pong message.
|
||||
open func engineDidReceivePong() {
|
||||
handleQueue.async {
|
||||
self._engineDidReceivePong()
|
||||
}
|
||||
}
|
||||
|
||||
private func _engineDidReceivePong() {
|
||||
emitAll(clientEvent: .pong, data: [])
|
||||
}
|
||||
|
||||
/// Called when the sends a ping to the server.
|
||||
open func engineDidSendPing() {
|
||||
handleQueue.async {
|
||||
self._engineDidSendPing()
|
||||
}
|
||||
}
|
||||
|
||||
private func _engineDidSendPing() {
|
||||
emitAll(clientEvent: .ping, data: [])
|
||||
}
|
||||
|
||||
private func forAll(do: (SocketIOClient) throws -> ()) rethrows {
|
||||
for (_, socket) in nsps {
|
||||
try `do`(socket)
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when the engine has a message that must be parsed.
|
||||
///
|
||||
/// - parameter msg: The message that needs parsing.
|
||||
open func parseEngineMessage(_ msg: String) {
|
||||
handleQueue.async {
|
||||
self._parseEngineMessage(msg)
|
||||
}
|
||||
}
|
||||
|
||||
private func _parseEngineMessage(_ msg: String) {
|
||||
guard let packet = parseSocketMessage(msg) else { return }
|
||||
guard packet.type != .binaryAck && packet.type != .binaryEvent else {
|
||||
waitingPackets.append(packet)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
nsps[packet.nsp]?.handlePacket(packet)
|
||||
}
|
||||
|
||||
/// Called when the engine receives binary data.
|
||||
///
|
||||
/// - parameter data: The data the engine received.
|
||||
open func parseEngineBinaryData(_ data: Data) {
|
||||
handleQueue.async {
|
||||
self._parseEngineBinaryData(data)
|
||||
}
|
||||
}
|
||||
|
||||
private func _parseEngineBinaryData(_ data: Data) {
|
||||
guard let packet = parseBinaryData(data) else { return }
|
||||
|
||||
nsps[packet.nsp]?.handlePacket(packet)
|
||||
}
|
||||
|
||||
/// Tries to reconnect to the server.
|
||||
///
|
||||
/// This will cause a `disconnect` event to be emitted, as well as an `reconnectAttempt` event.
|
||||
open func reconnect() {
|
||||
guard !reconnecting else { return }
|
||||
|
||||
engine?.disconnect(reason: "manual reconnect")
|
||||
}
|
||||
|
||||
private func tryReconnect(reason: String) {
|
||||
guard reconnecting else { return }
|
||||
|
||||
DefaultSocketLogger.Logger.log("Starting reconnect", type: SocketManager.logType)
|
||||
|
||||
// Set status to connecting and emit reconnect for all sockets
|
||||
forAll {socket in
|
||||
guard socket.status == .connected else { return }
|
||||
|
||||
socket.setReconnecting(reason: reason)
|
||||
}
|
||||
|
||||
_tryReconnect()
|
||||
}
|
||||
|
||||
private func _tryReconnect() {
|
||||
guard reconnects && reconnecting && status != .disconnected else { return }
|
||||
|
||||
if reconnectAttempts != -1 && currentReconnectAttempt + 1 > reconnectAttempts {
|
||||
return didDisconnect(reason: "Reconnect Failed")
|
||||
}
|
||||
|
||||
DefaultSocketLogger.Logger.log("Trying to reconnect", type: SocketManager.logType)
|
||||
emitAll(clientEvent: .reconnectAttempt, data: [(reconnectAttempts - currentReconnectAttempt)])
|
||||
|
||||
currentReconnectAttempt += 1
|
||||
connect()
|
||||
|
||||
handleQueue.asyncAfter(deadline: DispatchTime.now() + Double(reconnectWait), execute: _tryReconnect)
|
||||
}
|
||||
|
||||
/// Sets manager specific configs.
|
||||
///
|
||||
/// parameter config: The configs that should be set.
|
||||
open func setConfigs(_ config: SocketIOClientConfiguration) {
|
||||
for option in config {
|
||||
switch option {
|
||||
case let .forceNew(new):
|
||||
self.forceNew = new
|
||||
case let .reconnects(reconnects):
|
||||
self.reconnects = reconnects
|
||||
case let .reconnectWait(wait):
|
||||
reconnectWait = abs(wait)
|
||||
case let .log(log):
|
||||
DefaultSocketLogger.Logger.log = log
|
||||
case let .logger(logger):
|
||||
DefaultSocketLogger.Logger = logger
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
_config = config
|
||||
_config.insert(.path("/socket.io/"), replacing: false)
|
||||
|
||||
// If `ConfigSettable` & `SocketEngineSpec`, update its configs.
|
||||
if var settableEngine = engine as? ConfigSettable & SocketEngineSpec {
|
||||
settableEngine.engineQueue.sync {
|
||||
settableEngine.setConfigs(self._config)
|
||||
}
|
||||
|
||||
engine = settableEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `SocketIOClient` for the given namespace. This socket shares a transport with the manager.
|
||||
///
|
||||
/// Calling multiple times returns the same socket.
|
||||
///
|
||||
/// Sockets created from this method are retained by the manager.
|
||||
/// Call one of the `disconnectSocket` methods on this class to remove the socket from manager control.
|
||||
/// Or call `SocketIOClient.disconnect()` on the client.
|
||||
///
|
||||
/// - parameter forNamespace: The namespace for the socket.
|
||||
/// - returns: A `SocketIOClient` for the given namespace.
|
||||
open func socket(forNamespace nsp: String) -> SocketIOClient {
|
||||
assert(nsp.hasPrefix("/"), "forNamespace must have a leading /")
|
||||
|
||||
if let socket = nsps[nsp] {
|
||||
return socket
|
||||
}
|
||||
|
||||
let client = SocketIOClient(manager: self, nsp: nsp)
|
||||
|
||||
nsps[nsp] = client
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// Test properties
|
||||
|
||||
func setTestStatus(_ status: SocketIOStatus) {
|
||||
self.status = status
|
||||
}
|
||||
}
|
||||
128
Source/SocketIO/Manager/SocketManagerSpec.swift
Normal file
128
Source/SocketIO/Manager/SocketManagerSpec.swift
Normal file
@ -0,0 +1,128 @@
|
||||
//
|
||||
// Created by Erik Little on 10/18/17.
|
||||
//
|
||||
// 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 Dispatch
|
||||
import Foundation
|
||||
|
||||
// TODO Fix the types so that we aren't using concrete types
|
||||
|
||||
///
|
||||
/// A manager for a socket.io connection.
|
||||
///
|
||||
/// A `SocketManagerSpec` is responsible for multiplexing multiple namespaces through a single `SocketEngineSpec`.
|
||||
///
|
||||
/// Example with `SocketManager`:
|
||||
///
|
||||
/// ```swift
|
||||
/// let manager = SocketManager(socketURL: URL(string:"http://localhost:8080/")!)
|
||||
/// let defaultNamespaceSocket = manager.defaultSocket
|
||||
/// let swiftSocket = manager.socket(forNamespace: "/swift")
|
||||
///
|
||||
/// // defaultNamespaceSocket and swiftSocket both share a single connection to the server
|
||||
/// ```
|
||||
///
|
||||
/// Sockets created through the manager are retained by the manager. So at the very least, a single strong reference
|
||||
/// to the manager must be maintained to keep sockets alive.
|
||||
///
|
||||
/// To disconnect a socket and remove it from the manager, either call `SocketIOClient.disconnect()` on the socket,
|
||||
/// or call one of the `disconnectSocket` methods on this class.
|
||||
///
|
||||
@objc
|
||||
public protocol SocketManagerSpec : class, SocketEngineClient {
|
||||
// MARK: Properties
|
||||
|
||||
/// Returns the socket associated with the default namespace ("/").
|
||||
var defaultSocket: SocketIOClient { get }
|
||||
|
||||
/// The engine for this manager.
|
||||
var engine: SocketEngineSpec? { get set }
|
||||
|
||||
/// If `true` then every time `connect` is called, a new engine will be created.
|
||||
var forceNew: Bool { get set }
|
||||
|
||||
// TODO Per socket queues?
|
||||
/// The queue that all interaction with the client should occur on. This is the queue that event handlers are
|
||||
/// called on.
|
||||
var handleQueue: DispatchQueue { get set }
|
||||
|
||||
/// If `true`, this manager will try and reconnect on any disconnects.
|
||||
var reconnects: Bool { get set }
|
||||
|
||||
/// The number of seconds to wait before attempting to reconnect.
|
||||
var reconnectWait: Int { get set }
|
||||
|
||||
/// The URL of the socket.io server.
|
||||
var socketURL: URL { get }
|
||||
|
||||
/// The status of this manager.
|
||||
var status: SocketIOStatus { get }
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
/// Connects the underlying transport.
|
||||
func connect()
|
||||
|
||||
/// Connects a socket through this manager's engine.
|
||||
///
|
||||
/// - parameter socket: The socket who we should connect through this manager.
|
||||
func connectSocket(_ socket: SocketIOClient)
|
||||
|
||||
/// Called when the manager has disconnected from socket.io.
|
||||
///
|
||||
/// - parameter reason: The reason for the disconnection.
|
||||
func didDisconnect(reason: String)
|
||||
|
||||
/// Disconnects the manager and all associated sockets.
|
||||
func disconnect()
|
||||
|
||||
/// Disconnects the given socket.
|
||||
///
|
||||
/// - parameter socket: The socket to disconnect.
|
||||
func disconnectSocket(_ socket: SocketIOClient)
|
||||
|
||||
/// Disconnects the socket associated with `forNamespace`.
|
||||
///
|
||||
/// - parameter forNamespace: The namespace to disconnect from.
|
||||
func disconnectSocket(forNamespace nsp: String)
|
||||
|
||||
/// Sends an event to the server on all namespaces in this manager.
|
||||
///
|
||||
/// - parameter event: The event to send.
|
||||
/// - parameter withItems: The data to send with this event.
|
||||
func emitAll(_ event: String, withItems items: [Any])
|
||||
|
||||
/// Tries to reconnect to the server.
|
||||
///
|
||||
/// This will cause a `disconnect` event to be emitted, as well as an `reconnectAttempt` event.
|
||||
func reconnect()
|
||||
|
||||
/// Returns a `SocketIOClient` for the given namespace. This socket shares a transport with the manager.
|
||||
///
|
||||
/// Calling multiple times returns the same socket.
|
||||
///
|
||||
/// Sockets created from this method are retained by the manager.
|
||||
/// Call one of the `disconnectSocket` methods on the implementing class to remove the socket from manager control.
|
||||
/// Or call `SocketIOClient.disconnect()` on the client.
|
||||
///
|
||||
/// - parameter forNamespace: The namespace for the socket.
|
||||
/// - returns: A `SocketIOClient` for the given namespace.
|
||||
func socket(forNamespace nsp: String) -> SocketIOClient
|
||||
}
|
||||
@ -142,12 +142,8 @@ public struct SocketPacket : CustomStringConvertible {
|
||||
if dict["_placeholder"] as? Bool ?? false {
|
||||
return binary[dict["num"] as! Int]
|
||||
} else {
|
||||
return dict.reduce(JSON(), {cur, keyValue in
|
||||
var cur = cur
|
||||
|
||||
return dict.reduce(into: JSON(), {cur, keyValue in
|
||||
cur[keyValue.0] = _fillInPlaceholders(keyValue.1)
|
||||
|
||||
return cur
|
||||
})
|
||||
}
|
||||
case let arr as [Any]:
|
||||
@ -225,12 +221,8 @@ private extension SocketPacket {
|
||||
case let arr as [Any]:
|
||||
return arr.map({shred($0, binary: &binary)})
|
||||
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
|
||||
return dict.reduce(into: JSON(), {cur, keyValue in
|
||||
cur[keyValue.0] = shred(keyValue.1, binary: &binary)
|
||||
})
|
||||
default:
|
||||
return data
|
||||
|
||||
@ -26,14 +26,6 @@ import Foundation
|
||||
public protocol SocketParsable : class {
|
||||
// MARK: Properties
|
||||
|
||||
/// A list of packets that are waiting for binary data.
|
||||
///
|
||||
/// The way that socket.io works all data should be sent directly after each packet.
|
||||
/// So this should ideally be an array of one packet waiting for data.
|
||||
///
|
||||
/// **This should not be modified directly.**
|
||||
var waitingPackets: [SocketPacket] { get set }
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
/// Called when the engine has received some binary data that should be attached to a packet.
|
||||
@ -43,12 +35,13 @@ public protocol SocketParsable : class {
|
||||
/// into the correct placeholder.
|
||||
///
|
||||
/// - parameter data: The data that should be attached to a packet.
|
||||
func parseBinaryData(_ data: Data)
|
||||
func parseBinaryData(_ data: Data) -> SocketPacket?
|
||||
|
||||
/// Called when the engine has received a string that should be parsed into a socket.io packet.
|
||||
///
|
||||
/// - parameter message: The string that needs parsing.
|
||||
func parseSocketMessage(_ message: String)
|
||||
/// - returns: A completed socket packet if there is no more data left to collect.
|
||||
func parseSocketMessage(_ message: String) -> SocketPacket?
|
||||
}
|
||||
|
||||
/// Errors that can be thrown during parsing.
|
||||
@ -65,38 +58,18 @@ public enum SocketParsableError : Error {
|
||||
case invalidPacketType
|
||||
}
|
||||
|
||||
public extension SocketParsable where Self: SocketIOClientSpec {
|
||||
private func isCorrectNamespace(_ nsp: String) -> Bool {
|
||||
return nsp == self.nsp
|
||||
}
|
||||
|
||||
private func handleConnect(_ packetNamespace: String) {
|
||||
guard packetNamespace == nsp else { return }
|
||||
|
||||
didConnect(toNamespace: packetNamespace)
|
||||
}
|
||||
|
||||
private func handlePacket(_ pack: SocketPacket) {
|
||||
switch pack.type {
|
||||
case .event where isCorrectNamespace(pack.nsp):
|
||||
handleEvent(pack.event, data: pack.args, isInternalMessage: false, withAck: pack.id)
|
||||
case .ack where isCorrectNamespace(pack.nsp):
|
||||
handleAck(pack.id, data: pack.data)
|
||||
case .binaryEvent where isCorrectNamespace(pack.nsp):
|
||||
waitingPackets.append(pack)
|
||||
case .binaryAck where isCorrectNamespace(pack.nsp):
|
||||
waitingPackets.append(pack)
|
||||
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: \(pack.description)", type: "SocketParser")
|
||||
}
|
||||
}
|
||||
/// Says that a type will be able to buffer binary data before all data for an event has come in.
|
||||
public protocol SocketDataBufferable : class {
|
||||
/// A list of packets that are waiting for binary data.
|
||||
///
|
||||
/// The way that socket.io works all data should be sent directly after each packet.
|
||||
/// So this should ideally be an array of one packet waiting for data.
|
||||
///
|
||||
/// **This should not be modified directly.**
|
||||
var waitingPackets: [SocketPacket] { get set }
|
||||
}
|
||||
|
||||
public extension SocketParsable where Self: SocketManagerSpec & SocketDataBufferable {
|
||||
/// Parses a message from the engine, returning a complete SocketPacket or throwing.
|
||||
///
|
||||
/// - parameter message: The message to parse.
|
||||
@ -169,8 +142,9 @@ public extension SocketParsable where Self: SocketIOClientSpec {
|
||||
/// Called when the engine has received a string that should be parsed into a socket.io packet.
|
||||
///
|
||||
/// - parameter message: The string that needs parsing.
|
||||
public func parseSocketMessage(_ message: String) {
|
||||
guard !message.isEmpty else { return }
|
||||
/// - returns: A completed socket packet or nil if the packet is invalid.
|
||||
public func parseSocketMessage(_ message: String) -> SocketPacket? {
|
||||
guard !message.isEmpty else { return nil }
|
||||
|
||||
DefaultSocketLogger.Logger.log("Parsing \(message)", type: "SocketParser")
|
||||
|
||||
@ -179,9 +153,11 @@ public extension SocketParsable where Self: SocketIOClientSpec {
|
||||
|
||||
DefaultSocketLogger.Logger.log("Decoded packet as: \(packet.description)", type: "SocketParser")
|
||||
|
||||
handlePacket(packet)
|
||||
return packet
|
||||
} catch {
|
||||
DefaultSocketLogger.Logger.error("\(error): \(message)", type: "SocketParser")
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,21 +168,17 @@ public extension SocketParsable where Self: SocketIOClientSpec {
|
||||
/// into the correct placeholder.
|
||||
///
|
||||
/// - parameter data: The data that should be attached to a packet.
|
||||
public func parseBinaryData(_ data: Data) {
|
||||
/// - returns: A completed socket packet if there is no more data left to collect.
|
||||
public func parseBinaryData(_ data: Data) -> SocketPacket? {
|
||||
guard !waitingPackets.isEmpty else {
|
||||
DefaultSocketLogger.Logger.error("Got data when not remaking packet", type: "SocketParser")
|
||||
return
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Should execute event?
|
||||
guard waitingPackets[waitingPackets.count - 1].addData(data) else { return }
|
||||
guard waitingPackets[waitingPackets.count - 1].addData(data) else { return nil }
|
||||
|
||||
let packet = waitingPackets.removeLast()
|
||||
|
||||
if packet.type != .binaryAck {
|
||||
handleEvent(packet.event, data: packet.args, isInternalMessage: false, withAck: packet.id)
|
||||
} else {
|
||||
handleAck(packet.id, data: packet.args)
|
||||
}
|
||||
return waitingPackets.removeLast()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,110 +0,0 @@
|
||||
//
|
||||
// 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 {
|
||||
// MARK: Properties.
|
||||
|
||||
/// The shared manager.
|
||||
@objc
|
||||
open static let sharedManager = SocketClientManager()
|
||||
|
||||
private var sockets = [String: SocketIOClient]()
|
||||
|
||||
/// Gets a socket by its name.
|
||||
///
|
||||
/// - returns: The socket, if one had the given name.
|
||||
open subscript(string: String) -> SocketIOClient? {
|
||||
get {
|
||||
return sockets[string]
|
||||
}
|
||||
|
||||
set(socket) {
|
||||
sockets[string] = socket
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Methods.
|
||||
|
||||
/// Adds a socket.
|
||||
///
|
||||
/// - parameter socket: The socket to add.
|
||||
/// - parameter labeledAs: The label for this socket.
|
||||
@objc
|
||||
open func addSocket(_ socket: SocketIOClient, labeledAs label: String) {
|
||||
sockets[label] = socket
|
||||
}
|
||||
|
||||
/// Removes a socket by a given name.
|
||||
///
|
||||
/// - parameter withLabel: The label of the socket to remove.
|
||||
/// - returns: The socket for the given label, if one was present.
|
||||
@objc
|
||||
@discardableResult
|
||||
open func removeSocket(withLabel label: String) -> SocketIOClient? {
|
||||
return sockets.removeValue(forKey: label)
|
||||
}
|
||||
|
||||
/// Removes a socket.
|
||||
///
|
||||
/// - parameter socket: The socket to remove.
|
||||
/// - returns: The socket if it was in the manager.
|
||||
@objc
|
||||
@discardableResult
|
||||
open func removeSocket(_ socket: SocketIOClient) -> SocketIOClient? {
|
||||
var returnSocket: SocketIOClient?
|
||||
|
||||
for (label, dictSocket) in sockets where dictSocket === socket {
|
||||
returnSocket = sockets.removeValue(forKey: label)
|
||||
}
|
||||
|
||||
return returnSocket
|
||||
}
|
||||
|
||||
/// Removes all the sockets in the manager.
|
||||
@objc
|
||||
open func removeSockets() {
|
||||
sockets.removeAll()
|
||||
}
|
||||
}
|
||||
@ -42,7 +42,7 @@ extension CharacterSet {
|
||||
}
|
||||
}
|
||||
|
||||
extension NSDictionary {
|
||||
extension Dictionary where Key == String, Value == Any {
|
||||
private static func keyValueToSocketIOClientOption(key: String, value: Any) -> SocketIOClientOption? {
|
||||
switch (key, value) {
|
||||
case let ("connectParams", params as [String: Any]):
|
||||
@ -63,8 +63,6 @@ extension NSDictionary {
|
||||
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):
|
||||
@ -92,7 +90,7 @@ extension NSDictionary {
|
||||
var options = [] as SocketIOClientConfiguration
|
||||
|
||||
for (rawKey, value) in self {
|
||||
if let key = rawKey as? String, let opt = NSDictionary.keyValueToSocketIOClientOption(key: key, value: value) {
|
||||
if let opt = Dictionary.keyValueToSocketIOClientOption(key: rawKey, value: value) {
|
||||
options.insert(opt)
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,10 +150,9 @@ class SocketBasicPacketTest: XCTestCase {
|
||||
|
||||
func testBinaryStringPlaceholderInMessage() {
|
||||
let engineString = "52-[\"test\",\"~~0\",{\"num\":0,\"_placeholder\":true},{\"_placeholder\":true,\"num\":1}]"
|
||||
let socket = SocketIOClient(socketURL: URL(string: "http://localhost/")!)
|
||||
socket.setTestable()
|
||||
let manager = SocketManager(socketURL: URL(string: "http://localhost/")!)
|
||||
|
||||
var packet = try! socket.parseString(engineString)
|
||||
var packet = try! manager.parseString(engineString)
|
||||
|
||||
XCTAssertEqual(packet.event, "test")
|
||||
_ = packet.addData(data)
|
||||
|
||||
@ -10,20 +10,9 @@ import XCTest
|
||||
@testable import SocketIO
|
||||
|
||||
class SocketEngineTest: XCTestCase {
|
||||
var client: SocketIOClient!
|
||||
var engine: SocketEngine!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
client = SocketIOClient(socketURL: URL(string: "http://localhost")!)
|
||||
engine = SocketEngine(client: client, url: URL(string: "http://localhost")!, options: nil)
|
||||
|
||||
client.setTestable()
|
||||
}
|
||||
|
||||
func testBasicPollingMessage() {
|
||||
let expect = expectation(description: "Basic polling test")
|
||||
client.on("blankTest") {data, ack in
|
||||
socket.on("blankTest") {data, ack in
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
@ -35,11 +24,11 @@ class SocketEngineTest: XCTestCase {
|
||||
let finalExpectation = expectation(description: "Final packet in poll test")
|
||||
var gotBlank = false
|
||||
|
||||
client.on("blankTest") {data, ack in
|
||||
socket.on("blankTest") {data, ack in
|
||||
gotBlank = true
|
||||
}
|
||||
|
||||
client.on("stringTest") {data, ack in
|
||||
socket.on("stringTest") {data, ack in
|
||||
if let str = data[0] as? String, gotBlank {
|
||||
if str == "hello" {
|
||||
finalExpectation.fulfill()
|
||||
@ -54,7 +43,7 @@ class SocketEngineTest: XCTestCase {
|
||||
func testEngineDoesErrorOnUnknownTransport() {
|
||||
let finalExpectation = expectation(description: "Unknown Transport")
|
||||
|
||||
client.on("error") {data, ack in
|
||||
socket.on("error") {data, ack in
|
||||
if let error = data[0] as? String, error == "Unknown transport" {
|
||||
finalExpectation.fulfill()
|
||||
}
|
||||
@ -67,7 +56,7 @@ class SocketEngineTest: XCTestCase {
|
||||
func testEngineDoesErrorOnUnknownMessage() {
|
||||
let finalExpectation = expectation(description: "Engine Errors")
|
||||
|
||||
client.on("error") {data, ack in
|
||||
socket.on("error") {data, ack in
|
||||
finalExpectation.fulfill()
|
||||
}
|
||||
|
||||
@ -78,7 +67,7 @@ class SocketEngineTest: XCTestCase {
|
||||
func testEngineDecodesUTF8Properly() {
|
||||
let expect = expectation(description: "Engine Decodes utf8")
|
||||
|
||||
client.on("stringTest") {data, ack in
|
||||
socket.on("stringTest") {data, ack in
|
||||
XCTAssertEqual(data[0] as? String, "lïne one\nlīne \rtwo𦅙𦅛", "Failed string test")
|
||||
expect.fulfill()
|
||||
}
|
||||
@ -110,7 +99,7 @@ class SocketEngineTest: XCTestCase {
|
||||
let b64String = "b4aGVsbG8NCg=="
|
||||
let packetString = "451-[\"test\",{\"test\":{\"_placeholder\":true,\"num\":0}}]"
|
||||
|
||||
client.on("test") {data, ack in
|
||||
socket.on("test") {data, ack in
|
||||
if let data = data[0] as? Data, let string = String(data: data, encoding: .utf8) {
|
||||
XCTAssertEqual(string, "hello")
|
||||
}
|
||||
@ -123,4 +112,97 @@ class SocketEngineTest: XCTestCase {
|
||||
|
||||
waitForExpectations(timeout: 3, handler: nil)
|
||||
}
|
||||
|
||||
func testSettingExtraHeadersBeforeConnectSetsEngineExtraHeaders() {
|
||||
let newValue = ["hello": "world"]
|
||||
|
||||
manager.engine = engine
|
||||
manager.setTestStatus(.notConnected)
|
||||
manager.config = [.extraHeaders(["new": "value"])]
|
||||
manager.config.insert(.extraHeaders(newValue), replacing: true)
|
||||
|
||||
XCTAssertEqual(2, manager.config.count)
|
||||
XCTAssertEqual(manager.engine!.extraHeaders!, newValue)
|
||||
|
||||
for config in manager.config {
|
||||
switch config {
|
||||
case let .extraHeaders(headers):
|
||||
XCTAssertTrue(headers.keys.contains("hello"), "It should contain hello header key")
|
||||
XCTAssertFalse(headers.keys.contains("new"), "It should not contain old data")
|
||||
case .path:
|
||||
continue
|
||||
default:
|
||||
XCTFail("It should only have two configs")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testSettingExtraHeadersAfterConnectDoesNotIgnoreChanges() {
|
||||
let newValue = ["hello": "world"]
|
||||
|
||||
manager.engine = engine
|
||||
manager.setTestStatus(.connected)
|
||||
engine.setConnected(true)
|
||||
manager.config = [.extraHeaders(["new": "value"])]
|
||||
manager.config.insert(.extraHeaders(["hello": "world"]), replacing: true)
|
||||
|
||||
XCTAssertEqual(2, manager.config.count)
|
||||
XCTAssertEqual(manager.engine!.extraHeaders!, newValue)
|
||||
}
|
||||
|
||||
func testSettingPathAfterConnectDoesNotIgnoreChanges() {
|
||||
let newValue = "/newpath/"
|
||||
|
||||
manager.engine = engine
|
||||
manager.setTestStatus(.connected)
|
||||
engine.setConnected(true)
|
||||
manager.config.insert(.path(newValue))
|
||||
|
||||
XCTAssertEqual(1, manager.config.count)
|
||||
XCTAssertEqual(manager.engine!.socketPath, newValue)
|
||||
}
|
||||
|
||||
func testSettingCompressAfterConnectDoesNotIgnoreChanges() {
|
||||
manager.engine = engine
|
||||
manager.setTestStatus(.connected)
|
||||
engine.setConnected(true)
|
||||
manager.config.insert(.compress)
|
||||
|
||||
XCTAssertEqual(2, manager.config.count)
|
||||
XCTAssertTrue(manager.engine!.compress)
|
||||
}
|
||||
|
||||
func testSettingForcePollingAfterConnectDoesNotIgnoreChanges() {
|
||||
manager.engine = engine
|
||||
manager.setTestStatus(.connected)
|
||||
engine.setConnected(true)
|
||||
manager.config.insert(.forcePolling(true))
|
||||
|
||||
XCTAssertEqual(2, manager.config.count)
|
||||
XCTAssertTrue(manager.engine!.forcePolling)
|
||||
}
|
||||
|
||||
func testSettingForceWebSocketsAfterConnectDoesNotIgnoreChanges() {
|
||||
manager.engine = engine
|
||||
manager.setTestStatus(.connected)
|
||||
engine.setConnected(true)
|
||||
manager.config.insert(.forceWebsockets(true))
|
||||
|
||||
XCTAssertEqual(2, manager.config.count)
|
||||
XCTAssertTrue(manager.engine!.forceWebsockets)
|
||||
}
|
||||
|
||||
var manager: SocketManager!
|
||||
var socket: SocketIOClient!
|
||||
var engine: SocketEngine!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
manager = SocketManager(socketURL: URL(string: "http://localhost")!)
|
||||
socket = manager.defaultSocket
|
||||
engine = SocketEngine(client: manager, url: URL(string: "http://localhost")!, options: nil)
|
||||
|
||||
socket.setTestable()
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,15 +9,7 @@
|
||||
import XCTest
|
||||
import SocketIO
|
||||
|
||||
class TestSocketIOClientConfiguration: XCTestCase {
|
||||
var config = [] as SocketIOClientConfiguration
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
config = [.log(false), .forceNew(true)]
|
||||
}
|
||||
|
||||
class TestSocketIOClientConfiguration : XCTestCase {
|
||||
func testReplaceSameOption() {
|
||||
config.insert(.log(true))
|
||||
|
||||
@ -43,4 +35,12 @@ class TestSocketIOClientConfiguration: XCTestCase {
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
|
||||
var config = [] as SocketIOClientConfiguration
|
||||
|
||||
override func setUp() {
|
||||
config = [.log(false), .forceNew(true)]
|
||||
|
||||
super.setUp()
|
||||
}
|
||||
}
|
||||
|
||||
174
Tests/TestSocketIO/SocketMangerTest.swift
Normal file
174
Tests/TestSocketIO/SocketMangerTest.swift
Normal file
@ -0,0 +1,174 @@
|
||||
//
|
||||
// Created by Erik Little on 10/21/17.
|
||||
//
|
||||
|
||||
import Dispatch
|
||||
import Foundation
|
||||
@testable import SocketIO
|
||||
import XCTest
|
||||
|
||||
class SocketMangerTest : XCTestCase {
|
||||
func testManagerProperties() {
|
||||
XCTAssertNotNil(manager.defaultSocket)
|
||||
XCTAssertNil(manager.engine)
|
||||
XCTAssertFalse(manager.forceNew)
|
||||
XCTAssertEqual(manager.handleQueue, DispatchQueue.main)
|
||||
XCTAssertTrue(manager.reconnects)
|
||||
XCTAssertEqual(manager.reconnectWait, 10)
|
||||
XCTAssertEqual(manager.status, .notConnected)
|
||||
}
|
||||
|
||||
func testManagerCallsConnect() {
|
||||
setUpSockets()
|
||||
|
||||
socket.expectations[ManagerExpectation.didConnectCalled] = expectation(description: "The manager should call connect on the default socket")
|
||||
socket2.expectations[ManagerExpectation.didConnectCalled] = expectation(description: "The manager should call connect on the socket")
|
||||
|
||||
socket.connect()
|
||||
socket2.connect()
|
||||
|
||||
manager.fakeConnecting()
|
||||
manager.fakeConnecting(toNamespace: "/swift")
|
||||
|
||||
waitForExpectations(timeout: 0.3)
|
||||
}
|
||||
|
||||
func testManagerCallsDisconnect() {
|
||||
setUpSockets()
|
||||
|
||||
socket.expectations[ManagerExpectation.didDisconnectCalled] = expectation(description: "The manager should call disconnect on the default socket")
|
||||
socket2.expectations[ManagerExpectation.didDisconnectCalled] = expectation(description: "The manager should call disconnect on the socket")
|
||||
|
||||
socket2.on(clientEvent: .connect) {data, ack in
|
||||
self.manager.disconnect()
|
||||
self.manager.fakeDisconnecting()
|
||||
}
|
||||
|
||||
socket.connect()
|
||||
socket2.connect()
|
||||
|
||||
manager.fakeConnecting()
|
||||
manager.fakeConnecting(toNamespace: "/swift")
|
||||
|
||||
waitForExpectations(timeout: 0.3)
|
||||
}
|
||||
|
||||
func testManagerEmitAll() {
|
||||
setUpSockets()
|
||||
|
||||
socket.expectations[ManagerExpectation.emitAllEventCalled] = expectation(description: "The manager should emit an event to the default socket")
|
||||
socket2.expectations[ManagerExpectation.emitAllEventCalled] = expectation(description: "The manager should emit an event to the socket")
|
||||
|
||||
socket2.on(clientEvent: .connect) {data, ack in
|
||||
self.manager.emitAll("event", "testing")
|
||||
}
|
||||
|
||||
socket.connect()
|
||||
socket2.connect()
|
||||
|
||||
manager.fakeConnecting()
|
||||
manager.fakeConnecting(toNamespace: "/swift")
|
||||
|
||||
waitForExpectations(timeout: 0.3)
|
||||
}
|
||||
|
||||
private func setUpSockets() {
|
||||
socket = manager.testSocket(forNamespace: "/")
|
||||
socket2 = manager.testSocket(forNamespace: "/swift")
|
||||
}
|
||||
|
||||
private var manager: TestManager!
|
||||
private var socket: TestSocket!
|
||||
private var socket2: TestSocket!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
manager = TestManager(socketURL: URL(string: "http://localhost/")!, config: [.log(false)])
|
||||
socket = nil
|
||||
socket2 = nil
|
||||
}
|
||||
}
|
||||
|
||||
public enum ManagerExpectation : String {
|
||||
case didConnectCalled
|
||||
case didDisconnectCalled
|
||||
case emitAllEventCalled
|
||||
}
|
||||
|
||||
public class TestManager : SocketManager {
|
||||
public override func disconnect() {
|
||||
setTestStatus(.disconnected)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func testSocket(forNamespace nsp: String) -> TestSocket {
|
||||
return socket(forNamespace: nsp) as! TestSocket
|
||||
}
|
||||
|
||||
@objc
|
||||
public func fakeConnecting(toNamespace nsp: String) {
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
|
||||
// Fake connecting
|
||||
self.parseEngineMessage("0\(nsp)")
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func fakeDisconnecting() {
|
||||
engineDidClose(reason: "")
|
||||
}
|
||||
|
||||
@objc
|
||||
public func fakeConnecting() {
|
||||
engineDidOpen(reason: "")
|
||||
}
|
||||
|
||||
public override func socket(forNamespace nsp: String) -> SocketIOClient {
|
||||
// set socket to our test socket, the superclass method will get this from nsps
|
||||
nsps[nsp] = TestSocket(manager: self, nsp: nsp)
|
||||
|
||||
return super.socket(forNamespace: nsp)
|
||||
}
|
||||
}
|
||||
|
||||
public class TestSocket : SocketIOClient {
|
||||
public var expectations = [ManagerExpectation: XCTestExpectation]()
|
||||
|
||||
@objc
|
||||
public var expects = NSMutableDictionary()
|
||||
|
||||
public override func didConnect(toNamespace nsp: String) {
|
||||
expectations[ManagerExpectation.didConnectCalled]?.fulfill()
|
||||
expectations[ManagerExpectation.didConnectCalled] = nil
|
||||
|
||||
if let expect = expects[ManagerExpectation.didConnectCalled.rawValue] as? XCTestExpectation {
|
||||
expect.fulfill()
|
||||
expects[ManagerExpectation.didConnectCalled.rawValue] = nil
|
||||
}
|
||||
|
||||
super.didConnect(toNamespace: nsp)
|
||||
}
|
||||
|
||||
public override func didDisconnect(reason: String) {
|
||||
expectations[ManagerExpectation.didDisconnectCalled]?.fulfill()
|
||||
expectations[ManagerExpectation.didDisconnectCalled] = nil
|
||||
|
||||
if let expect = expects[ManagerExpectation.didDisconnectCalled.rawValue] as? XCTestExpectation {
|
||||
expect.fulfill()
|
||||
expects[ManagerExpectation.didDisconnectCalled.rawValue] = nil
|
||||
}
|
||||
|
||||
super.didDisconnect(reason: reason)
|
||||
}
|
||||
|
||||
public override func emit(_ event: String, with items: [Any]) {
|
||||
expectations[ManagerExpectation.emitAllEventCalled]?.fulfill()
|
||||
expectations[ManagerExpectation.emitAllEventCalled] = nil
|
||||
|
||||
if let expect = expects[ManagerExpectation.emitAllEventCalled.rawValue] as? XCTestExpectation {
|
||||
expect.fulfill()
|
||||
expects[ManagerExpectation.emitAllEventCalled.rawValue] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,26 +10,6 @@ import XCTest
|
||||
@testable import SocketIO
|
||||
|
||||
class SocketParserTest: XCTestCase {
|
||||
let testSocket = SocketIOClient(socketURL: URL(string: "http://localhost/")!)
|
||||
|
||||
//Format key: message; namespace-data-binary-id
|
||||
static let packetTypes: [String: (String, [Any], [Data], Int)] = [
|
||||
"0": ("/", [], [], -1), "1": ("/", [], [], -1),
|
||||
"25[\"test\"]": ("/", ["test"], [], 5),
|
||||
"2[\"test\",\"~~0\"]": ("/", ["test", "~~0"], [], -1),
|
||||
"2/swift,[\"testArrayEmitReturn\",[\"test3\",\"test4\"]]": ("/swift", ["testArrayEmitReturn", ["test3", "test4"] as NSArray], [], -1),
|
||||
"51-/swift,[\"testMultipleItemsWithBufferEmitReturn\",[1,2],{\"test\":\"bob\"},25,\"polo\",{\"_placeholder\":true,\"num\":0}]": ("/swift", ["testMultipleItemsWithBufferEmitReturn", [1, 2] as NSArray, ["test": "bob"] as NSDictionary, 25, "polo", ["_placeholder": true, "num": 0] as NSDictionary], [], -1),
|
||||
"3/swift,0[[\"test3\",\"test4\"]]": ("/swift", [["test3", "test4"] as NSArray], [], 0),
|
||||
"61-/swift,19[[1,2],{\"test\":\"bob\"},25,\"polo\",{\"_placeholder\":true,\"num\":0}]":
|
||||
("/swift", [ [1, 2] as NSArray, ["test": "bob"] as NSDictionary, 25, "polo", ["_placeholder": true, "num": 0] as NSDictionary], [], 19),
|
||||
"4/swift,": ("/swift", [], [], -1),
|
||||
"0/swift": ("/swift", [], [], -1),
|
||||
"1/swift": ("/swift", [], [], -1),
|
||||
"4\"ERROR\"": ("/", ["ERROR"], [], -1),
|
||||
"4{\"test\":2}": ("/", [["test": 2]], [], -1),
|
||||
"41": ("/", [1], [], -1),
|
||||
"4[1, \"hello\"]": ("/", [1, "hello"], [], -1)]
|
||||
|
||||
func testDisconnect() {
|
||||
let message = "1"
|
||||
validateParseResult(message)
|
||||
@ -108,7 +88,7 @@ class SocketParserTest: XCTestCase {
|
||||
func testInvalidInput() {
|
||||
let message = "8"
|
||||
do {
|
||||
let _ = try testSocket.parseString(message)
|
||||
let _ = try testManager.parseString(message)
|
||||
XCTFail()
|
||||
} catch {
|
||||
|
||||
@ -125,8 +105,8 @@ class SocketParserTest: XCTestCase {
|
||||
|
||||
func validateParseResult(_ message: String) {
|
||||
let validValues = SocketParserTest.packetTypes[message]!
|
||||
let packet = try! testSocket.parseString(message)
|
||||
let type = String(message.characters.prefix(1))
|
||||
let packet = try! testManager.parseString(message)
|
||||
let type = String(message.prefix(1))
|
||||
|
||||
XCTAssertEqual(packet.type, SocketPacket.PacketType(rawValue: Int(type) ?? -1)!)
|
||||
XCTAssertEqual(packet.nsp, validValues.0)
|
||||
@ -139,8 +119,29 @@ class SocketParserTest: XCTestCase {
|
||||
let keys = Array(SocketParserTest.packetTypes.keys)
|
||||
measure {
|
||||
for item in keys.enumerated() {
|
||||
_ = try! self.testSocket.parseString(item.element)
|
||||
_ = try! self.testManager.parseString(item.element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let testManager = SocketManager(socketURL: URL(string: "http://localhost/")!)
|
||||
|
||||
//Format key: message; namespace-data-binary-id
|
||||
static let packetTypes: [String: (String, [Any], [Data], Int)] = [
|
||||
"0": ("/", [], [], -1), "1": ("/", [], [], -1),
|
||||
"25[\"test\"]": ("/", ["test"], [], 5),
|
||||
"2[\"test\",\"~~0\"]": ("/", ["test", "~~0"], [], -1),
|
||||
"2/swift,[\"testArrayEmitReturn\",[\"test3\",\"test4\"]]": ("/swift", ["testArrayEmitReturn", ["test3", "test4"] as NSArray], [], -1),
|
||||
"51-/swift,[\"testMultipleItemsWithBufferEmitReturn\",[1,2],{\"test\":\"bob\"},25,\"polo\",{\"_placeholder\":true,\"num\":0}]": ("/swift", ["testMultipleItemsWithBufferEmitReturn", [1, 2] as NSArray, ["test": "bob"] as NSDictionary, 25, "polo", ["_placeholder": true, "num": 0] as NSDictionary], [], -1),
|
||||
"3/swift,0[[\"test3\",\"test4\"]]": ("/swift", [["test3", "test4"] as NSArray], [], 0),
|
||||
"61-/swift,19[[1,2],{\"test\":\"bob\"},25,\"polo\",{\"_placeholder\":true,\"num\":0}]":
|
||||
("/swift", [ [1, 2] as NSArray, ["test": "bob"] as NSDictionary, 25, "polo", ["_placeholder": true, "num": 0] as NSDictionary], [], 19),
|
||||
"4/swift,": ("/swift", [], [], -1),
|
||||
"0/swift": ("/swift", [], [], -1),
|
||||
"1/swift": ("/swift", [], [], -1),
|
||||
"4\"ERROR\"": ("/", ["ERROR"], [], -1),
|
||||
"4{\"test\":2}": ("/", [["test": 2]], [], -1),
|
||||
"41": ("/", [1], [], -1),
|
||||
"4[1, \"hello\"]": ("/", [1, "hello"], [], -1)
|
||||
]
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ class SocketSideEffectTest: XCTestCase {
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
socket.parseSocketMessage("30[\"hello world\"]")
|
||||
manager.parseEngineMessage("30[\"hello world\"]")
|
||||
waitForExpectations(timeout: 3, handler: nil)
|
||||
}
|
||||
|
||||
@ -45,8 +45,8 @@ class SocketSideEffectTest: XCTestCase {
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
socket.parseSocketMessage("61-0[{\"_placeholder\":true,\"num\":0},{\"test\":true}]")
|
||||
socket.parseBinaryData(Data())
|
||||
manager.parseEngineMessage("61-0[{\"_placeholder\":true,\"num\":0},{\"test\":true}]")
|
||||
manager.parseEngineBinaryData(Data())
|
||||
waitForExpectations(timeout: 3, handler: nil)
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ class SocketSideEffectTest: XCTestCase {
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
socket.parseSocketMessage("2[\"test\",\"hello world\"]")
|
||||
manager.parseEngineMessage("2[\"test\",\"hello world\"]")
|
||||
waitForExpectations(timeout: 3, handler: nil)
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ class SocketSideEffectTest: XCTestCase {
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
socket.parseSocketMessage("2[\"test\",\"\\\"hello world\\\"\"]")
|
||||
manager.parseEngineMessage("2[\"test\",\"\\\"hello world\\\"\"]")
|
||||
waitForExpectations(timeout: 3, handler: nil)
|
||||
}
|
||||
|
||||
@ -80,7 +80,7 @@ class SocketSideEffectTest: XCTestCase {
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
socket.parseSocketMessage("2[\"test\",\"hello world\"]")
|
||||
manager.parseEngineMessage("2[\"test\",\"hello world\"]")
|
||||
waitForExpectations(timeout: 3, handler: nil)
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@ class SocketSideEffectTest: XCTestCase {
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
|
||||
// Fake connecting
|
||||
self.socket.parseEngineMessage("0/")
|
||||
self.manager.parseEngineMessage("0/")
|
||||
}
|
||||
|
||||
waitForExpectations(timeout: 3, handler: nil)
|
||||
@ -136,59 +136,47 @@ class SocketSideEffectTest: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
socket.parseSocketMessage("4\"test error\"")
|
||||
manager.parseEngineMessage("4\"test error\"")
|
||||
waitForExpectations(timeout: 3, handler: nil)
|
||||
}
|
||||
|
||||
func testHandleBinaryEvent() {
|
||||
let expect = expectation(description: "handled binary event")
|
||||
socket.on("test") {data, ack in
|
||||
if let dict = data[0] as? NSDictionary, let data = dict["test"] as? NSData {
|
||||
if let dict = data[0] as? [String: Any], let data = dict["test"] as? Data {
|
||||
XCTAssertEqual(data as Data, self.data)
|
||||
expect.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
socket.parseSocketMessage("51-[\"test\",{\"test\":{\"_placeholder\":true,\"num\":0}}]")
|
||||
socket.parseBinaryData(data)
|
||||
manager.parseEngineMessage("51-[\"test\",{\"test\":{\"_placeholder\":true,\"num\":0}}]")
|
||||
manager.parseEngineBinaryData(data)
|
||||
waitForExpectations(timeout: 3, handler: nil)
|
||||
}
|
||||
|
||||
func testHandleMultipleBinaryEvent() {
|
||||
let expect = expectation(description: "handled multiple binary event")
|
||||
socket.on("test") {data, ack in
|
||||
if let dict = data[0] as? NSDictionary, let data = dict["test"] as? NSData,
|
||||
let data2 = dict["test2"] as? NSData {
|
||||
XCTAssertEqual(data as Data, self.data)
|
||||
XCTAssertEqual(data2 as Data, self.data2)
|
||||
expect.fulfill()
|
||||
if let dict = data[0] as? [String: Any], let data = dict["test"] as? Data,
|
||||
let data2 = dict["test2"] as? Data {
|
||||
XCTAssertEqual(data as Data, self.data)
|
||||
XCTAssertEqual(data2 as Data, self.data2)
|
||||
expect.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
socket.parseSocketMessage("52-[\"test\",{\"test\":{\"_placeholder\":true,\"num\":0},\"test2\":{\"_placeholder\":true,\"num\":1}}]")
|
||||
socket.parseBinaryData(data)
|
||||
socket.parseBinaryData(data2)
|
||||
manager.parseEngineMessage("52-[\"test\",{\"test\":{\"_placeholder\":true,\"num\":0},\"test2\":{\"_placeholder\":true,\"num\":1}}]")
|
||||
manager.parseEngineBinaryData(data)
|
||||
manager.parseEngineBinaryData(data2)
|
||||
waitForExpectations(timeout: 3, handler: nil)
|
||||
}
|
||||
|
||||
func testSocketManager() {
|
||||
let manager = SocketClientManager.sharedManager
|
||||
manager["test"] = socket
|
||||
|
||||
XCTAssert(manager["test"] === socket, "failed to get socket")
|
||||
|
||||
manager["test"] = nil
|
||||
|
||||
XCTAssert(manager["test"] == nil, "socket not removed")
|
||||
|
||||
}
|
||||
|
||||
func testChangingStatusCallsStatusChangeHandler() {
|
||||
let expect = expectation(description: "The client should announce when the status changes")
|
||||
let statusChange = SocketIOClientStatus.connecting
|
||||
let statusChange = SocketIOStatus.connecting
|
||||
|
||||
socket.on("statusChange") {data, ack in
|
||||
guard let status = data[0] as? SocketIOClientStatus else {
|
||||
guard let status = data[0] as? SocketIOStatus else {
|
||||
XCTFail("Status should be one of the defined statuses")
|
||||
|
||||
return
|
||||
@ -251,9 +239,9 @@ class SocketSideEffectTest: XCTestCase {
|
||||
func testConnectTimesOutIfNotConnected() {
|
||||
let expect = expectation(description: "The client should call the timeout function")
|
||||
|
||||
socket = manager.socket(forNamespace: "/someNamespace")
|
||||
socket.setTestStatus(.notConnected)
|
||||
socket.nsp = "/someNamespace"
|
||||
socket.engine = TestEngine(client: socket, url: socket.socketURL, options: nil)
|
||||
manager.engine = TestEngine(client: manager, url: manager.socketURL, options: nil)
|
||||
|
||||
socket.connect(timeoutAfter: 0.5, withHandler: {
|
||||
expect.fulfill()
|
||||
@ -266,7 +254,7 @@ class SocketSideEffectTest: XCTestCase {
|
||||
let expect = expectation(description: "The client should not call the timeout function")
|
||||
|
||||
socket.setTestStatus(.notConnected)
|
||||
socket.engine = TestEngine(client: socket, url: socket.socketURL, options: nil)
|
||||
manager.engine = TestEngine(client: manager, url: manager.socketURL, options: nil)
|
||||
|
||||
socket.on(clientEvent: .connect) {data, ack in
|
||||
expect.fulfill()
|
||||
@ -278,7 +266,7 @@ class SocketSideEffectTest: XCTestCase {
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
|
||||
// Fake connecting
|
||||
self.socket.parseEngineMessage("0/")
|
||||
self.manager.parseEngineMessage("0/")
|
||||
}
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
@ -288,7 +276,7 @@ class SocketSideEffectTest: XCTestCase {
|
||||
let expect = expectation(description: "The client call the connect handler")
|
||||
|
||||
socket.setTestStatus(.notConnected)
|
||||
socket.engine = TestEngine(client: socket, url: socket.socketURL, options: nil)
|
||||
manager.engine = TestEngine(client: manager, url: manager.socketURL, options: nil)
|
||||
|
||||
socket.on(clientEvent: .connect) {data, ack in
|
||||
expect.fulfill()
|
||||
@ -305,9 +293,9 @@ class SocketSideEffectTest: XCTestCase {
|
||||
let expect = expectation(description: "The client should not call the timeout function")
|
||||
let nspString = "/swift"
|
||||
|
||||
socket = manager.socket(forNamespace: "/swift")
|
||||
socket.setTestStatus(.notConnected)
|
||||
socket.nsp = nspString
|
||||
socket.engine = TestEngine(client: socket, url: socket.socketURL, options: nil)
|
||||
manager.engine = TestEngine(client: manager, url: manager.socketURL, options: nil)
|
||||
|
||||
socket.on(clientEvent: .connect) {data, ack in
|
||||
guard let nsp = data[0] as? String else {
|
||||
@ -327,7 +315,7 @@ class SocketSideEffectTest: XCTestCase {
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
|
||||
// Fake connecting
|
||||
self.socket.parseEngineMessage("0/swift")
|
||||
self.manager.parseEngineMessage("0/swift")
|
||||
}
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
@ -377,48 +365,68 @@ class SocketSideEffectTest: XCTestCase {
|
||||
|
||||
func testSettingConfigAfterInit() {
|
||||
socket.setTestStatus(.notConnected)
|
||||
socket.config.insert(.log(true))
|
||||
manager.config.insert(.log(true))
|
||||
|
||||
XCTAssertTrue(DefaultSocketLogger.Logger.log, "It should set logging to true after creation")
|
||||
|
||||
socket.config = [.log(false), .nsp("/test")]
|
||||
manager.config = [.log(false)]
|
||||
|
||||
XCTAssertFalse(DefaultSocketLogger.Logger.log, "It should set logging to false after creation")
|
||||
XCTAssertEqual(socket.nsp, "/test", "It should set the namespace after creation")
|
||||
}
|
||||
|
||||
func testSettingExtraHeadersAfterInit() {
|
||||
socket.setTestStatus(.notConnected)
|
||||
socket.config = [.extraHeaders(["new": "value"])]
|
||||
socket.config.insert(.extraHeaders(["hello": "world"]), replacing: true)
|
||||
func testSettingConfigAfterDisconnect() {
|
||||
socket.setTestStatus(.disconnected)
|
||||
manager.config.insert(.log(true))
|
||||
|
||||
for config in socket.config {
|
||||
switch config {
|
||||
case let .extraHeaders(headers):
|
||||
XCTAssertTrue(headers.keys.contains("hello"), "It should contain hello header key")
|
||||
XCTAssertFalse(headers.keys.contains("new"), "It should not contain old data")
|
||||
case .path:
|
||||
continue
|
||||
default:
|
||||
XCTFail("It should only have two configs")
|
||||
}
|
||||
XCTAssertTrue(DefaultSocketLogger.Logger.log, "It should set logging to true after creation")
|
||||
|
||||
manager.config = [.log(false)]
|
||||
|
||||
XCTAssertFalse(DefaultSocketLogger.Logger.log, "It should set logging to false after creation")
|
||||
}
|
||||
|
||||
func testSettingConfigAfterInitWhenConnectedDoesNotIgnoreChanges() {
|
||||
manager.setTestStatus(.connected)
|
||||
manager.config = [.log(true)]
|
||||
|
||||
XCTAssertTrue(DefaultSocketLogger.Logger.log, "It should set logging to false after creation")
|
||||
}
|
||||
|
||||
func testClientCallsSentPingHandler() {
|
||||
let expect = expectation(description: "The client should emit a ping event")
|
||||
|
||||
socket.on(clientEvent: .ping) {data, ack in
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
manager.engineDidSendPing()
|
||||
|
||||
waitForExpectations(timeout: 0.2)
|
||||
}
|
||||
|
||||
func testSettingConfigAfterInitWhenConnectedIgnoresChanges() {
|
||||
socket.config = [.log(true), .nsp("/test")]
|
||||
func testClientCallsGotPongHandler() {
|
||||
let expect = expectation(description: "The client should emit a pong event")
|
||||
|
||||
XCTAssertFalse(DefaultSocketLogger.Logger.log, "It should set logging to false after creation")
|
||||
XCTAssertEqual(socket.nsp, "/", "It should set the namespace after creation")
|
||||
socket.on(clientEvent: .pong) {data, ack in
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
manager.engineDidReceivePong()
|
||||
|
||||
waitForExpectations(timeout: 0.2)
|
||||
}
|
||||
|
||||
let data = "test".data(using: String.Encoding.utf8)!
|
||||
let data2 = "test2".data(using: String.Encoding.utf8)!
|
||||
|
||||
private var manager: SocketManager!
|
||||
private var socket: SocketIOClient!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
socket = SocketIOClient(socketURL: URL(string: "http://localhost/")!)
|
||||
|
||||
manager = SocketManager(socketURL: URL(string: "http://localhost/")!, config: [.log(false)])
|
||||
socket = manager.defaultSocket
|
||||
socket.setTestable()
|
||||
}
|
||||
}
|
||||
@ -437,6 +445,7 @@ struct ThrowingData : SocketData {
|
||||
class TestEngine : SocketEngineSpec {
|
||||
weak var client: SocketEngineClient?
|
||||
private(set) var closed = false
|
||||
private(set) var compress = false
|
||||
private(set) var connected = false
|
||||
var connectParams: [String: Any]? = nil
|
||||
private(set) var cookies: [HTTPCookie]? = nil
|
||||
@ -454,7 +463,7 @@ class TestEngine : SocketEngineSpec {
|
||||
private(set) var websocket = false
|
||||
private(set) var ws: WebSocket? = nil
|
||||
|
||||
required init(client: SocketEngineClient, url: URL, options: NSDictionary?) {
|
||||
required init(client: SocketEngineClient, url: URL, options: [String: Any]?) {
|
||||
self.client = client
|
||||
}
|
||||
|
||||
|
||||
16
Tests/TestSocketIOObjc/ManagerObjectiveCTest.h
Normal file
16
Tests/TestSocketIOObjc/ManagerObjectiveCTest.h
Normal file
@ -0,0 +1,16 @@
|
||||
//
|
||||
// Created by Erik Little on 10/21/17.
|
||||
//
|
||||
|
||||
#import "SocketIO_Tests-Swift.h"
|
||||
|
||||
@import XCTest;
|
||||
@import SocketIO;
|
||||
|
||||
@interface ManagerObjectiveCTest : XCTestCase
|
||||
|
||||
@property TestSocket* socket;
|
||||
@property TestSocket* socket2;
|
||||
@property TestManager* manager;
|
||||
|
||||
@end
|
||||
122
Tests/TestSocketIOObjc/ManagerObjectiveCTest.m
Normal file
122
Tests/TestSocketIOObjc/ManagerObjectiveCTest.m
Normal file
@ -0,0 +1,122 @@
|
||||
//
|
||||
// Created by Erik Little on 10/21/17.
|
||||
//
|
||||
|
||||
#import "ManagerObjectiveCTest.h"
|
||||
|
||||
@import Dispatch;
|
||||
@import Foundation;
|
||||
@import XCTest;
|
||||
@import SocketIO;
|
||||
|
||||
@implementation ManagerObjectiveCTest
|
||||
|
||||
- (void)testSettingConfig {
|
||||
NSURL* url = [[NSURL alloc] initWithString:@"http://localhost"];
|
||||
self.manager = [[TestManager alloc] initWithSocketURL:url config:@{@"forceNew": @YES}];
|
||||
|
||||
XCTAssertTrue(self.manager.forceNew);
|
||||
}
|
||||
|
||||
- (void)testManagerProperties {
|
||||
XCTAssertNotNil(self.manager.defaultSocket);
|
||||
XCTAssertNil(self.manager.engine);
|
||||
XCTAssertFalse(self.manager.forceNew);
|
||||
XCTAssertEqual(self.manager.handleQueue, dispatch_get_main_queue());
|
||||
XCTAssertTrue(self.manager.reconnects);
|
||||
XCTAssertEqual(self.manager.reconnectWait, 10);
|
||||
XCTAssertEqual(self.manager.status, SocketIOStatusNotConnected);
|
||||
}
|
||||
|
||||
- (void)testConnectSocketSyntax {
|
||||
[self setUpSockets];
|
||||
[self.manager connectSocket:self.socket];
|
||||
}
|
||||
|
||||
- (void)testDisconnectSocketSyntax {
|
||||
[self setUpSockets];
|
||||
[self.manager disconnectSocket:self.socket];
|
||||
}
|
||||
|
||||
- (void)testSocketForNamespaceSyntax {
|
||||
SocketIOClient* client = [self.manager socketForNamespace:@"/swift"];
|
||||
client = nil;
|
||||
}
|
||||
|
||||
- (void)testManagerCallsConnect {
|
||||
[self setUpSockets];
|
||||
|
||||
XCTestExpectation* expect = [self expectationWithDescription:@"The manager should call connect on the default socket"];
|
||||
XCTestExpectation* expect2 = [self expectationWithDescription:@"The manager should call connect on the socket"];
|
||||
|
||||
self.socket.expects[@"didConnectCalled"] = expect;
|
||||
self.socket2.expects[@"didConnectCalled"] = expect2;
|
||||
|
||||
[self.socket connect];
|
||||
[self.socket2 connect];
|
||||
|
||||
[self.manager fakeConnecting];
|
||||
[self.manager fakeConnectingToNamespace:@"/swift"];
|
||||
|
||||
[self waitForExpectationsWithTimeout:0.3 handler:nil];
|
||||
}
|
||||
|
||||
- (void)testManagerCallsDisconnect {
|
||||
[self setUpSockets];
|
||||
|
||||
XCTestExpectation* expect = [self expectationWithDescription:@"The manager should call disconnect on the default socket"];
|
||||
XCTestExpectation* expect2 = [self expectationWithDescription:@"The manager should call disconnect on the socket"];
|
||||
|
||||
self.socket.expects[@"didDisconnectCalled"] = expect;
|
||||
self.socket2.expects[@"didDisconnectCalled"] = expect2;
|
||||
|
||||
[self.socket2 on:@"connect" callback:^(NSArray* data, SocketAckEmitter* ack) {
|
||||
[self.manager disconnect];
|
||||
[self.manager fakeDisconnecting];
|
||||
}];
|
||||
|
||||
[self.socket connect];
|
||||
[self.socket2 connect];
|
||||
|
||||
[self.manager fakeConnecting];
|
||||
[self.manager fakeConnectingToNamespace:@"/swift"];
|
||||
|
||||
[self waitForExpectationsWithTimeout:0.3 handler:nil];
|
||||
}
|
||||
|
||||
- (void)testManagerEmitAll {
|
||||
[self setUpSockets];
|
||||
|
||||
XCTestExpectation* expect = [self expectationWithDescription:@"The manager should emit an event to the default socket"];
|
||||
XCTestExpectation* expect2 = [self expectationWithDescription:@"The manager should emit an event to the socket"];
|
||||
|
||||
self.socket.expects[@"emitAllEventCalled"] = expect;
|
||||
self.socket2.expects[@"emitAllEventCalled"] = expect2;
|
||||
|
||||
[self.socket2 on:@"connect" callback:^(NSArray* data, SocketAckEmitter* ack) {
|
||||
[self.manager emitAll:@"event" withItems:@[@"testing"]];
|
||||
}];
|
||||
|
||||
[self.socket connect];
|
||||
[self.socket2 connect];
|
||||
|
||||
[self.manager fakeConnecting];
|
||||
[self.manager fakeConnectingToNamespace:@"/swift"];
|
||||
|
||||
[self waitForExpectationsWithTimeout:0.3 handler:nil];
|
||||
}
|
||||
|
||||
- (void)setUpSockets {
|
||||
self.socket = [self.manager testSocketForNamespace:@"/"];
|
||||
self.socket2 = [self.manager testSocketForNamespace:@"/swift"];
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
NSURL* url = [[NSURL alloc] initWithString:@"http://localhost"];
|
||||
self.manager = [[TestManager alloc] initWithSocketURL:url config:@{@"log": @NO}];
|
||||
self.socket = nil;
|
||||
self.socket2 = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
16
Tests/TestSocketIOObjc/SocketObjectiveCTest.h
Normal file
16
Tests/TestSocketIOObjc/SocketObjectiveCTest.h
Normal file
@ -0,0 +1,16 @@
|
||||
//
|
||||
// Created by Erik Little on 10/21/17.
|
||||
//
|
||||
|
||||
|
||||
@import Dispatch;
|
||||
@import Foundation;
|
||||
@import XCTest;
|
||||
@import SocketIO;
|
||||
|
||||
@interface SocketObjectiveCTest : XCTestCase
|
||||
|
||||
@property SocketIOClient* socket;
|
||||
@property SocketManager* manager;
|
||||
|
||||
@end
|
||||
@ -7,36 +7,20 @@
|
||||
// Merely tests whether the Objective-C api breaks
|
||||
//
|
||||
|
||||
#import "SocketObjectiveCTest.h"
|
||||
|
||||
@import Dispatch;
|
||||
@import Foundation;
|
||||
@import XCTest;
|
||||
@import SocketIO;
|
||||
|
||||
@interface SocketObjectiveCTest : XCTestCase
|
||||
|
||||
@property SocketIOClient* socket;
|
||||
|
||||
@end
|
||||
// TODO Manager interface tests
|
||||
|
||||
@implementation SocketObjectiveCTest
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
NSURL* url = [[NSURL alloc] initWithString:@"http://localhost"];
|
||||
self.socket = [[SocketIOClient alloc] initWithSocketURL:url config:@{@"log": @NO, @"forcePolling": @YES}];
|
||||
}
|
||||
|
||||
- (void)testProperties {
|
||||
NSURL* url = nil;
|
||||
|
||||
url = self.socket.socketURL;
|
||||
self.socket.forceNew = false;
|
||||
self.socket.handleQueue = dispatch_get_main_queue();
|
||||
self.socket.nsp = @"/objective-c";
|
||||
self.socket.reconnects = false;
|
||||
self.socket.reconnectWait = 1;
|
||||
if (self.socket.status == SocketIOClientStatusConnected) { }
|
||||
if (self.socket.engine == NULL) { }
|
||||
XCTAssertTrue([self.socket.nsp isEqualToString:@"/"]);
|
||||
XCTAssertEqual(self.socket.status, SocketIOStatusNotConnected);
|
||||
}
|
||||
|
||||
- (void)testOnSyntax {
|
||||
@ -62,7 +46,7 @@
|
||||
}
|
||||
|
||||
- (void)testJoinNamespaceSyntax {
|
||||
[self.socket joinNamespace:@"/objective-c"];
|
||||
[self.socket joinNamespace];
|
||||
}
|
||||
|
||||
- (void)testOnAnySyntax {
|
||||
@ -74,10 +58,6 @@
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)testReconnectSyntax {
|
||||
[self.socket reconnect];
|
||||
}
|
||||
|
||||
- (void)testRemoveAllHandlersSyntax {
|
||||
[self.socket removeAllHandlers];
|
||||
}
|
||||
@ -94,15 +74,16 @@
|
||||
[self.socket off:@"test"];
|
||||
}
|
||||
|
||||
- (void)testSocketManager {
|
||||
SocketClientManager* manager = [SocketClientManager sharedManager];
|
||||
[manager addSocket:self.socket labeledAs:@"test"];
|
||||
[manager removeSocketWithLabel:@"test"];
|
||||
}
|
||||
|
||||
- (void)testSSLSecurity {
|
||||
SSLSecurity* sec = [[SSLSecurity alloc] initWithUsePublicKeys:0];
|
||||
sec = nil;
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
NSURL* url = [[NSURL alloc] initWithString:@"http://localhost"];
|
||||
self.manager = [[SocketManager alloc] initWithSocketURL:url config:@{@"log": @NO}];
|
||||
self.socket = [self.manager defaultSocket];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
109
Usage Docs/12to13.md
Normal file
109
Usage Docs/12to13.md
Normal file
@ -0,0 +1,109 @@
|
||||
# Upgrading from v12
|
||||
|
||||
This guide will help you navigate the changes that were introduced in v13.
|
||||
|
||||
## What are the big changes?
|
||||
|
||||
The biggest change is how to create and manage clients. Much like the native JS client and server,
|
||||
the swift client now only uses one engine per connection. Previously in order to use namespaces it was required
|
||||
to create multiple clients, and each client had its own engine.
|
||||
|
||||
Some v12 code might've looked like this:
|
||||
|
||||
```swift
|
||||
let defaultSocket = SocketIOClient(socketURL: myURL)
|
||||
let namespaceSocket = SocketIOClient(socketURL: myURL, config: [.nsp("/swift")])
|
||||
|
||||
// add handlers for sockets and connect
|
||||
|
||||
```
|
||||
|
||||
In v12 this would have opened two connections to the socket.io.
|
||||
|
||||
|
||||
In v13 the same code would look like this:
|
||||
|
||||
```swift
|
||||
let manager = SocketManager(socketURL: myURL)
|
||||
let defaultSocket = manager.defaultSocket
|
||||
let namespaceSocket = manager.socket(forNamespace: "/swift")
|
||||
|
||||
// add handlers for sockets and connect
|
||||
```
|
||||
|
||||
In v13 `defaultSocket` and `namespaceSocket` will share a single transport. This means one less connection to the server
|
||||
needs to be opened.
|
||||
|
||||
## What might I have to change?
|
||||
|
||||
- The most obvious thing you will need to change is that instead of creating `SocketIOClient`s directly, you will create a
|
||||
`SocketManager` and either use the `defaultSocket` property if you don't need namespaces, or call the
|
||||
`socket(forNamespace:)` method on the manager.
|
||||
|
||||
- `SocketIOClient` is no longer a client to an engine. So if you were overriding the engine methods, these have been moved
|
||||
to the manager.
|
||||
|
||||
- The library is now a single target. So you might have to change some of your Xcode project settings.
|
||||
|
||||
- `SocketIOClient`s no longer take a configuration, they are shared from the manager.
|
||||
|
||||
- The `joinNamespace()` and `leaveNamespace()` methods on `SocketIOClient` no longer take any arguments, and in most cases
|
||||
no longer need to be called. Namespace joining/leaving can be managed by calling `connect()`/`disconnect()` on the socket
|
||||
associated with that namespace.
|
||||
|
||||
----------
|
||||
|
||||
# What things should I know?
|
||||
|
||||
How sockets are stored
|
||||
---
|
||||
|
||||
You should know that `SocketIOClient`s no longer need to be held around in properties, but the `SocketManager` should.
|
||||
|
||||
One of the most common mistakes people made is not maintaining a strong reference to the client.
|
||||
|
||||
```swift
|
||||
class Manager {
|
||||
func addHandlers() {
|
||||
let socket = SocketIOClient(socketURL: myURL, config: [.nsp("/swift")])
|
||||
|
||||
// Add handlers
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This would have resulted in the client being released and no handlers being called.
|
||||
|
||||
A *correct* equivalent would be:
|
||||
|
||||
```swift
|
||||
class Manager {
|
||||
let socketManager = SocketManager(socketURL: someURL)
|
||||
|
||||
func addHandlers() {
|
||||
let socket = socketManager.socket(forNamespace: "/swift")
|
||||
|
||||
// Add handlers
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This code is fine because the `SocketManager` will maintain a strong reference to the socket.
|
||||
|
||||
It's also worth noting that subsequent calls to `socket(forNamespace:)` will return the *same* socket instance as the
|
||||
first call. So you don't need to hold onto the socket directly just to access it again, just call `socket(forNamespace:)`
|
||||
on the manager to get it. **This does mean that if you need multiple sockets on the same namespace, you will have to use
|
||||
multiple managers.**
|
||||
|
||||
What to call connect on
|
||||
---
|
||||
|
||||
Connect can either be called on the manager directly, or on one of the sockets made from it. In either case, if the manager
|
||||
was not already connected to the server, a connection will be made. Also in both cases the default socket (namespace "/")
|
||||
will fire a `connect` event.
|
||||
|
||||
The difference is that if `connect()` is just called on the manager, then any sockets for that manager that are not the default
|
||||
socket will not automatically connect. `connect()` will need to be called individually for each socket. However, if `connect()`
|
||||
is called on a client, then in addition to opening the connection if needed, the client will connect to the its namespace,
|
||||
and a `connect` event fired.
|
||||
|
||||
@ -13,11 +13,11 @@ One of the most common reasons your event might not be called is if the client i
|
||||
Take this code for example:
|
||||
|
||||
```swift
|
||||
class SocketManager {
|
||||
class Manager {
|
||||
func addHandlers() {
|
||||
let socket = SocketIOClient(socketURL: URL(string: "http://somesocketioserver.com")!)
|
||||
let manager = SocketManager(socketURL: URL(string: "http://somesocketioserver.com")!)
|
||||
|
||||
socket.on("myEvent") {data, ack in
|
||||
manager.defaultSocket.on("myEvent") {data, ack in
|
||||
print(data)
|
||||
}
|
||||
}
|
||||
@ -25,30 +25,20 @@ class SocketManager {
|
||||
}
|
||||
```
|
||||
|
||||
This code is **incorrect**, and the event handler will never be called. Because as soon as this method is called `socket`
|
||||
will be released and its memory reclaimed.
|
||||
This code is **incorrect**, and the event handler will never be called. Because as soon as this method is called `manager`
|
||||
will be released, along with the socket, and its memory reclaimed.
|
||||
|
||||
A correct way would be:
|
||||
|
||||
```swift
|
||||
class SocketManager {
|
||||
let socket = SocketIOClient(socketURL: URL(string: "http://somesocketioserver.com")!)
|
||||
class Manager {
|
||||
let manager = SocketManager(socketURL: URL(string: "http://somesocketioserver.com")!)
|
||||
|
||||
func addHandlers() {
|
||||
socket.on("myEvent") {data, ack in
|
||||
manager.defaultSocket.on("myEvent") {data, ack in
|
||||
print(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
------
|
||||
|
||||
Another case where this might happen is if you use namespaces in your socket.io application.
|
||||
|
||||
In the JavaScript client a url that looks like `http://somesocketioserver.com/client` would be done with the `nsp` config.
|
||||
|
||||
```swift
|
||||
let socket = SocketIOClient(socketURL: URL(string: "http://somesocketioserver.com")!, config: [.nsp("/client")])
|
||||
```
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user