Skip to content

Commit 0de79e3

Browse files
Merge pull request #1335 from apollographql/add/websocket-pause
Add ability to pause/resume websocket without dumping subscriptions
2 parents b44895d + ab34ab9 commit 0de79e3

2 files changed

Lines changed: 95 additions & 1 deletion

File tree

Sources/ApolloWebSocket/WebSocketTransport.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,23 @@ public class WebSocketTransport {
323323

324324
reconnect.value = oldReconnectValue
325325
}
326+
327+
/// Disconnects the websocket while setting the auto-reconnect value to false,
328+
/// allowing purposeful disconnects that do not dump existing subscriptions.
329+
/// NOTE: You will receive an error on the subscription (should be a `Starscream.WSError` with code 1000) when the socket disconnects.
330+
/// ALSO NOTE: To reconnect after calling this, you will need to call `resumeWebSocketConnection`.
331+
public func pauseWebSocketConnection() {
332+
self.reconnect.value = false
333+
self.websocket.disconnect()
334+
}
335+
336+
/// Reconnects a paused web socket.
337+
///
338+
/// - Parameter autoReconnect: `true` if you want the websocket to automatically reconnect if the connection drops. Defaults to true.
339+
public func resumeWebSocketConnection(autoReconnect: Bool = true) {
340+
self.reconnect.value = autoReconnect
341+
self.websocket.connect()
342+
}
326343
}
327344

328345
// MARK: - HTTPNetworkTransport conformance

Tests/ApolloWebsocketTests/StarWarsSubscriptionTests.swift

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@ import Apollo
33
import ApolloTestSupport
44
@testable import ApolloWebSocket
55
import StarWarsAPI
6+
import Starscream
67

78
class StarWarsSubscriptionTests: XCTestCase {
8-
let SERVER: String = "ws://localhost:8080/websocket"
9+
let SERVER = "ws://localhost:8080/websocket"
910
let concurrentQueue = DispatchQueue(label: "com.apollographql.testing", attributes: .concurrent)
1011

1112
var client: ApolloClient!
1213
var webSocketTransport: WebSocketTransport!
1314

1415
var connectionStartedExpectation: XCTestExpectation?
16+
var disconnectedExpectation: XCTestExpectation?
17+
var reconnectedExpectation: XCTestExpectation?
1518

1619
override func setUp() {
1720
super.setUp()
@@ -408,11 +411,85 @@ class StarWarsSubscriptionTests: XCTestCase {
408411

409412
waitForExpectations(timeout: 10, handler: nil)
410413
}
414+
415+
func testPausingAndResumingWebSocketConnection() {
416+
let subscription = ReviewAddedSubscription()
417+
let reviewMutation = CreateAwesomeReviewMutation()
418+
419+
// Send the mutations via a separate transport so they can still be sent when the websocket is disconnected
420+
let httpTransport = HTTPNetworkTransport(url: URL(string: "http://localhost:8080/graphql")!)
421+
let httpClient = ApolloClient(networkTransport: httpTransport)
422+
423+
func sendReview() {
424+
let reviewSentExpectation = self.expectation(description: "review sent")
425+
httpClient.perform(mutation: reviewMutation) { mutationResult in
426+
switch mutationResult {
427+
case .success:
428+
break
429+
case .failure(let error):
430+
XCTFail("Unexpected error sending review: \(error)")
431+
}
432+
433+
reviewSentExpectation.fulfill()
434+
}
435+
self.wait(for: [reviewSentExpectation], timeout: 10)
436+
}
437+
438+
let subscriptionExpectation = self.expectation(description: "Received review")
439+
// This should get hit twice - once before we pause the web socket and once after.
440+
subscriptionExpectation.expectedFulfillmentCount = 2
441+
let reviewAddedSubscription = self.client.subscribe(subscription: subscription) { subscriptionResult in
442+
switch subscriptionResult {
443+
case .success(let graphQLResult):
444+
XCTAssertEqual(graphQLResult.data?.reviewAdded?.episode, .jedi)
445+
subscriptionExpectation.fulfill()
446+
case .failure(let error):
447+
if let wsError = error as? Starscream.WSError {
448+
// This is an expected error on disconnection, ignore it.
449+
XCTAssertEqual(wsError.code, 1000)
450+
} else {
451+
XCTFail("Unexpected error receiving subscription: \(error)")
452+
subscriptionExpectation.fulfill()
453+
}
454+
}
455+
}
456+
457+
self.waitForSubscriptionsToStart()
458+
sendReview()
459+
460+
self.disconnectedExpectation = self.expectation(description: "Web socket disconnected")
461+
webSocketTransport.pauseWebSocketConnection()
462+
self.wait(for: [self.disconnectedExpectation!], timeout: 10)
463+
464+
// This should not go through since the socket is paused
465+
sendReview()
466+
467+
self.reconnectedExpectation = self.expectation(description: "Web socket reconnected")
468+
webSocketTransport.resumeWebSocketConnection()
469+
self.wait(for: [self.reconnectedExpectation!], timeout: 10)
470+
self.waitForSubscriptionsToStart()
471+
472+
// Now that we've reconnected, this should go through to the same subscription.
473+
sendReview()
474+
475+
self.wait(for: [subscriptionExpectation], timeout: 10)
476+
477+
// Cancel subscription so it doesn't keep receiving from other tests.
478+
reviewAddedSubscription.cancel()
479+
}
411480
}
412481

413482
extension StarWarsSubscriptionTests: WebSocketTransportDelegate {
414483

415484
func webSocketTransportDidConnect(_ webSocketTransport: WebSocketTransport) {
416485
self.connectionStartedExpectation?.fulfill()
417486
}
487+
488+
func webSocketTransportDidReconnect(_ webSocketTransport: WebSocketTransport) {
489+
self.reconnectedExpectation?.fulfill()
490+
}
491+
492+
func webSocketTransport(_ webSocketTransport: WebSocketTransport, didDisconnectWithError error: Error?) {
493+
self.disconnectedExpectation?.fulfill()
494+
}
418495
}

0 commit comments

Comments
 (0)