Last active
July 10, 2018 15:26
-
-
Save ForsakenHarmony/e2b29f9164618458a23b3671dab20dcf to your computer and use it in GitHub Desktop.
Graphql over websockets
This file contains 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 { watch as watchOnline } from 'is-offline'; | |
import Sockette from 'sockette'; | |
const msg_types = { | |
GQL_CONNECTION_INIT: 'connection_init', // Client -> Server | |
GQL_CONNECTION_ACK: 'connection_ack', // Server -> Client | |
GQL_CONNECTION_ERROR: 'connection_error', // Server -> Client | |
GQL_CONNECTION_TERMINATE: 'connection_terminate', // Client -> Server | |
GQL_START: 'start', // Client -> Server | |
GQL_DATA: 'data', // Server -> Client | |
GQL_ERROR: 'error', // Server -> Client | |
GQL_COMPLETE: 'complete', // Server -> Client | |
GQL_STOP: 'stop', // Client -> Server | |
}; | |
const url = process.env.WS_URL; | |
export default function subscriber(token) { | |
const subscriptions = {}; | |
let open = false; | |
let reconnecting = false; | |
let lastid = 0; | |
const genId = () => String(lastid++); | |
let buffer = []; | |
const socket = new Sockette(url, { | |
protocols: 'graphql-ws', | |
timeout: 5e3, | |
maxAttempts: 10, | |
onopen: () => { | |
open = true; | |
const message = { | |
type: msg_types.GQL_CONNECTION_INIT, | |
payload: { | |
Authorization: `Bearer ${token}`, | |
}, | |
}; | |
socket.json(message); | |
buffer.forEach(m => socket.json(m)); | |
if (reconnecting) { | |
Object.values(subscriptions).forEach(s => socket.json(s.message)); | |
} | |
reconnecting = false; | |
}, | |
onmessage: event => { | |
const data = JSON.parse(event.data); | |
const sub = subscriptions[data.id]; | |
switch (data.type) { | |
case msg_types.GQL_CONNECTION_ACK: { | |
console.log('init_success, the handshake is complete'); | |
break; | |
} | |
case msg_types.GQL_CONNECTION_ERROR: { | |
console.error('init_fail returned from WebSocket server'); | |
break; | |
} | |
case msg_types.GQL_DATA: { | |
sub && sub.handler(data.payload.data); | |
break; | |
} | |
case msg_types.GQL_COMPLETE: { | |
delete subscriptions[data.id]; | |
break; | |
} | |
case msg_types.GQL_ERROR: { | |
sub && sub.handler(void 0, data.payload.errors); | |
break; | |
} | |
} | |
}, | |
onreconnect: () => { | |
reconnecting = true; | |
}, | |
onmaximum: e => { | |
console.log('Stop Attempting!', e); | |
}, | |
onclose: () => { | |
open = false; | |
}, | |
onerror: e => { | |
console.error('WS Error:', e); | |
}, | |
}); | |
watchOnline(online => { | |
if (!online && open) { | |
socket.close(1000, 'closed'); | |
} else if (online) { | |
socket.reconnect(); | |
} | |
}); | |
function push_msg(msg) { | |
if (!open) return buffer.push(msg); | |
socket.json(msg); | |
} | |
function request(query, vars, handler) { | |
const id = genId(); | |
const message = { | |
id, | |
type: msg_types.GQL_START, | |
payload: query(vars), | |
}; | |
subscriptions[id] = { | |
message, | |
handler, | |
}; | |
push_msg(message); | |
return () => { | |
const message = { | |
id, | |
type: msg_types.GQL_STOP, | |
}; | |
delete subscriptions[id]; | |
push_msg(message); | |
}; | |
} | |
return { | |
query(query, vars) { | |
return new Promise((res, rej) => { | |
request(query, vars, (d, e) => { | |
if (e) rej(e); | |
if (d) res(d); | |
}); | |
}); | |
}, | |
subscribe(query, vars, handler) { | |
if (typeof handler !== 'function') { | |
throw new Error('handler has to be a function'); | |
} | |
return request(query, vars, handler); | |
}, | |
close() { | |
Object.values(subscriptions).forEach(({ message: { id } }) => { | |
const message = { | |
id, | |
type: msg_types.GQL_STOP, | |
}; | |
push_msg(message); | |
}); | |
push_msg({ | |
type: msg_types.GQL_CONNECTION_TERMINATE, | |
}); | |
// socket.close(1000, 'closed'); | |
}, | |
}; | |
} | |
const getOpname = /(query|mutation|subsciption) ?([\w\d-_]+)? ?\(.*?\)? \{/; | |
export function gql(str) { | |
str = Array.isArray(str) ? str.join('') : str; | |
const name = getOpname.exec(str); | |
return function(variables) { | |
const data = { query: str }; | |
if (variables) data.variables = variables; | |
if (name && name.length) { | |
const operationName = name[2]; | |
if (operationName) data.operationName = name[2]; | |
} | |
return data; | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment