-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathindex.js
132 lines (117 loc) · 3.68 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import {
handleProtocols,
makeServer,
MessageType,
stringifyMessage,
} from 'graphql-ws';
import { buildSchema } from 'graphql';
import graphiql from './graphiql';
// construct a schema, using GraphQL schema language
const schema = buildSchema(`
type Query {
hello: String
}
type Subscription {
greetings: String
}
`);
// the roots provide resolvers for each GraphQL operation
const roots = {
query: {
hello: () => 'Hello World!',
},
subscription: {
greetings: async function* sayHiIn5Languages() {
for (const hi of ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo']) {
yield { greetings: hi };
}
},
},
};
// use cloudflare server websocket for graphql-ws
function useWebsocket(socket, request, protocol) {
// configure and make server
const server = makeServer({ schema, roots });
// accept socket to begin
socket.accept();
// subprotocol pinger because WS level ping/pongs are not be available
let pinger, pongWait;
function ping() {
if (socket.readyState === socket.OPEN) {
// send the subprotocol level ping message
socket.send(stringifyMessage({ type: MessageType.Ping }));
// wait for the pong for 6 seconds and then terminate
pongWait = setTimeout(() => {
clearInterval(pinger);
socket.close();
}, 6000);
}
}
// ping the client on an interval every 12 seconds
pinger = setInterval(() => ping(), 12000);
// use the server
const closed = server.opened(
{
protocol, // will be validated
send: (data) => socket.send(data),
close: (code, reason) => socket.close(code, reason),
onMessage: (cb) =>
socket.addEventListener('message', async (event) => {
try {
// wait for the the operation to complete
// - if init message, waits for connect
// - if query/mutation, waits for result
// - if subscription, waits for complete
await cb(event.data);
} catch (err) {
// all errors that could be thrown during the
// execution of operations will be caught here
socket.close(1011, err.message);
}
}),
// pong received, clear termination timeout
onPong: () => clearTimeout(pongWait),
},
// pass values to the `extra` field in the context
{ socket, request },
);
// notify server that the socket closed and stop the pinger
socket.addEventListener('close', (code, reason) => {
clearTimeout(pongWait);
clearInterval(pinger);
closed(code, reason);
});
}
function handleRequest(request) {
const url = new URL(request.url);
switch (url.pathname) {
case '/':
return graphiql();
case '/graphql':
const upgradeHeader = request.headers.get('Upgrade');
if (upgradeHeader !== 'websocket') {
return new Response('Expected websocket', { status: 400 });
}
const [client, server] = Object.values(new WebSocketPair());
const protocol = handleProtocols(
request.headers.get('Sec-WebSocket-Protocol'),
);
useWebsocket(server, request, subprotocol);
return new Response(null, {
status: 101,
webSocket: client,
headers: protocol
? {
// As per the WS spec, if the server does not accept any subprotocol - it should omit this header.
// Beware that doing so will have Chrome abruptly close the WebSocket connection with a 1006 code.
'Sec-WebSocket-Protocol': protocol,
}
: {},
});
default:
return new Response('Not found', { status: 404 });
}
}
addEventListener('fetch', (event) => {
event.respondWith(handleRequest(event.request));
});