Last active
July 22, 2016 19:06
-
-
Save trbngr/c4ae216d7b4c1f3f6ded08e23084203c to your computer and use it in GitHub Desktop.
My root component for a relay-subscriptions application.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React, {Component, PropTypes} from 'react'; | |
| import {SubscriptionProvider} from 'relay-subscriptions' | |
| import uuid from 'uuid' | |
| const outgoing = { | |
| subscribe: 'subscribe', | |
| ping: 'ping' | |
| }; | |
| const incoming = { | |
| accepted: 'accepted', | |
| rejected: 'rejected', | |
| payload: 'payload', | |
| pong: 'pong' | |
| }; | |
| const emptyRequest = { | |
| clientId: '', | |
| getQueryString: () => '', | |
| getVariables: () => ({}), | |
| dispose: () => { | |
| }, | |
| onNext: () => { | |
| }, | |
| onError: () => { | |
| }, | |
| shouldProcessPayload: () => false | |
| }; | |
| class SubscriptionRoot extends Component { | |
| constructor(props, context) { | |
| super(props, context); | |
| this.clientId = uuid(); | |
| this.state = { connected: false, reconnect: true }; | |
| this.requestStore = new RequestStore(); | |
| this.pingIntervalId = undefined; | |
| this.subscribe = ::this.subscribe; | |
| } | |
| static log(...args) { | |
| console.log('[subscription root]:', ...args); | |
| } | |
| getChildContext() { | |
| return { | |
| auth: this.props.auth | |
| }; | |
| } | |
| componentDidMount() { | |
| SubscriptionRoot.log('mounting'); | |
| this.connect(); | |
| } | |
| connect() { | |
| this.socket = new WebSocket(GRAPHQL_WS); | |
| this.socket.onmessage = ({ data }) => { | |
| const payload = JSON.parse(data); | |
| if (!payload) | |
| return; | |
| this.handlePayload(payload) | |
| }; | |
| this.socket.onopen = () => { | |
| this.pingIntervalId = setInterval(() => this.send(outgoing.ping), GRAPHQL_WS_PING_INTERVAL); | |
| this.setState({ connected: true }, () => { | |
| SubscriptionRoot.log('connected to graphql subscriptions.'); | |
| }); | |
| }; | |
| this.socket.onclose = () => { | |
| clearInterval(this.pingIntervalId); | |
| this.setState({ connected: false }, () => { | |
| SubscriptionRoot.log('disconnected from graphql subscriptions.'); | |
| var reconnect = this.state.reconnect; | |
| if (reconnect) { | |
| SubscriptionRoot.log('trying to reconnect'); | |
| this.connect(); | |
| } else { | |
| this.requestStore.dispose(); | |
| } | |
| }); | |
| }; | |
| this.socket.onerror = error => { | |
| console.warn(error); | |
| this.requestStore.raiseSocketError(error); | |
| }; | |
| } | |
| componentWillUnmount() { | |
| this.setState({ reconnect: false }, () => this.socket.close(0, "unmounting")) | |
| } | |
| send(type, request = emptyRequest) { | |
| if (!this.state.connected) { | |
| console.warn(`Not connected. We shouldn't see this. Maybe we can add some reconnect strategy?`); | |
| return; | |
| } | |
| const { auth } = this.props; | |
| this.socket.send(JSON.stringify({ | |
| type, | |
| clientId: this.clientId, | |
| queryId: this.requestStore.add(request), | |
| query: request.getQueryString(), | |
| variables: JSON.stringify(request.getVariables() || {}), | |
| token: auth.getToken() | |
| })); | |
| } | |
| handlePayload(payload) { | |
| switch (payload.type) { | |
| case incoming.accepted: | |
| SubscriptionRoot.log(`${this.requestStore.getDebugName(payload.queryId)} may live.`); | |
| break; | |
| case incoming.rejected: | |
| SubscriptionRoot.log(`${this.requestStore.getDebugName(payload.queryId)} has been reject. reason: '${payload.message}' removing subscription.`); | |
| this.requestStore.remove(payload.queryId); | |
| break; | |
| case incoming.payload: | |
| const request = this.requestStore.get(payload.queryId); | |
| if (!request) { | |
| SubscriptionRoot.log('No request found for subscription payload. Discarding', payload); | |
| return; | |
| } | |
| SubscriptionRoot.log(`got payload for '${request.getDebugName()}'. dispatching...`, payload.message.data); | |
| if (payload.clientId !== this.clientId) { | |
| if (!!request.shouldProcessPayload(payload.message.data)) { | |
| request.onNext(payload.message.data) | |
| } | |
| } | |
| break; | |
| case incoming.pong: | |
| default: | |
| SubscriptionRoot.log('unsupported payload', payload); | |
| break; | |
| } | |
| } | |
| subscribe(request, options = { | |
| shouldProcessPayload: () => { | |
| } | |
| }) { | |
| request.setDisposable({ | |
| dispose: () => { | |
| SubscriptionRoot.log('dispose', request); | |
| } | |
| }); | |
| this.send(outgoing.subscribe, Object.assign(request, options)); | |
| } | |
| render() { | |
| return ( | |
| <SubscriptionProvider environment={this.props.environment} subscribe={this.subscribe}> | |
| {this.props.children} | |
| </SubscriptionProvider> | |
| ); | |
| } | |
| } | |
| class RequestStore { | |
| constructor() { | |
| this.store = {}; | |
| } | |
| add(request) { | |
| const id = uuid(); | |
| this.store = Object.assign(this.store, { [id]: request }); | |
| return id; | |
| } | |
| remove(id) { | |
| this.store = Object.keys(this.store) | |
| .filter(key => key !== id) | |
| .map(key => ({ key: this.store[key] })); | |
| } | |
| dispose() { | |
| Object.keys(this.store).forEach(key => this.store[key].dispose()); | |
| } | |
| get(id) { | |
| return this.store[id]; | |
| } | |
| getDebugName(id) { | |
| const request = this.store[id]; | |
| return request ? request.getDebugName() : ''; | |
| } | |
| raiseSocketError(e) { | |
| Object.keys(this.store).forEach(key => this.store[key].onError(e)); | |
| } | |
| } | |
| SubscriptionRoot.propTypes = { | |
| auth: PropTypes.object.isRequired, | |
| environment: PropTypes.object.isRequired, | |
| children: PropTypes.element | |
| }; | |
| SubscriptionRoot.childContextTypes = { | |
| auth: PropTypes.object.isRequired | |
| }; | |
| SubscriptionRoot.defaultProps = {}; | |
| export default SubscriptionRoot; |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
relay-subscriptions