Git Product home page Git Product logo

apollo-phoenix-websocket's Introduction

APW - Apollo Phoenix Websocket

help maintain this lib

Apollo is a feature rich GQL client, APW implements an Apollo GraphQL Network Layer for it over Phoenix Channels allowing you to re-use a single bidirectional connection for executing your queries and mutations, the backend can send new data via subscriptions, and the Apollo client can update its internal store and update your views accordingly.

Since version 0.6.0, all Apollo operations are supported: queries, mutations, watchQueries (pooling) and subscriptions.

Using the Apollo Client, queries and mutations resolve to promises, and watchQueries and subscriptions resolve to observables.

See the Apollo client documentation for more info on how to invoke your GQL backend.

Installation

npm install --save apollo-phoenix-websocket

Usage

Just import the createNetworkInterface from APW and use it to create an ApolloClient.

The createNetworkInterface function takes an options object, the only required property is uri which specifies your endpoint websocket address.

import ApolloClient from 'apollo-client'
import {createNetworkInterface} from 'apollo-phoenix-websocket'

// Nothing to configure if you are using an Absinthe backend
// Otherwise take a look at the Options section.
const networkInterface = createNetworkInterface({uri: 'ws://localhost:4000/socket'})

const apollo = new ApolloClient({networkInterface})

Options

Most likely, (as you are looking for a phoenix-websocket transport) you might be using the Absinthe library to implement your Elixir GQL server. APW is configured by default to work out of the box with an Absinthe backend.

But if need araises, you can supply some advanced options to customize how it works. Here's is a commented example of the options that you can set for APW, with their respective default values:

createNetworkInterface({

  // The websockets endpoint to connect to, like ws://example.com:4000/socket
  uri: WS_URI,

  // how to send queries and mutations
  channel: {
    topic: '__absinthe__:control',
    event: 'doc',
  },

  // for using websocket subscriptions
  subscription: (subscriptionResponse) => ({
    topic: subscriptionResponse.subscriptionId,
    event: 'subscription:data',

    // extract the data from the event payload
    map: payload => payload.result.data,

    // what to do when unsubscribing
    off: controlChannel => {
      controlChannel.push('unsubscribe', {
        subscriptionId: subscriptionResponse.subscriptionId
      })
    }
  }),

  // If you want to reuse an existing Phoenix Socket, just provide a function
  // for APW to get it. By default, it will use the Phoenix Socket module.
  Socket: options => new Socket(options),
})

Middlewares

You can use middlewares with use just like with the standard apollo network interface. For example, a middleware can set authorization token on every request.

networkInterface.use([{
  applyMiddleware({request, options}, next) {
    // Here you can modify the interface options, for example
    // you can change the socket/channel that will handle the request
    // For example for a channel expecting authenticated queries
    options.channel.topic = "gql:restricted"
    options.channel.params = {...paramsForTopicJoin}

    // Or Modify the request
    request.variables = {name: 'Luke'}
    
    // Or Just add authorization token
    options.params = {
      ...options.params,
      auth_token: 'my-secret-token',
    }

    next()
  }
}])

Afterware

You can use afterwares with useAfter just like the standard apollo network interface. An example use-case is for error handling:

networkInterface.useAfter([{
  applyAfterware({response, options}, next) {
    // throw an error that will trigger your error handler
    if (response.error) {
      throw new Error(response.error)
    }
    next();
  }
}])

Absinthe backend

Absinthe is an amazing project (kudos to @benwilson512 et al.). It's actually very simple to create a GQL backend with it.

Take a look at the following places for more information:

Made with love <3

If you want to provide feedback or even better if you want to contribute some code feel free to open a new issue. Possible thanks to the awesome work of our contributors.

apollo-phoenix-websocket's People

Contributors

conor-mac-aoidh avatar g3z avatar skosch avatar vic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

apollo-phoenix-websocket's Issues

can't set context

i tried adding a context as showed in the readme

applyMiddleware({ request, options }, next) {
	// get the authentication token from local storage if it exists
	const store = require('libs/store').default;
	const token = store.getState().credentials && store.getState().credentials.token;
	request.context = token ? { guardian_token: token } : null;

	next();
},

but on phoenix this is what i get %{"vsn" => "1.0.0"}

def connect(params, socket) do
    IO.inspect params
    # ...
end

am I doing something wrong ?

Authentication workaround

I've imagined the steps for a login with APW like so

step Client Server
1 Create connection
2 Create empty context
3 Send login mutation
4 Populate context with user
5 Receive token
6 Store token for later use (see #22 )

turns out (as @benwilson512 pointed out) step 4 is not feasible at the moment

it was only pointed out to me yesterday that this (step 4) is an issue for websockets
because there isn't a way to alter or change the context of the websocket connection
when doing HTTP you can basically set it uniquely on each request
but for websockets that doesn't work, cause the context is setup once

do you think it's possible to build a workaround for this if/until absinthe is modified
maybe is it possible to force a disconnection/reconnection manually (so that token is picked up)

Is an update using Apollo's SubscriptionClient required?

Does this library need to be updated to work with the latest version of Apollo? If I use this lib's createNetworkInterface, the console is showing HTTPFetchNetworkInterface is being used, and the URI seems to default to /graphql.

Per the Apollo docs, to use "full websocket" (all queries, mutations, and subscriptions over ws):

import { SubscriptionClient } from 'subscriptions-transport-ws';
import ApolloClient from 'apollo-client';

const GRAPHQL_ENDPOINT = 'ws://localhost:3000/graphql';
const client = new SubscriptionClient(GRAPHQL_ENDPOINT, {
  reconnect: true,
});

const apolloClient = new ApolloClient({
    networkInterface: client,
});

compatibility with Apollo 2.x

Hi there,

First of all, thank you so much for this adapter, I have been using this with out any problems on Apollo 1.9.

The connections in Apollo 2.x have changed and are more modulair now.
Is there any work being done to make this compatible with 2.x ?

Thanks in advance,

Gerard

React Native suspend&resume

I'm using this in conjunction with react-native and everything was smooth until i tested a release build on my phone:
When the app is suspended and resumed after a while (30s) it crashes.
I have trouble identifying the source but here is the log

<Notice>: TCP Conn 0x1701834d0 SSL Handshake DONE
Apr  8 16:31:44 doStuff(CFNetwork)[1821] <Notice>: TIC TLS Event [3:0x170184440]: 20, Pending(0)
Apr  8 16:31:44 doStuff(CFNetwork)[1821] <Notice>: TIC TCP Conn Event [3:0x170184440]: 8 Err(0)
Apr  8 16:31:44 doStuff(CFNetwork)[1821] <Notice>: TIC TLS Handshake Complete [3:0x170184440]
Apr  8 16:31:44 doStuff[1821] <Notice>: tried to join multiple times. 'join' can only be called a single time per channel instance
Apr  8 16:31:44 SpringBoard[1493] <Notice>: Destination SBNotificationBannerDestination is now ready to receive notifications
Apr  8 16:31:44 doStuff[1821] <Notice>: Unhandled JS Exception: tried to join multiple times. 'join' can only be called a single time per channel instance
Apr  8 16:31:44 apsd(PersistentConnection)[90] <Notice>: 2017-04-08 16:31:44 +0200 apsd[90]: __dumpLogsForInconsistencyIfNecessary - lastBecameInconsistentTime 0.000000  sBecameInconsistentTime 0.000000  logDumpReason (null)
Apr  8 16:31:44 doStuff(CoreFoundation)[1821] <Notice>: *** Terminating app due to uncaught exception 'RCTFatalException: Unhandled JS Exception: tried to join multiple times. 'join' can only be called a single time per channel instance', reason: 'Unhandled JS Exception: tried to join multiple times. 'join' can only be ca..., stack:
i@65:234
u@64:1084
reportFatalError@5:186
<unknown>@19:136
value@19:810
'

my best guess is that tried to join multiple times. 'join' can only be called a single time per channel instance refers to this call in APW

this is how i have set up my Network Interface

let networkInterface = createNetworkInterface({
	uri: 'wss://my-phoenix-app.herokuapp.com:443/socket'
});

networkInterface.use([{
	applyMiddleware(req, next) {
		// get the authentication token from local storage if it exists
		const token = credentials_store && credentials_store.data && credentials_store.data.token;
		req.options.channel = req.options.channel || {};
		req.options.channel.topic = token ? "gql:query" : 'gql:login';
		req.options.channel.params = token ? { guardian_token: token } : null;
		next();
	}
}]);

file uploads support

Are uploads supported ?
absinthe plug has support for them (types)
And If you think uploads over WS are not a good idea,
would it be possible to use a more traditional REST approach maybe combining APW with something like apollo-upload-client

Question: how to receive a payload after connected to a websocket

I can see how to make a query or mutation from the client, but say my app's back end wants to send information in the same shape of a graphql response, but it has not been initiated by a query from the front end.

Is there any way to receive a payload like that through the channel and then insert it into Apollo's store?

Add supscription support

Just to continue the discussion from this issue about adding subscription support. There is an example here of apollo/absinthe subscriptions. However, there does not seem to be much documentation on the apollo side of their subscription support.. It is not mentioned at all in the API reference.

There is a SubscriptionNetworkInterface here. So I take it then that we need to add subscribe and unsubscribe methods to the network interface created by this repo?

Or should there actually be a separate network interface for subscriptions? See this repo

Question: How to initiate a subscription from the client

One thing that's a bit unclear from the docs is how do I initiate a subscription from the client?

I see that client.networkInterface has a subscribe() function on it. Do I use that? If so, what are its arguments?

Is it possible to wrap a component in the graphql HOC like how one does with queries and mutations?

HOWTO: manage general purpose channels

While waiting for subscriptions support both here and in absinthe I though of building a custom pubsub mechanism to inform the clients when they should update. To do so i should be able to handle the websockets events in my application (for example join-room, new-chat-message, ...) I was wondering if it's possible to do so while using this library and how would you suggest doing it (or why not)

URI option required but not used when socket is provided

In the following code, the uri option is required to be passed in, even though its not used since the socket parameter is provided. I've used this to supply my own socket connection parameters.

createNetworkInterface({
    uri: '/this-does-not-matter',
    Socket: () => { return socket; }
  });

Handling connection lost

I am looking for a away to handle errors when the server loses the connection. I tried to use the applyAfterware, however it does not catch it since there is no response from the server. I always get the following error message in console:

failed: Error during WebSocket handshake: net::ERR_INVALID_HTTP_RESPONSE

What is the best way to catch that kind of error using apollo-phoenix-websocket?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.