diff --git a/Podfile b/Podfile index 7ed01693..30db80c0 100644 --- a/Podfile +++ b/Podfile @@ -1,4 +1,4 @@ -xcodeproj 'SignalR.Client.ObjC/SignalR.Client.ObjC' +project 'SignalR.Client.ObjC/SignalR.Client.ObjC.xcodeproj' workspace 'SignalR.Client.ObjC' target "SignalR.Client.iOS" do @@ -23,4 +23,4 @@ target :"SignalR.Client.OSX" do target :"SignalR.Client.OSXTests" do pod 'OCMock' end -end \ No newline at end of file +end diff --git a/Podfile.lock b/Podfile.lock index 3c53754a..2af314c5 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -28,9 +28,17 @@ DEPENDENCIES: - OCMock - SocketRocket (= 0.4.2) +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - AFNetworking + - OCMock + - SocketRocket + SPEC CHECKSUMS: AFNetworking: cb8d14a848e831097108418f5d49217339d4eb60 OCMock: 18c9b7e67d4c2770e95bb77a9cc1ae0c91fe3835 SocketRocket: ffe08119b00ef982f6c37052a4705a057c8494ad -COCOAPODS: 0.39.0 +PODFILE CHECKSUM: b3a3a58c75f13f9a520b6c950906fdd364953a26 + +COCOAPODS: 1.5.3 diff --git a/SignalR.Client.ObjC.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SignalR.Client.ObjC.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/SignalR.Client.ObjC.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SignalR.Client.ObjC.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/SignalR.Client.ObjC.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..0c67376e --- /dev/null +++ b/SignalR.Client.ObjC.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + + diff --git a/SignalR.Client.ObjC/SignalR.Client.ObjC.xcodeproj/project.pbxproj b/SignalR.Client.ObjC/SignalR.Client.ObjC.xcodeproj/project.pbxproj index a8f1b0cc..6246c7d6 100644 --- a/SignalR.Client.ObjC/SignalR.Client.ObjC.xcodeproj/project.pbxproj +++ b/SignalR.Client.ObjC/SignalR.Client.ObjC.xcodeproj/project.pbxproj @@ -53,7 +53,6 @@ 39302A211C69768D0061C6B5 /* SRServerSentEventsTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = 3920786115AF13F6009B959E /* SRServerSentEventsTransport.m */; }; 39302A221C69768D0061C6B5 /* SRWebSocketTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = 39AF0D7D17138E3800E13E6E /* SRWebSocketTransport.m */; }; 39302A231C6976A30061C6B5 /* SRSubscription.m in Sources */ = {isa = PBXBuildFile; fileRef = 3920783615AF13F6009B959E /* SRSubscription.m */; }; - 39302A271C6977800061C6B5 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 39302A251C6977800061C6B5 /* Info.plist */; }; 39302A281C6977800061C6B5 /* SignalR.h in Headers */ = {isa = PBXBuildFile; fileRef = 39302A261C6977800061C6B5 /* SignalR.h */; }; 39302A381C6978AC0061C6B5 /* SignalR.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39302A2E1C6978AB0061C6B5 /* SignalR.framework */; }; 39302A451C6979E10061C6B5 /* SRHubConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 3920782715AF13F6009B959E /* SRHubConnection.m */; }; @@ -522,12 +521,12 @@ isa = PBXNativeTarget; buildConfigurationList = 39302A051C6974C20061C6B5 /* Build configuration list for PBXNativeTarget "SignalR.Client.OSX" */; buildPhases = ( + 759DD48E5A906D4D2714F305 /* [CP] Check Pods Manifest.lock */, BE773CA6D603623DBCDFD6AF /* Check Pods Manifest.lock */, 393029EF1C6974C10061C6B5 /* Sources */, 393029F01C6974C10061C6B5 /* Frameworks */, 393029F11C6974C10061C6B5 /* Headers */, 393029F21C6974C10061C6B5 /* Resources */, - 7C5BAF419D1DB75581A41B87 /* Copy Pods Resources */, ); buildRules = ( ); @@ -542,12 +541,13 @@ isa = PBXNativeTarget; buildConfigurationList = 39302A081C6974C20061C6B5 /* Build configuration list for PBXNativeTarget "SignalR.Client.OSXTests" */; buildPhases = ( + 29F8A99392528EEAD6A859C3 /* [CP] Check Pods Manifest.lock */, 537C4819A81E84FE2982C1CF /* Check Pods Manifest.lock */, 393029F91C6974C20061C6B5 /* Sources */, 393029FA1C6974C20061C6B5 /* Frameworks */, 393029FB1C6974C20061C6B5 /* Resources */, 67E90B84652BAD667E4611D1 /* Embed Pods Frameworks */, - 0B029DDB5BC07F36BE28C47C /* Copy Pods Resources */, + ED3F4313A751EA87E66F745B /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -563,12 +563,12 @@ isa = PBXNativeTarget; buildConfigurationList = 39302A3F1C6978AC0061C6B5 /* Build configuration list for PBXNativeTarget "SignalR.Client.iOS" */; buildPhases = ( + A48F4A4D58939ECEEB8E687E /* [CP] Check Pods Manifest.lock */, 42E081C85DE4F4916D0F3D75 /* Check Pods Manifest.lock */, 39302A291C6978AB0061C6B5 /* Sources */, 39302A2A1C6978AB0061C6B5 /* Frameworks */, 39302A2B1C6978AB0061C6B5 /* Headers */, 39302A2C1C6978AB0061C6B5 /* Resources */, - 796268F885EFEC8F1356854A /* Copy Pods Resources */, ); buildRules = ( ); @@ -583,12 +583,13 @@ isa = PBXNativeTarget; buildConfigurationList = 39302A421C6978AC0061C6B5 /* Build configuration list for PBXNativeTarget "SignalR.Client.iOSTests" */; buildPhases = ( + EF8CD3D9CD825628C4319223 /* [CP] Check Pods Manifest.lock */, 21C56E6B6FDFA0220435E7AD /* Check Pods Manifest.lock */, 39302A331C6978AC0061C6B5 /* Sources */, 39302A341C6978AC0061C6B5 /* Frameworks */, 39302A351C6978AC0061C6B5 /* Resources */, 2AC719B5534D658A6E1038BA /* Embed Pods Frameworks */, - 1A11AFB25C13B6568AF7E084 /* Copy Pods Resources */, + 239C11727851DAB652DA48A3 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -648,7 +649,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 39302A271C6977800061C6B5 /* Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -676,49 +676,67 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 0B029DDB5BC07F36BE28C47C /* Copy Pods Resources */ = { + 21C56E6B6FDFA0220435E7AD /* Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-SignalR.Client.OSX-SignalR.Client.OSXTests/Pods-SignalR.Client.OSX-SignalR.Client.OSXTests-resources.sh\"\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; - 1A11AFB25C13B6568AF7E084 /* Copy Pods Resources */ = { + 239C11727851DAB652DA48A3 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( + "${SRCROOT}/../Pods/Target Support Files/Pods-SignalR.Client.iOS-SignalR.Client.iOSTests/Pods-SignalR.Client.iOS-SignalR.Client.iOSTests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework", + "${BUILT_PRODUCTS_DIR}/SocketRocket-iOS/SocketRocket.framework", + "${BUILT_PRODUCTS_DIR}/OCMock-iOS/OCMock.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( ); - name = "Copy Pods Resources"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SocketRocket.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-SignalR.Client.iOS-SignalR.Client.iOSTests/Pods-SignalR.Client.iOS-SignalR.Client.iOSTests-resources.sh\"\n"; + shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-SignalR.Client.iOS-SignalR.Client.iOSTests/Pods-SignalR.Client.iOS-SignalR.Client.iOSTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 21C56E6B6FDFA0220435E7AD /* Check Pods Manifest.lock */ = { + 29F8A99392528EEAD6A859C3 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-SignalR.Client.OSX-SignalR.Client.OSXTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 2AC719B5534D658A6E1038BA /* Embed Pods Frameworks */ = { @@ -781,34 +799,48 @@ shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-SignalR.Client.OSX-SignalR.Client.OSXTests/Pods-SignalR.Client.OSX-SignalR.Client.OSXTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 796268F885EFEC8F1356854A /* Copy Pods Resources */ = { + 759DD48E5A906D4D2714F305 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "Copy Pods Resources"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-SignalR.Client.OSX-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-SignalR.Client.iOS/Pods-SignalR.Client.iOS-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 7C5BAF419D1DB75581A41B87 /* Copy Pods Resources */ = { + A48F4A4D58939ECEEB8E687E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "Copy Pods Resources"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-SignalR.Client.iOS-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-SignalR.Client.OSX/Pods-SignalR.Client.OSX-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; BE773CA6D603623DBCDFD6AF /* Check Pods Manifest.lock */ = { @@ -826,6 +858,54 @@ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; + ED3F4313A751EA87E66F745B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${SRCROOT}/../Pods/Target Support Files/Pods-SignalR.Client.OSX-SignalR.Client.OSXTests/Pods-SignalR.Client.OSX-SignalR.Client.OSXTests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/AFNetworking-93a4f461/AFNetworking.framework", + "${BUILT_PRODUCTS_DIR}/SocketRocket-macOS/SocketRocket.framework", + "${BUILT_PRODUCTS_DIR}/OCMock-macOS/OCMock.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + ); + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SocketRocket.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-SignalR.Client.OSX-SignalR.Client.OSXTests/Pods-SignalR.Client.OSX-SignalR.Client.OSXTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + EF8CD3D9CD825628C4319223 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-SignalR.Client.iOS-SignalR.Client.iOSTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/SignalR.Client/SRConnection.m b/SignalR.Client/SRConnection.m index fcd23734..f9cd5b94 100644 --- a/SignalR.Client/SRConnection.m +++ b/SignalR.Client/SRConnection.m @@ -45,6 +45,10 @@ @interface SRConnection () @property (strong, nonatomic, readwrite) NSString *connectionData; @property (strong, nonatomic, readwrite) SRHeartbeatMonitor *monitor; +@property (strong, nonatomic, readwrite) id transport; +@property (strong, nonatomic, readwrite) NSDate* lastActive; +@property (strong, nonatomic, readwrite) NSNumber* reconnectWindow; + - (void)negotiate:(id )transport; - (void)verifyProtocolVersion:(NSString *)versionString; - (NSString *)createUserAgentString:(NSString *)client; @@ -67,6 +71,9 @@ @implementation SRConnection @synthesize transport = _transport; @synthesize credentials = _credentials; @synthesize headers = _headers; +@synthesize lastActive = _lastActive; +@synthesize reconnectWindow = _reconnectWindow; + #pragma mark - #pragma mark Initialization @@ -146,7 +153,10 @@ - (void)negotiate:(id)transport { // If we have a keep alive if (negotiationResponse.keepAliveTimeout != nil) { - _keepAliveData = [[SRKeepAliveData alloc] initWithTimeout:negotiationResponse.keepAliveTimeout]; + strongSelf.keepAliveData = [[SRKeepAliveData alloc] initWithTimeout:negotiationResponse.keepAliveTimeout]; + strongSelf.reconnectWindow = @([strongSelf.disconnectTimeout floatValue] + [strongSelf.keepAliveData.timeout floatValue]); + } else { + strongSelf.reconnectWindow = strongSelf.disconnectTimeout; } [strongSelf startTransport]; @@ -416,6 +426,28 @@ - (void)updateLastKeepAlive { } } +-(void)markActive{ + if([self verifyLastActive]){ + _lastActive = [NSDate date]; + } +} + +- (BOOL) verifyLastActive { + if (_lastActive == nil || _reconnectWindow == nil){ + //todo figure out what really should go here + _lastActive = [NSDate date]; + return YES; + } + NSTimeInterval timeElapsed = [[NSDate date] timeIntervalSinceDate: _lastActive];//number of seconds + if (timeElapsed > [_reconnectWindow floatValue]){ + SRLogConnectionDebug(@"There has not been an active server connection for an extended period of time. Stopping connection."); + [self stop:_defaultAbortTimeout]; + return NO; + } + return YES; +} + + - (void)prepareRequest:(NSMutableURLRequest *)request { #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR [request addValue:[self createUserAgentString:NSLocalizedString(@"SignalR.Client.iOS",@"")] forHTTPHeaderField:@"User-Agent"]; diff --git a/SignalR.Client/SRConnectionInterface.h b/SignalR.Client/SRConnectionInterface.h index 92d72ee1..2785fa41 100644 --- a/SignalR.Client/SRConnectionInterface.h +++ b/SignalR.Client/SRConnectionInterface.h @@ -53,6 +53,8 @@ ///------------------------------- - (NSString *)onSending;//TODO: this just encapsulates connectionData. can we pull this into a getUrl like js client does? +- (BOOL) verifyLastActive; +- (void) markActive; ///------------------------------- /// @name Connection Management diff --git a/SignalR.Client/SRHeartbeatMonitor.h b/SignalR.Client/SRHeartbeatMonitor.h index 8a0a4d4b..b999ea0a 100644 --- a/SignalR.Client/SRHeartbeatMonitor.h +++ b/SignalR.Client/SRHeartbeatMonitor.h @@ -28,6 +28,7 @@ @property (assign, nonatomic, readonly, getter = hasBeenWarned) BOOL beenWarned; @property (assign, nonatomic, readonly) BOOL timedOut; +@property (assign, nonatomic, readonly) BOOL monitorKeepAlive; - (instancetype)initWithConnection:(id )connection; - (void)start; diff --git a/SignalR.Client/SRHeartbeatMonitor.m b/SignalR.Client/SRHeartbeatMonitor.m index 3417c29f..5388969f 100644 --- a/SignalR.Client/SRHeartbeatMonitor.m +++ b/SignalR.Client/SRHeartbeatMonitor.m @@ -44,7 +44,10 @@ - (instancetype)initWithConnection:(id )connection { } - (void)start { - [_connection updateLastKeepAlive]; + _monitorKeepAlive = [_connection keepAliveData] && [_connection.transport supportsKeepAlive]; + if (_monitorKeepAlive){ + [_connection updateLastKeepAlive]; + } _beenWarned = NO; _timedOut = NO; _timer = [NSTimer scheduledTimerWithTimeInterval:[[[_connection keepAliveData] checkInterval] integerValue] @@ -60,26 +63,29 @@ - (void)heartbeat:(NSTimer *)timer { } - (void)beat:(NSInteger)timeElapsed { - if (_connection.state == connected) { - if (timeElapsed >= [[[_connection keepAliveData] timeout] integerValue]) { - if (!self.timedOut) { - // Connection has been lost - SRLogConnectionWarn(@"Connection Timed-out : Transport Lost Connection"); - _timedOut = true; - [[_connection transport] lostConnection:_connection]; - } - } else if (timeElapsed >= [[[_connection keepAliveData] timeoutWarning] integerValue]) { - if (!self.hasBeenWarned) { - // Inform user and set HasBeenWarned to true - SRLogConnectionWarn(@"Connection Timeout Warning : Notifying user"); - _beenWarned = true; - [_connection connectionDidSlow]; + if (_monitorKeepAlive){ + if (_connection.state == connected) { + if (timeElapsed >= [[[_connection keepAliveData] timeout] integerValue]) { + if (!self.timedOut) { + // Connection has been lost + SRLogConnectionWarn(@"Connection Timed-out : Transport Lost Connection"); + _timedOut = true; + [[_connection transport] lostConnection:_connection]; + } + } else if (timeElapsed >= [[[_connection keepAliveData] timeoutWarning] integerValue]) { + if (!self.hasBeenWarned) { + // Inform user and set HasBeenWarned to true + SRLogConnectionWarn(@"Connection Timeout Warning : Notifying user"); + _beenWarned = true; + [_connection connectionDidSlow]; + } + } else { + _beenWarned = false; + _timedOut = false; } - } else { - _beenWarned = false; - _timedOut = false; } } + [_connection markActive]; } - (void)stop { diff --git a/SignalR.Client/Transports/SRLongPollingTransport.m b/SignalR.Client/Transports/SRLongPollingTransport.m index 11c7b48c..06a6590a 100644 --- a/SignalR.Client/Transports/SRLongPollingTransport.m +++ b/SignalR.Client/Transports/SRLongPollingTransport.m @@ -27,11 +27,13 @@ #import "SRLog.h" #import "SRLongPollingTransport.h" - @interface SRLongPollingTransport() - - @property (strong, nonatomic, readwrite) NSOperationQueue *pollingOperationQueue; - - @end +@interface SRLongPollingTransport() + +@property (strong, nonatomic, readwrite) NSOperationQueue *pollingOperationQueue; +@property (nonatomic, readwrite) int reconnectErrors; +@property (strong, nonatomic, readwrite) NSBlockOperation *reconnectTimeout; + +@end @implementation SRLongPollingTransport @@ -63,6 +65,7 @@ - (void)negotiate:(id)connection connectionData:(NSString - (void)start:(id)connection connectionData:(NSString *)connectionData completionHandler:(void (^)(id response, NSError *error))block { SRLogLPDebug(@"longPolling will connect with connectionData %@", connectionData); + self.reconnectErrors = 0; [self poll:connection connectionData:connectionData completionHandler:block]; } @@ -73,6 +76,9 @@ - (void)send:(id)connection data:(NSString *)data connect - (void)abort:(id)connection timeout:(NSNumber *)timeout connectionData:(NSString *)connectionData { SRLogLPDebug(@"longPolling will abort"); + [[self reconnectTimeout] cancel]; + self.reconnectTimeout = nil; + [super abort:connection timeout:timeout connectionData:connectionData]; } @@ -85,8 +91,6 @@ - (void)lostConnection:(id)connection { - (void)poll:(id)connection connectionData:(NSString *)connectionData completionHandler:(void (^)(id response, NSError *error))block { - __block NSNumber *canReconnect = @(YES); - NSString *url = connection.url; if(connection.messageId == nil) { url = [url stringByAppendingString:@"connect"]; @@ -96,7 +100,7 @@ - (void)poll:(id)connection connectionData:(NSString *)co url = [url stringByAppendingString:@"poll"]; } - [self delayConnectionReconnect:connection canReconnect:canReconnect]; + [self delayConnectionReconnect:connection]; __weak __typeof(&*self)weakSelf = self; __weak __typeof(&*connection)weakConnection = connection; @@ -128,9 +132,12 @@ - (void)poll:(id)connection connectionData:(NSString *)co [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { __strong __typeof(&*weakSelf)strongSelf = weakSelf; __strong __typeof(&*weakConnection)strongConnection = weakConnection; - + BOOL shouldReconnect = NO; BOOL disconnectedReceived = NO; + strongSelf.reconnectErrors = 0; + [[strongSelf reconnectTimeout] cancel]; + strongSelf.reconnectTimeout = nil; SRLogLPInfo(@"longPolling did receive: %@", operation.responseString); @@ -143,7 +150,7 @@ - (void)poll:(id)connection connectionData:(NSString *)co // If the timeout for the reconnect hasn't fired as yet just fire the // event here before any incoming messages are processed SRLogLPWarn(@"reconnecting"); - [strongSelf connectionReconnect:strongConnection canReconnect:canReconnect]; + [strongSelf connectionReconnect:strongConnection]; } if (shouldReconnect) { @@ -159,7 +166,6 @@ - (void)poll:(id)connection connectionData:(NSString *)co if (![strongSelf tryCompleteAbort]) { //Abort has not been called so continue polling... - canReconnect = @(YES); [strongSelf poll:strongConnection connectionData:connectionData completionHandler:nil]; } else { SRLogLPWarn(@"longPolling has shutdown due to abort"); @@ -167,10 +173,17 @@ - (void)poll:(id)connection connectionData:(NSString *)co } failure:^(AFHTTPRequestOperation *operation, NSError *error) { __strong __typeof(&*weakSelf)strongSelf = weakSelf; __strong __typeof(&*weakConnection)strongConnection = weakConnection; - + SRLogLPError(@"longPolling did fail with error %@", error); - canReconnect = @(NO); + strongSelf.reconnectErrors++; + [[strongSelf reconnectTimeout] cancel];//cancel the isalive reconnect so we dont falsely assume we succeeded + strongSelf.reconnectTimeout = nil; + + + if (![strongConnection verifyLastActive]){ + return;//connection will have aborted above + } // Transition into reconnecting state [SRConnection ensureReconnecting:strongConnection]; @@ -181,8 +194,6 @@ - (void)poll:(id)connection connectionData:(NSString *)co SRLogLPDebug(@"will poll again in %ld seconds",(long)[_errorDelay integerValue]); - canReconnect = @(YES); - [[NSBlockOperation blockOperationWithBlock:^{ [strongSelf poll:strongConnection connectionData:connectionData completionHandler:nil]; }] performSelector:@selector(start) withObject:nil afterDelay:[strongSelf.errorDelay integerValue]]; @@ -197,30 +208,29 @@ - (void)poll:(id)connection connectionData:(NSString *)co [self.pollingOperationQueue addOperation:operation]; } -- (void)delayConnectionReconnect:(id)connection canReconnect:(NSNumber *)canReconnect { +- (void)delayConnectionReconnect:(id)connection { if ([self isConnectionReconnecting:connection]) { __weak __typeof(&*self)weakSelf = self; __weak __typeof(&*connection)weakConnection = connection; - __weak __typeof(&*canReconnect)weakCanReconnect = canReconnect; SRLogLPDebug(@"will reconnect in %@", self.reconnectDelay); - [[NSBlockOperation blockOperationWithBlock:^{ + //in order to force the retry timeout, we increase backoff for time + int isAliveDelay = MIN( [self.reconnectDelay integerValue] + pow(2.0, self.reconnectErrors), 360000);//NOTE: one hour will be much longer than connection's reconnect timeout. this is not indicating how long we wait to reconnect + self.reconnectTimeout = [NSBlockOperation blockOperationWithBlock:^{ __strong __typeof(&*weakSelf)strongSelf = weakSelf; __strong __typeof(&*weakConnection)strongConnection = weakConnection; - __strong __typeof(&*weakCanReconnect)strongCanReconnect = weakCanReconnect; SRLogLPWarn(@"reconnecting"); - [strongSelf connectionReconnect:strongConnection canReconnect:strongCanReconnect]; + [strongSelf connectionReconnect:strongConnection]; - }] performSelector:@selector(start) withObject:nil afterDelay:[self.reconnectDelay integerValue]]; + }]; + //note: we rely on our error and success callback to cancel this. + [self.reconnectTimeout performSelector:@selector(start) withObject:nil afterDelay:isAliveDelay]; } } -- (void)connectionReconnect:(id)connection canReconnect:(NSNumber *)canReconnect { - if ([canReconnect boolValue]) { - canReconnect = @(NO); - // Mark the connection as connected - if ([connection changeState:reconnecting toState:connected]) { - [connection didReconnect]; - } +- (void)connectionReconnect:(id)connection { + // Mark the connection as connected + if ([connection changeState:reconnecting toState:connected]) { + [connection didReconnect]; } } diff --git a/SignalR.Client/Transports/SRServerSentEventsTransport.m b/SignalR.Client/Transports/SRServerSentEventsTransport.m index 281eb463..d1f856b1 100644 --- a/SignalR.Client/Transports/SRServerSentEventsTransport.m +++ b/SignalR.Client/Transports/SRServerSentEventsTransport.m @@ -67,7 +67,7 @@ - (BOOL)supportsKeepAlive { - (void)negotiate:(id)connection connectionData:(NSString *)connectionData completionHandler:(void (^)(SRNegotiationResponse * response, NSError *error))block { SRLogSSEDebug(@"serverSentEvents will negotiate"); - [super negotiate:connection connectionData:connectionData completionHandler:nil]; + [super negotiate:connection connectionData:connectionData completionHandler:block]; } - (void)start:(id)connection connectionData:(NSString *)connectionData completionHandler:(void (^)(id response, NSError *error))block { @@ -86,6 +86,11 @@ - (void)start:(id)connection connectionData:(NSString *)c }; NSError *timeout = [[NSError alloc]initWithDomain:[NSString stringWithFormat:NSLocalizedString(@"com.SignalR.SignalR-ObjC.%@",@""),NSStringFromClass([self class])] code:NSURLErrorTimedOut userInfo:userInfo]; SRLogSSEError(@"serverSentEvents failed to receive initialized message before timeout"); + strongSelf.stop = YES; + strongSelf.eventSource.opened = nil; + strongSelf.eventSource.message = nil; + strongSelf.eventSource.closed = nil; + [strongSelf.eventSource close]; strongSelf.completionHandler(nil, timeout); strongSelf.completionHandler = nil; } @@ -150,14 +155,7 @@ - (void)open:(id )connection connectionData:(NSString *)c //operation.securityPolicy = self.securityPolicy; _eventSource = [[SREventSourceStreamReader alloc] initWithStream:operation.outputStream]; _eventSource.opened = ^() { - __strong __typeof(&*weakConnection)strongConnection = weakConnection; SRLogSSEInfo(@"serverSentEvents did open eventSource"); - - // This will noop if we're not in the reconnecting state - if([strongConnection changeState:reconnecting toState:connected]) { - // Raise the reconnect event if the connection comes back up - [strongConnection didReconnect]; - } }; _eventSource.message = ^(SRServerSentEvent * sseEvent) { __strong __typeof(&*weakSelf)strongSelf = weakSelf; @@ -167,6 +165,15 @@ - (void)open:(id )connection connectionData:(NSString *)c NSString *data = [[NSString alloc] initWithData:sseEvent.data encoding:NSUTF8StringEncoding]; SRLogSSEInfo(@"serverSentEvents did receive: %@", data); if([data caseInsensitiveCompare:@"initialized"] == NSOrderedSame) { + // SSE can get a 503 error, terminated connection, etc + // which should not trigger reconnect, so instead the server sends + // down initialized. This is sufficient for knowing we have + // reconnected. + // This will noop if we're not in the reconnecting state + if([strongConnection changeState:reconnecting toState:connected]) { + // Raise the reconnect event if the connection comes back up + [strongConnection didReconnect]; + } return; } @@ -215,8 +222,7 @@ - (void)open:(id )connection connectionData:(NSString *)c [strongSelf completeAbort]; } else if ([strongSelf tryCompleteAbort]) { - } - else { + } else if ([strongConnection verifyLastActive]){//check in if we should abandon [strongSelf reconnect:strongConnection data:connectionData]; } }; @@ -228,7 +234,7 @@ - (void)open:(id )connection connectionData:(NSString *)c if (strongSelf.stop) { [strongSelf completeAbort]; } else if ([strongSelf tryCompleteAbort]) { - } else { + } else if ([strongConnection verifyLastActive]){//check in if we should abandon [strongSelf reconnect:strongConnection data:connectionData]; } } failure:^(AFHTTPRequestOperation *operation, NSError *error) { @@ -245,9 +251,14 @@ - (void)open:(id )connection connectionData:(NSString *)c selector:@selector(start) object:nil]; self.connectTimeoutOperation = nil; - + strongSelf.stop = YES; + strongSelf.eventSource.opened = nil; + strongSelf.eventSource.message = nil; + strongSelf.eventSource.closed = nil; + [strongSelf.eventSource close]; strongSelf.completionHandler(nil, error); strongSelf.completionHandler = nil; + strongSelf.eventSource = nil; } else if (!isReconnecting){//failure should first attempt to reconect SRLogSSEWarn(@"will reconnect from errors: %@", error); } else {//failure while reconnecting should error diff --git a/Tests/Mocks/SRMockSSENetworkStream.h b/Tests/Mocks/SRMockSSENetworkStream.h index 0d0680f4..8cd04589 100644 --- a/Tests/Mocks/SRMockSSENetworkStream.h +++ b/Tests/Mocks/SRMockSSENetworkStream.h @@ -8,14 +8,19 @@ #import #import +#import +@class SRMockWaitBlockOperation; @interface SRMockSSENetworkStream : NSObject +@property (strong, nonatomic, readonly) NSOutputStream * stream; + - (void)prepareForOpeningResponse:(void (^)())then; - (void)prepareForOpeningResponse:(NSString *)response then:(void (^)())then; - (void)prepareForNextResponse:(NSString *)response then:(void (^)())then; - (void)prepareForClose; - (void)prepareForError:(NSError *)error; +- (void)prepareForConnectTimeout:(NSInteger)timeout beforeCaptureTimeout:(void (^)(SRMockWaitBlockOperation *transportConnectTimeout))beforeCaptureTimeout afterCaptureTimeout:(void (^)(SRMockWaitBlockOperation *transportConnectTimeout))afterCaptureTimeout; - (void)stopMocking; diff --git a/Tests/Mocks/SRMockSSENetworkStream.m b/Tests/Mocks/SRMockSSENetworkStream.m index 2bf008e8..1352e437 100644 --- a/Tests/Mocks/SRMockSSENetworkStream.m +++ b/Tests/Mocks/SRMockSSENetworkStream.m @@ -7,6 +7,7 @@ // #import "SRMockSSENetworkStream.h" +#import "SRMockWaitBlockOperation.h" #import @interface SRMockSSENetworkStream () @@ -14,6 +15,7 @@ @interface SRMockSSENetworkStream () @property (readwrite, nonatomic, strong) NSData* lastData; @property (readwrite, nonatomic, strong) id dataDelegate; @property (readwrite, nonatomic, strong) id mock; +@property (readwrite, nonatomic, strong) NSOutputStream* outputStream; //only call this directly if you don't want to trigger the stream.opened callback @property (readwrite, nonatomic, copy) void (^onSuccess)(AFHTTPRequestOperation *operation, id responseObject); @property (readwrite, nonatomic, copy) void (^onFailure)(AFHTTPRequestOperation *operation, NSError *error); @@ -47,6 +49,10 @@ - (instancetype)init { return self; } +- (NSOutputStream *)stream { + return self.outputStream; +} + - (void)prepareForOpeningResponse:(void (^)())then { return [self prepareForOpeningResponse:nil then:then]; } @@ -54,7 +60,8 @@ - (void)prepareForOpeningResponse:(void (^)())then { - (void)prepareForOpeningResponse:(NSString *)response then:(void (^)())then { NSOutputStream* dataStream = [[NSOutputStream alloc] initToMemory]; [[[self.mock stub] andReturn: dataStream] outputStream]; - + _outputStream = dataStream; + if (!response) { response = @""; } @@ -74,6 +81,24 @@ - (void)prepareForOpeningResponse:(NSString *)response then:(void (^)())then { } } + +- (void)prepareForConnectTimeout:(NSInteger)timeout beforeCaptureTimeout:(void (^)(SRMockWaitBlockOperation *))beforeCaptureTimeout afterCaptureTimeout:(void (^)(SRMockWaitBlockOperation *))afterCaptureTimeout{ + //note: even though it's a connect timeout, we want an outputstream + //so that we can verify it closes + _outputStream = [[NSOutputStream alloc] initToMemory]; + [_outputStream open]; + [[[self.mock stub] andReturn: _outputStream] outputStream]; + + SRMockWaitBlockOperation* transportConnectTimeout = [[SRMockWaitBlockOperation alloc] initWithWaitTime:10]; + if (beforeCaptureTimeout) { + beforeCaptureTimeout(transportConnectTimeout); + } + [transportConnectTimeout stopMocking]; + if (afterCaptureTimeout) { + afterCaptureTimeout(transportConnectTimeout); + } +} + - (void)prepareForNextResponse:(NSString *)response then:(void (^)())then { NSMutableData* prior = [[NSMutableData alloc] initWithData: _lastData]; NSData* data = [response dataUsingEncoding:NSUTF8StringEncoding]; diff --git a/Tests/Tests/SRAutoTransportTests.m b/Tests/Tests/SRAutoTransportTests.m index 9688777f..425230c4 100644 --- a/Tests/Tests/SRAutoTransportTests.m +++ b/Tests/Tests/SRAutoTransportTests.m @@ -14,10 +14,16 @@ #import "SRAutoTransport.h" #import "SRWebSocketTransport.h" #import "SRServerSentEventsTransport.h" +#import "SRLongPollingTransport.h" #import "SRMockClientTransport.h" #import "SRMockWaitBlockOperation.h" #import "SRMockWSNetworkStream.h" #import "SRMockSSENetworkStream.h" +#import "SRMockNetwork.h" + +@interface SRLongPollingTransport () +@property (strong, nonatomic, readwrite) NSOperationQueue *pollingOperationQueue; +@end @interface SRAutoTransport (UnitTest) @@ -109,5 +115,71 @@ - (void)testSlowToInitializeWebsocketCleansUpAndTriesNextTransport { XCTAssert([[autoTransport name] isEqualToString:[sse name]]); } +- (void)testSlowToInitializeServerSentEventsCleansUpAndTriesNextTransport { + XCTestExpectation *initialized = [self expectationWithDescription:@"Handler called"]; + + SRConnection* connection = [[SRConnection alloc] initWithURLString:@"http://localhost:0000"]; + + SRServerSentEventsTransport *sse = [[SRServerSentEventsTransport alloc] init]; + [sse setServerSentEventsOperationQueue:nil]; + + id mockSSETransport = [OCMockObject partialMockForObject:sse]; + [[[mockSSETransport expect] andForwardToRealObject] start:[OCMArg any] connectionData:[OCMArg any] completionHandler:[OCMArg any]]; + + SRLongPollingTransport *lp = [[SRLongPollingTransport alloc] init]; + lp.pollingOperationQueue = nil;////set to nil to get around weird ARC OCMock bugs http://stackoverflow.com/questions/18121902/using-ocmock-on-nsoperation-gives-bad-access + + id mockLPTransport = [OCMockObject partialMockForObject:lp]; + [[[mockLPTransport expect] andForwardToRealObject] start:[OCMArg any] connectionData:[OCMArg any] completionHandler:[OCMArg any]]; + + SRAutoTransport* autoTransport = [[SRAutoTransport alloc] initWithTransports:@[sse, lp]]; + + id json = @{ + @"ConnectionId": @"10101", + @"ConnectionToken": @"10101010101", + @"DisconnectTimeout": @30, + @"ProtocolVersion": @"1.3.0.0", + @"TransportConnectTimeout": @10, + @"TryWebSockets": @NO + }; + [SRMockClientTransport negotiateForTransport:autoTransport statusCode:@200 json:json]; + __block id connect = nil; + + connection.started = ^{ + [initialized fulfill];//note, we wont fulfill this till we've fallen back to longPolling + [connect stopMocking];//if we dont do this we create a tight loop as longpolling will + //instantly return more data + }; + + SRMockSSENetworkStream* sseNetworkStream = [[SRMockSSENetworkStream alloc] init]; + //cannot OCMock bridge free classes + // [[[sseNetworkStream stream] expect] close]; + + + //Setup SSE to get timed out so we fallback + [sseNetworkStream prepareForConnectTimeout:10 beforeCaptureTimeout:^(SRMockWaitBlockOperation *transportConnectTimeout){ + //Start the connection and capture the performSelector blocks + //for use later + [connection start:autoTransport]; + } afterCaptureTimeout:^(SRMockWaitBlockOperation *transportConnectTimeout){ + //this is before the timeout has occurred, so last minute mock + //the request + connect = [SRMockNetwork mockHttpRequestOperationForClass:[AFHTTPRequestOperation class] + statusCode:@200 + responseString:@"abcdefg"]; + transportConnectTimeout.afterWait();//calls the timeout method, afterWait is the block passed + //at this point we should have fallen back to longPolling + }]; + + [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) { + if (error) { + NSLog(@"Timeout Error: %@", error); + } + }]; + [mockSSETransport verify]; + XCTAssertEqual([[sseNetworkStream stream] streamStatus], NSStreamStatusClosed); + + XCTAssert([[autoTransport name] isEqualToString:[lp name]]); +} @end diff --git a/Tests/Tests/SRServerSentEventsTransportTests.m b/Tests/Tests/SRServerSentEventsTransportTests.m index 120a6c21..8d36c44f 100644 --- a/Tests/Tests/SRServerSentEventsTransportTests.m +++ b/Tests/Tests/SRServerSentEventsTransportTests.m @@ -52,17 +52,17 @@ - (void)testStartCallsTheCompletionHandlerAfterSuccess { SRConnection* connection = [[SRConnection alloc] initWithURLString:@"http://localhost:0000"]; connection.connectionToken = @"10101010101"; connection.connectionId = @"10101"; + connection.transportConnectTimeout = @10; [connection changeState:disconnected toState:connected]; SRServerSentEventsTransport* sse = [[SRServerSentEventsTransport alloc] init]; sse.serverSentEventsOperationQueue = nil;//set to nil to get around weird ARC OCMock bugs http://stackoverflow.com/questions/18121902/using-ocmock-on-nsoperation-gives-bad-access - - [NetworkMock prepareForOpeningResponse:^{ + //note: SSE sends down initialized but it gets ignored in all clients + [NetworkMock prepareForOpeningResponse:@"data: initialized\n\ndata: {}\n" then:^{ [sse start: connection connectionData:@"12345" completionHandler:^(id response, NSError *error){ [expectation fulfill]; }]; }]; - [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) { if (error) { NSLog(@"Timeout Error: %@", error); @@ -78,6 +78,7 @@ - (void)testParsesInitialBuffer { SRConnection* connection = [[SRConnection alloc] initWithURLString:@"http://localhost:0000"]; connection.connectionToken = @"10101010101"; connection.connectionId = @"10101"; + connection.transportConnectTimeout = @10; [connection changeState:disconnected toState:connected]; SRServerSentEventsTransport* sse = [[SRServerSentEventsTransport alloc] init]; @@ -104,6 +105,7 @@ - (void)testIgnoresInitializedAndEmptyLinesWhenParsingMessages { SRConnection* connection = [[SRConnection alloc] initWithURLString:@"http://localhost:0000"]; connection.connectionToken = @"10101010101"; connection.connectionId = @"10101"; + connection.transportConnectTimeout = @10; [connection changeState:disconnected toState:connected]; connection.received = ^(NSDictionary * data){ @@ -134,6 +136,7 @@ - (void)testConnectionInitialFailureUsesCallback { SRConnection* connection = [[SRConnection alloc] initWithURLString:@"http://localhost:0000"]; connection.connectionToken = @"10101010101"; connection.connectionId = @"10101"; + connection.transportConnectTimeout = @10; [connection changeState:disconnected toState:connected]; SRServerSentEventsTransport* sse = [[SRServerSentEventsTransport alloc] init]; @@ -196,7 +199,7 @@ - (void)testConnectionErrorRetries__RetriesAfterADelay__CommunicatesLifeCycleVia [NetworkMock stopMocking]; SRMockSSENetworkStream* NetworkReconnectMock = [[SRMockSSENetworkStream alloc]init]; [reconnectDelay.mock stopMocking];//dont want to accidentally get other blocks - [NetworkReconnectMock prepareForOpeningResponse:^{ + [NetworkReconnectMock prepareForOpeningResponse:@"data: initialized\n\n" then:^{ reconnectDelay.afterWait(); }]; @@ -256,7 +259,7 @@ - (void)testLostConnectionAbortsAllConnectionsAndReconnects { [NetworkMock stopMocking]; SRMockSSENetworkStream* NetworkReconnectMock = [[SRMockSSENetworkStream alloc]init]; [reconnectDelay.mock stopMocking];//dont want to accidentally get other blocks - [NetworkReconnectMock prepareForOpeningResponse:^{ + [NetworkReconnectMock prepareForOpeningResponse:@"data: initialized\n\n" then:^{ reconnectDelay.afterWait(); }]; @@ -354,6 +357,7 @@ - (void)testHandlesExtraEmptyLinesWhenParsingMessages { SRConnection* connection = [[SRConnection alloc] initWithURLString:@"http://localhost:0000"]; connection.connectionToken = @"10101010101"; connection.connectionId = @"10101"; + connection.transportConnectTimeout = @10; [connection changeState:disconnected toState:connected]; connection.received = ^(NSString * data){ @@ -383,6 +387,7 @@ - (void)testHandlesNewLinesSpreadOutOverReads { SRConnection* connection = [[SRConnection alloc]initWithURLString:@"http://localhost:0000"]; connection.connectionToken = @"10101010101"; connection.connectionId = @"10101"; + connection.transportConnectTimeout = @10; [connection changeState:disconnected toState:connected]; connection.received = ^(NSDictionary * data){ @@ -850,7 +855,7 @@ - (void)testStreamClosesCleanlyShouldReconnect { [NetworkMock stopMocking]; SRMockSSENetworkStream* NetworkReconnectMock = [[SRMockSSENetworkStream alloc]init]; [reconnectDelay stopMocking]; - [NetworkReconnectMock prepareForOpeningResponse:^{ + [NetworkReconnectMock prepareForOpeningResponse:@"data: initialized\n\n" then:^{ reconnectDelay.afterWait(); }];