Skip to content

Commit 77b975d

Browse files
Initial commit
0 parents  commit 77b975d

File tree

15 files changed

+1115
-0
lines changed

15 files changed

+1115
-0
lines changed

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj
5+
xcuserdata/
6+
DerivedData/
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1320"
4+
version = "1.3">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForTesting = "YES"
11+
buildForRunning = "YES"
12+
buildForProfiling = "YES"
13+
buildForArchiving = "YES"
14+
buildForAnalyzing = "YES">
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "GraphQLWS"
18+
BuildableName = "GraphQLWS"
19+
BlueprintName = "GraphQLWS"
20+
ReferencedContainer = "container:">
21+
</BuildableReference>
22+
</BuildActionEntry>
23+
<BuildActionEntry
24+
buildForTesting = "YES"
25+
buildForRunning = "YES"
26+
buildForProfiling = "NO"
27+
buildForArchiving = "NO"
28+
buildForAnalyzing = "YES">
29+
<BuildableReference
30+
BuildableIdentifier = "primary"
31+
BlueprintIdentifier = "GraphQLWSTests"
32+
BuildableName = "GraphQLWSTests"
33+
BlueprintName = "GraphQLWSTests"
34+
ReferencedContainer = "container:">
35+
</BuildableReference>
36+
</BuildActionEntry>
37+
<BuildActionEntry
38+
buildForTesting = "YES"
39+
buildForRunning = "YES"
40+
buildForProfiling = "YES"
41+
buildForArchiving = "YES"
42+
buildForAnalyzing = "YES">
43+
<BuildableReference
44+
BuildableIdentifier = "primary"
45+
BlueprintIdentifier = "GraphQLTransportWS"
46+
BuildableName = "GraphQLTransportWS"
47+
BlueprintName = "GraphQLTransportWS"
48+
ReferencedContainer = "container:">
49+
</BuildableReference>
50+
</BuildActionEntry>
51+
<BuildActionEntry
52+
buildForTesting = "YES"
53+
buildForRunning = "YES"
54+
buildForProfiling = "YES"
55+
buildForArchiving = "YES"
56+
buildForAnalyzing = "YES">
57+
<BuildableReference
58+
BuildableIdentifier = "primary"
59+
BlueprintIdentifier = "GraphQLWSCore"
60+
BuildableName = "GraphQLWSCore"
61+
BlueprintName = "GraphQLWSCore"
62+
ReferencedContainer = "container:">
63+
</BuildableReference>
64+
</BuildActionEntry>
65+
</BuildActionEntries>
66+
</BuildAction>
67+
<TestAction
68+
buildConfiguration = "Debug"
69+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
70+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
71+
shouldUseLaunchSchemeArgsEnv = "YES">
72+
<Testables>
73+
<TestableReference
74+
skipped = "NO">
75+
<BuildableReference
76+
BuildableIdentifier = "primary"
77+
BlueprintIdentifier = "GraphQLWSTests"
78+
BuildableName = "GraphQLWSTests"
79+
BlueprintName = "GraphQLWSTests"
80+
ReferencedContainer = "container:">
81+
</BuildableReference>
82+
</TestableReference>
83+
</Testables>
84+
</TestAction>
85+
<LaunchAction
86+
buildConfiguration = "Debug"
87+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
88+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
89+
launchStyle = "0"
90+
useCustomWorkingDirectory = "NO"
91+
ignoresPersistentStateOnLaunch = "NO"
92+
debugDocumentVersioning = "YES"
93+
debugServiceExtension = "internal"
94+
allowLocationSimulation = "YES">
95+
</LaunchAction>
96+
<ProfileAction
97+
buildConfiguration = "Release"
98+
shouldUseLaunchSchemeArgsEnv = "YES"
99+
savedToolIdentifier = ""
100+
useCustomWorkingDirectory = "NO"
101+
debugDocumentVersioning = "YES">
102+
<MacroExpansion>
103+
<BuildableReference
104+
BuildableIdentifier = "primary"
105+
BlueprintIdentifier = "GraphQLWS"
106+
BuildableName = "GraphQLWS"
107+
BlueprintName = "GraphQLWS"
108+
ReferencedContainer = "container:">
109+
</BuildableReference>
110+
</MacroExpansion>
111+
</ProfileAction>
112+
<AnalyzeAction
113+
buildConfiguration = "Debug">
114+
</AnalyzeAction>
115+
<ArchiveAction
116+
buildConfiguration = "Release"
117+
revealArchiveInOrganizer = "YES">
118+
</ArchiveAction>
119+
</Scheme>

Package.resolved

Lines changed: 61 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// swift-tools-version:5.4
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "GraphQLWS",
7+
products: [
8+
.library(
9+
name: "GraphQLWS",
10+
targets: ["GraphQLWS"]
11+
),
12+
],
13+
dependencies: [
14+
.package(name: "Graphiti", url: "https://github.com/GraphQLSwift/Graphiti.git", from: "1.0.0"),
15+
// TODO: Mainline when this PR is merged: https://github.com/GraphQLSwift/GraphQL/pull/97
16+
.package(name: "GraphQL", url: "https://github.com/NeedleInAJayStack/GraphQL.git", .branch("fix/GraphQLRequest")),
17+
.package(name: "GraphQLRxSwift", url: "https://github.com/GraphQLSwift/GraphQLRxSwift.git", from: "0.0.4"),
18+
.package(name: "RxSwift", url: "https://github.com/ReactiveX/RxSwift.git", from: "6.1.0"),
19+
.package(name: "swift-nio", url: "https://github.com/apple/swift-nio.git", .upToNextMinor(from: "2.33.0"))
20+
],
21+
targets: [
22+
.target(
23+
name: "GraphQLWS",
24+
dependencies: [
25+
.product(name: "Graphiti", package: "Graphiti"),
26+
.product(name: "GraphQLRxSwift", package: "GraphQLRxSwift"),
27+
.product(name: "GraphQL", package: "GraphQL"),
28+
.product(name: "NIO", package: "swift-nio"),
29+
.product(name: "RxSwift", package: "RxSwift")
30+
]),
31+
.testTarget(
32+
name: "GraphQLWSTests",
33+
dependencies: ["GraphQLWS"]
34+
),
35+
]
36+
)

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# GraphQLWS
2+
3+
A description of this package.

Sources/GraphQLWS/Client.swift

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright (c) 2021 PassiveLogic, Inc.
2+
3+
import Foundation
4+
5+
/// Adds client-side [graphql-ws protocol](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md)
6+
/// support, namely parsing and adding callbacks for each type of server respose.
7+
class Client {
8+
let onMessage: (String) -> Void
9+
let onConnectionError: (ConnectionErrorResponse) -> Void
10+
let onConnectionAck: (ConnectionAckResponse) -> Void
11+
let onConnectionKeepAlive: (ConnectionKeepAliveResponse) -> Void
12+
let onData: (DataResponse) -> Void
13+
let onError: (ErrorResponse) -> Void
14+
let onComplete: (CompleteResponse) -> Void
15+
16+
let decoder = JSONDecoder()
17+
18+
/// Create a new client.
19+
///
20+
/// - Parameters:
21+
/// - onMessage: callback run on receipt of any message
22+
/// - onConnectionError: callback run on receipt of a `connection_error` message
23+
/// - onConnectionAck: callback run on receipt of a `connection_ack` message
24+
/// - onData: callback run on receipt of a `data` message
25+
/// - onError: callback run on receipt of an `error` message
26+
/// - onComplete: callback run on receipt of a `complete` message
27+
init(
28+
onMessage: @escaping (String) -> Void = { _ in () },
29+
onConnectionError: @escaping (ConnectionErrorResponse) -> Void = { _ in () },
30+
onConnectionAck: @escaping (ConnectionAckResponse) -> Void = { _ in () },
31+
onConnectionKeepAlive: @escaping (ConnectionKeepAliveResponse) -> Void = { _ in () },
32+
onData: @escaping (DataResponse) -> Void = { _ in () },
33+
onError: @escaping (ErrorResponse) -> Void = { _ in () },
34+
onComplete: @escaping (CompleteResponse) -> Void = { _ in () }
35+
) {
36+
self.onMessage = onMessage
37+
self.onConnectionError = onConnectionError
38+
self.onConnectionAck = onConnectionAck
39+
self.onConnectionKeepAlive = onConnectionKeepAlive
40+
self.onData = onData
41+
self.onError = onError
42+
self.onComplete = onComplete
43+
}
44+
45+
func attach(to messenger: Messenger) {
46+
messenger.onRecieve { message in
47+
self.onMessage(message)
48+
49+
// Detect and ignore error responses.
50+
if message.starts(with: "44") {
51+
// TODO: Determine what to do with returned error messages
52+
return
53+
}
54+
55+
guard let json = message.data(using: .utf8) else {
56+
let error = GraphQLWSError.invalidEncoding()
57+
messenger.error(error.message, code: error.code)
58+
return
59+
}
60+
61+
let response: Response
62+
do {
63+
response = try self.decoder.decode(Response.self, from: json)
64+
}
65+
catch {
66+
let error = GraphQLWSError.noType()
67+
messenger.error(error.message, code: error.code)
68+
return
69+
}
70+
71+
switch response.type {
72+
case .GQL_CONNECTION_ERROR:
73+
guard let connectionErrorResponse = try? self.decoder.decode(ConnectionErrorResponse.self, from: json) else {
74+
let error = GraphQLWSError.invalidResponseFormat(messageType: .GQL_CONNECTION_ERROR)
75+
messenger.error(error.message, code: error.code)
76+
return
77+
}
78+
self.onConnectionError(connectionErrorResponse)
79+
case .GQL_CONNECTION_ACK:
80+
guard let connectionAckResponse = try? self.decoder.decode(ConnectionAckResponse.self, from: json) else {
81+
let error = GraphQLWSError.invalidResponseFormat(messageType: .GQL_CONNECTION_ACK)
82+
messenger.error(error.message, code: error.code)
83+
return
84+
}
85+
self.onConnectionAck(connectionAckResponse)
86+
case .GQL_CONNECTION_KEEP_ALIVE:
87+
guard let connectionKeepAliveResponse = try? self.decoder.decode(ConnectionKeepAliveResponse.self, from: json) else {
88+
let error = GraphQLWSError.invalidResponseFormat(messageType: .GQL_CONNECTION_KEEP_ALIVE)
89+
messenger.error(error.message, code: error.code)
90+
return
91+
}
92+
self.onConnectionKeepAlive(connectionKeepAliveResponse)
93+
case .GQL_DATA:
94+
guard let nextResponse = try? self.decoder.decode(DataResponse.self, from: json) else {
95+
let error = GraphQLWSError.invalidResponseFormat(messageType: .GQL_DATA)
96+
messenger.error(error.message, code: error.code)
97+
return
98+
}
99+
self.onData(nextResponse)
100+
case .GQL_ERROR:
101+
guard let errorResponse = try? self.decoder.decode(ErrorResponse.self, from: json) else {
102+
let error = GraphQLWSError.invalidResponseFormat(messageType: .GQL_ERROR)
103+
messenger.error(error.message, code: error.code)
104+
return
105+
}
106+
self.onError(errorResponse)
107+
case .GQL_COMPLETE:
108+
guard let completeResponse = try? self.decoder.decode(CompleteResponse.self, from: json) else {
109+
let error = GraphQLWSError.invalidResponseFormat(messageType: .GQL_COMPLETE)
110+
messenger.error(error.message, code: error.code)
111+
return
112+
}
113+
self.onComplete(completeResponse)
114+
case .unknown:
115+
let error = GraphQLWSError.invalidType()
116+
messenger.error(error.message, code: error.code)
117+
}
118+
}
119+
}
120+
}

0 commit comments

Comments
 (0)