Skip to content

Swift implementation of the graphql-transport-ws WebSocket subprotocol.

License

Notifications You must be signed in to change notification settings

GraphQLSwift/GraphQLTransportWS

Repository files navigation

GraphQLTransportWS

This implements the graphql-transport-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

Usage

To use this package, include it in your Package.swift dependencies:

.package(url: "https://github.com/GraphQLSwift/GraphQLTransportWS", from: "<version>")

Then create a class to implement the Messenger protocol. Here's an example using WebSocketKit:

import WebSocketKit
import GraphQLTransportWS

/// Messenger wrapper for WebSockets
struct WebSocketMessenger: Messenger {
    let websocket: WebSocket

    func send<S>(_ message: S) where S: Collection, S.Element == Character async throws {
        try await websocket.send(message)
    }

    func error(_ message: String, code: Int) async throws {
        try await websocket.send("\(code): \(message)")
    }

    func close() async throws {
        try await 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 in
        let messenger = WebSocketMessenger(websocket: websocket)
        let server = GraphQLTransportWS.Server<EmptyInitPayload?>(
            messenger: messenger,
            onExecute: { graphQLRequest in
                try await api.execute(
                    request: graphQLRequest.query,
                    context: context,
                    on: self.eventLoop,
                    variables: graphQLRequest.variables,
                    operationName: graphQLRequest.operationName
                )
            },
            onSubscribe: { graphQLRequest in
                try await api.subscribe(
                    request: graphQLRequest.query,
                    context: context,
                    on: self.eventLoop,
                    variables: graphQLRequest.variables,
                    operationName: graphQLRequest.operationName
                )
            }
        )
        let incoming = AsyncStream<String> { continuation in
            websocket.onText { _, message in
                continuation.yield(message)
            }
        }
        try await server.listen(to: incoming)
    }
)

Authentication

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:

struct UsernameAndPasswordInitPayload: Equatable & Codable {
    let username: String
    let password: String
}

let server = GraphQLTransportWS.Server<UsernameAndPasswordInitPayload>(
    messenger: messenger,
    onExecute: { ... },
    onSubscribe: { ... }
)
server.auth { payload in
    guard payload.username == "admin" else {
        throw Abort(.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?>

About

Swift implementation of the graphql-transport-ws WebSocket subprotocol.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages