This implements the graphql-ws WebSocket subprotocol. It is mainly intended for server support, but there is a basic client implementation included.
Features:
- Server implementation that implements defined protocol conversations
- Client and Server types that wrap messengers
- Codable Server and Client message structures
- Custom authentication support
To use this package, include it in your Package.swift dependencies:
.package(url:"https://github.com/GraphQLSwift/GraphQLWS", from:"<version>"),Then create a concrete type that conforms to the Messenger protocol. Here's an example using
WebSocketKit:
import WebSocketKit
import GraphQLWS
/// Messenger wrapper for WebSockets
structWebSocketMessenger:Messenger{letwebsocket:WebSocketfunc send(_ message:Data)asyncthrows{tryawait websocket.send(String(decoding: message, as:UTF8.self))}func error(_ message:String, code:Int)asyncthrows{tryawait websocket.close(code: code)}func close()asyncthrows{tryawait websocket.close()}}Next create a Server, provide the messenger you just defined, and wrap the API execute and subscribe commands:
routes.webSocket("graphqlSubscribe",
onUpgrade:{ request, websocket inletmessenger=WebSocketMessenger(websocket: websocket)letserver=GraphQLWS.Server<EmptyInitPayload?>(
messenger: messenger,
onExecute:{ graphQLRequest intryawait api.execute(
request: graphQLRequest.query,
context: context,
on:self.eventLoop,
variables: graphQLRequest.variables,
operationName: graphQLRequest.operationName
)},
onSubscribe:{ graphQLRequest intryawait api.subscribe(
request: graphQLRequest.query,
context: context,
on:self.eventLoop,
variables: graphQLRequest.variables,
operationName: graphQLRequest.operationName
)})letincoming= AsyncStream<Data>{ continuation in
websocket.onText{ _, message in
continuation.yield(Data(message.utf8))}}tryawait server.listen(to: incoming)})This package exposes authentication hooks on the connection_init message. To perform custom authentication,
provide a codable type to the Server init and define an auth callback on the server. For example:
structUsernameAndPasswordInitPayload:Equatable&Codable{letusername:Stringletpassword:String}letserver=GraphQLWS.Server<UsernameAndPasswordInitPayload>(
messenger: messenger,
onExecute:{...},
onSubscribe:{...})
server.auth{ payload inguard payload.username =="admin"else{throwAbort(.unauthorized)}}This example would require connection_init message from the client to look like this:
{
"type": "connection_init",
"payload": {
"username": "admin",
"password": "supersafe"
}
}If the payload field is not required on your server, you may make Server's generic declaration optional like Server<Payload?>