@graffy/server 0.17.8-alpha.1 → 0.17.8-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs +25 -12
- package/index.mjs +25 -12
- package/package.json +4 -4
- package/types/httpServer.d.ts +12 -5
- package/types/wsServer.d.ts +12 -5
package/index.cjs
CHANGED
|
@@ -5,18 +5,23 @@ const common = require("@graffy/common");
|
|
|
5
5
|
const debug = require("debug");
|
|
6
6
|
const ws = require("ws");
|
|
7
7
|
const log$1 = debug("graffy:server:http");
|
|
8
|
-
function server$1(store, { auth } = {}) {
|
|
8
|
+
function server$1(store, { auth, allowedOptions = [] } = {}) {
|
|
9
9
|
if (!store) throw new Error("server.store_undef");
|
|
10
10
|
return async (req, res) => {
|
|
11
11
|
const parsed = url.parse(req.url, true);
|
|
12
12
|
const optParam = parsed.query.opts && String(parsed.query.opts);
|
|
13
|
-
const
|
|
13
|
+
const rawOptions = optParam && JSON.parse(decodeURIComponent(optParam));
|
|
14
|
+
const safeOptions = Object.fromEntries(
|
|
15
|
+
Object.entries(rawOptions || {}).filter(
|
|
16
|
+
([k]) => allowedOptions.includes(k)
|
|
17
|
+
)
|
|
18
|
+
);
|
|
14
19
|
if (req.method === "GET") {
|
|
15
20
|
try {
|
|
16
21
|
const qParam = parsed.query.q && String(parsed.query.q);
|
|
17
22
|
const query = qParam && common.unpack(JSON.parse(decodeURIComponent(qParam)));
|
|
18
23
|
if (req.headers.accept === "text/event-stream") {
|
|
19
|
-
if (auth && !await auth("watch", common.decodeQuery(query),
|
|
24
|
+
if (auth && !await auth("watch", common.decodeQuery(query), safeOptions)) {
|
|
20
25
|
const body = "unauthorized";
|
|
21
26
|
res.writeHead(401, {
|
|
22
27
|
"Content-Type": "text/plain",
|
|
@@ -35,7 +40,7 @@ function server$1(store, { auth } = {}) {
|
|
|
35
40
|
}, 29e3);
|
|
36
41
|
try {
|
|
37
42
|
const stream = store.call("watch", query, {
|
|
38
|
-
...
|
|
43
|
+
...safeOptions,
|
|
39
44
|
raw: true
|
|
40
45
|
});
|
|
41
46
|
for await (const value of stream) {
|
|
@@ -77,7 +82,7 @@ data: ${e.message}
|
|
|
77
82
|
if (auth && !await auth(
|
|
78
83
|
op,
|
|
79
84
|
(op === "write" ? common.decodeGraph : common.decodeQuery)(payload),
|
|
80
|
-
|
|
85
|
+
safeOptions
|
|
81
86
|
)) {
|
|
82
87
|
const body2 = "unauthorized";
|
|
83
88
|
res.writeHead(401, {
|
|
@@ -87,7 +92,7 @@ data: ${e.message}
|
|
|
87
92
|
res.end(body2);
|
|
88
93
|
return;
|
|
89
94
|
}
|
|
90
|
-
const value = await store.call(op, payload,
|
|
95
|
+
const value = await store.call(op, payload, safeOptions);
|
|
91
96
|
const body = JSON.stringify(common.pack(value));
|
|
92
97
|
res.writeHead(200, {
|
|
93
98
|
"Content-Type": "application/json",
|
|
@@ -116,14 +121,19 @@ data: ${e.message}
|
|
|
116
121
|
}
|
|
117
122
|
const log = debug("graffy:server:ws");
|
|
118
123
|
const PING_INTERVAL = 3e4;
|
|
119
|
-
function server(store, { auth } = {}) {
|
|
124
|
+
function server(store, { auth, allowedOptions = [] } = {}) {
|
|
120
125
|
if (!store) throw new Error("server.store_undef");
|
|
121
126
|
const wss = new ws.WebSocketServer({ noServer: true });
|
|
122
127
|
wss.on("connection", function connection(ws2) {
|
|
123
128
|
ws2.graffyStreams = {};
|
|
124
129
|
ws2.on("message", async function message(msg) {
|
|
125
130
|
try {
|
|
126
|
-
const [id, op, packedPayload,
|
|
131
|
+
const [id, op, packedPayload, rawOptions] = JSON.parse(msg);
|
|
132
|
+
const safeOptions = Object.fromEntries(
|
|
133
|
+
Object.entries(rawOptions || {}).filter(
|
|
134
|
+
([k]) => allowedOptions.includes(k)
|
|
135
|
+
)
|
|
136
|
+
);
|
|
127
137
|
const payload = common.unpack(packedPayload);
|
|
128
138
|
if (id === ":pong") {
|
|
129
139
|
ws2.pingPending = false;
|
|
@@ -131,7 +141,7 @@ function server(store, { auth } = {}) {
|
|
|
131
141
|
}
|
|
132
142
|
if (auth && op !== "unwatch") {
|
|
133
143
|
const decoded = op === "write" ? common.decodeGraph(payload) : common.decodeQuery(payload);
|
|
134
|
-
if (!await auth(op, decoded,
|
|
144
|
+
if (!await auth(op, decoded, safeOptions)) {
|
|
135
145
|
ws2.send(JSON.stringify([id, "unauthorized"]));
|
|
136
146
|
return;
|
|
137
147
|
}
|
|
@@ -140,7 +150,7 @@ function server(store, { auth } = {}) {
|
|
|
140
150
|
case "read":
|
|
141
151
|
case "write":
|
|
142
152
|
try {
|
|
143
|
-
const result = await store.call(op, payload,
|
|
153
|
+
const result = await store.call(op, payload, safeOptions);
|
|
144
154
|
ws2.send(JSON.stringify([id, null, common.pack(result)]));
|
|
145
155
|
} catch (e) {
|
|
146
156
|
log(`${op}error:${e.message} ${payload}`);
|
|
@@ -150,7 +160,7 @@ function server(store, { auth } = {}) {
|
|
|
150
160
|
case "watch":
|
|
151
161
|
try {
|
|
152
162
|
const stream = store.call("watch", payload, {
|
|
153
|
-
...
|
|
163
|
+
...safeOptions,
|
|
154
164
|
raw: true
|
|
155
165
|
});
|
|
156
166
|
ws2.graffyStreams[id] = stream;
|
|
@@ -182,7 +192,10 @@ function server(store, { auth } = {}) {
|
|
|
182
192
|
});
|
|
183
193
|
setInterval(function ping() {
|
|
184
194
|
wss.clients.forEach(function each(ws2) {
|
|
185
|
-
if (ws2.pingPending)
|
|
195
|
+
if (ws2.pingPending) {
|
|
196
|
+
ws2.terminate();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
186
199
|
ws2.pingPending = true;
|
|
187
200
|
ws2.send(JSON.stringify([":ping", Date.now()]));
|
|
188
201
|
});
|
package/index.mjs
CHANGED
|
@@ -3,18 +3,23 @@ import { unpack, decodeQuery, pack, decodeGraph } from "@graffy/common";
|
|
|
3
3
|
import debug from "debug";
|
|
4
4
|
import { WebSocketServer } from "ws";
|
|
5
5
|
const log$1 = debug("graffy:server:http");
|
|
6
|
-
function server$1(store, { auth } = {}) {
|
|
6
|
+
function server$1(store, { auth, allowedOptions = [] } = {}) {
|
|
7
7
|
if (!store) throw new Error("server.store_undef");
|
|
8
8
|
return async (req, res) => {
|
|
9
9
|
const parsed = url.parse(req.url, true);
|
|
10
10
|
const optParam = parsed.query.opts && String(parsed.query.opts);
|
|
11
|
-
const
|
|
11
|
+
const rawOptions = optParam && JSON.parse(decodeURIComponent(optParam));
|
|
12
|
+
const safeOptions = Object.fromEntries(
|
|
13
|
+
Object.entries(rawOptions || {}).filter(
|
|
14
|
+
([k]) => allowedOptions.includes(k)
|
|
15
|
+
)
|
|
16
|
+
);
|
|
12
17
|
if (req.method === "GET") {
|
|
13
18
|
try {
|
|
14
19
|
const qParam = parsed.query.q && String(parsed.query.q);
|
|
15
20
|
const query = qParam && unpack(JSON.parse(decodeURIComponent(qParam)));
|
|
16
21
|
if (req.headers.accept === "text/event-stream") {
|
|
17
|
-
if (auth && !await auth("watch", decodeQuery(query),
|
|
22
|
+
if (auth && !await auth("watch", decodeQuery(query), safeOptions)) {
|
|
18
23
|
const body = "unauthorized";
|
|
19
24
|
res.writeHead(401, {
|
|
20
25
|
"Content-Type": "text/plain",
|
|
@@ -33,7 +38,7 @@ function server$1(store, { auth } = {}) {
|
|
|
33
38
|
}, 29e3);
|
|
34
39
|
try {
|
|
35
40
|
const stream = store.call("watch", query, {
|
|
36
|
-
...
|
|
41
|
+
...safeOptions,
|
|
37
42
|
raw: true
|
|
38
43
|
});
|
|
39
44
|
for await (const value of stream) {
|
|
@@ -75,7 +80,7 @@ data: ${e.message}
|
|
|
75
80
|
if (auth && !await auth(
|
|
76
81
|
op,
|
|
77
82
|
(op === "write" ? decodeGraph : decodeQuery)(payload),
|
|
78
|
-
|
|
83
|
+
safeOptions
|
|
79
84
|
)) {
|
|
80
85
|
const body2 = "unauthorized";
|
|
81
86
|
res.writeHead(401, {
|
|
@@ -85,7 +90,7 @@ data: ${e.message}
|
|
|
85
90
|
res.end(body2);
|
|
86
91
|
return;
|
|
87
92
|
}
|
|
88
|
-
const value = await store.call(op, payload,
|
|
93
|
+
const value = await store.call(op, payload, safeOptions);
|
|
89
94
|
const body = JSON.stringify(pack(value));
|
|
90
95
|
res.writeHead(200, {
|
|
91
96
|
"Content-Type": "application/json",
|
|
@@ -114,14 +119,19 @@ data: ${e.message}
|
|
|
114
119
|
}
|
|
115
120
|
const log = debug("graffy:server:ws");
|
|
116
121
|
const PING_INTERVAL = 3e4;
|
|
117
|
-
function server(store, { auth } = {}) {
|
|
122
|
+
function server(store, { auth, allowedOptions = [] } = {}) {
|
|
118
123
|
if (!store) throw new Error("server.store_undef");
|
|
119
124
|
const wss = new WebSocketServer({ noServer: true });
|
|
120
125
|
wss.on("connection", function connection(ws) {
|
|
121
126
|
ws.graffyStreams = {};
|
|
122
127
|
ws.on("message", async function message(msg) {
|
|
123
128
|
try {
|
|
124
|
-
const [id, op, packedPayload,
|
|
129
|
+
const [id, op, packedPayload, rawOptions] = JSON.parse(msg);
|
|
130
|
+
const safeOptions = Object.fromEntries(
|
|
131
|
+
Object.entries(rawOptions || {}).filter(
|
|
132
|
+
([k]) => allowedOptions.includes(k)
|
|
133
|
+
)
|
|
134
|
+
);
|
|
125
135
|
const payload = unpack(packedPayload);
|
|
126
136
|
if (id === ":pong") {
|
|
127
137
|
ws.pingPending = false;
|
|
@@ -129,7 +139,7 @@ function server(store, { auth } = {}) {
|
|
|
129
139
|
}
|
|
130
140
|
if (auth && op !== "unwatch") {
|
|
131
141
|
const decoded = op === "write" ? decodeGraph(payload) : decodeQuery(payload);
|
|
132
|
-
if (!await auth(op, decoded,
|
|
142
|
+
if (!await auth(op, decoded, safeOptions)) {
|
|
133
143
|
ws.send(JSON.stringify([id, "unauthorized"]));
|
|
134
144
|
return;
|
|
135
145
|
}
|
|
@@ -138,7 +148,7 @@ function server(store, { auth } = {}) {
|
|
|
138
148
|
case "read":
|
|
139
149
|
case "write":
|
|
140
150
|
try {
|
|
141
|
-
const result = await store.call(op, payload,
|
|
151
|
+
const result = await store.call(op, payload, safeOptions);
|
|
142
152
|
ws.send(JSON.stringify([id, null, pack(result)]));
|
|
143
153
|
} catch (e) {
|
|
144
154
|
log(`${op}error:${e.message} ${payload}`);
|
|
@@ -148,7 +158,7 @@ function server(store, { auth } = {}) {
|
|
|
148
158
|
case "watch":
|
|
149
159
|
try {
|
|
150
160
|
const stream = store.call("watch", payload, {
|
|
151
|
-
...
|
|
161
|
+
...safeOptions,
|
|
152
162
|
raw: true
|
|
153
163
|
});
|
|
154
164
|
ws.graffyStreams[id] = stream;
|
|
@@ -180,7 +190,10 @@ function server(store, { auth } = {}) {
|
|
|
180
190
|
});
|
|
181
191
|
setInterval(function ping() {
|
|
182
192
|
wss.clients.forEach(function each(ws) {
|
|
183
|
-
if (ws.pingPending)
|
|
193
|
+
if (ws.pingPending) {
|
|
194
|
+
ws.terminate();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
184
197
|
ws.pingPending = true;
|
|
185
198
|
ws.send(JSON.stringify([":ping", Date.now()]));
|
|
186
199
|
});
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@graffy/server",
|
|
3
3
|
"description": "Node.js library for building an API for a Graffy store.",
|
|
4
4
|
"author": "aravind (https://github.com/aravindet)",
|
|
5
|
-
"version": "0.17.8-alpha.
|
|
5
|
+
"version": "0.17.8-alpha.3",
|
|
6
6
|
"main": "./index.cjs",
|
|
7
7
|
"exports": {
|
|
8
8
|
"import": "./index.mjs",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
"license": "Apache-2.0",
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@graffy/common": "0.17.8-alpha.
|
|
20
|
-
"debug": "^4.4.
|
|
21
|
-
"ws": "^8.
|
|
19
|
+
"@graffy/common": "0.17.8-alpha.3",
|
|
20
|
+
"debug": "^4.4.3",
|
|
21
|
+
"ws": "^8.19.0"
|
|
22
22
|
}
|
|
23
23
|
}
|
package/types/httpServer.d.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {import('@graffy/core').default} GraffyStore
|
|
3
3
|
* @param {GraffyStore} store
|
|
4
|
-
* @param {
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* @param {object} [options]
|
|
5
|
+
* @param {(operation: string, payload: any, options: any) => Promise<boolean>} [options.auth]
|
|
6
|
+
* Optional callback to authorize each request. Receives the operation name,
|
|
7
|
+
* decoded payload, and the filtered options. Return `true` to allow, `false`
|
|
8
|
+
* (or a rejected promise) to reject with 401.
|
|
9
|
+
* @param {string[]} [options.allowedOptions]
|
|
10
|
+
* Allowlist of option keys that clients are permitted to pass through to
|
|
11
|
+
* `store.call` and the `auth` callback. Any key not in this list is stripped
|
|
12
|
+
* from the client-supplied options before use. Defaults to `[]` (strip all).
|
|
7
13
|
* @returns
|
|
8
14
|
*/
|
|
9
|
-
export default function server(store: GraffyStore, { auth }?: {
|
|
15
|
+
export default function server(store: GraffyStore, { auth, allowedOptions }?: {
|
|
10
16
|
auth?: (operation: string, payload: any, options: any) => Promise<boolean>;
|
|
11
|
-
|
|
17
|
+
allowedOptions?: string[];
|
|
18
|
+
}): (req: any, res: any) => Promise<void>;
|
|
12
19
|
export type GraffyStore = import("@graffy/core").default;
|
package/types/wsServer.d.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {import('@graffy/core').default} GraffyStore
|
|
3
3
|
* @param {GraffyStore} store
|
|
4
|
-
* @param {
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* @param {object} [options]
|
|
5
|
+
* @param {(operation: string, payload: any, options: any) => Promise<boolean>} [options.auth]
|
|
6
|
+
* Optional callback to authorize each request. Receives the operation name,
|
|
7
|
+
* decoded payload, and the filtered options. Return `true` to allow, `false`
|
|
8
|
+
* (or a rejected promise) to reject with an error response.
|
|
9
|
+
* @param {string[]} [options.allowedOptions]
|
|
10
|
+
* Allowlist of option keys that clients are permitted to pass through to
|
|
11
|
+
* `store.call` and the `auth` callback. Any key not in this list is stripped
|
|
12
|
+
* from the client-supplied options before use. Defaults to `[]` (strip all).
|
|
7
13
|
* @returns
|
|
8
14
|
*/
|
|
9
|
-
export default function server(store: GraffyStore, { auth }?: {
|
|
15
|
+
export default function server(store: GraffyStore, { auth, allowedOptions }?: {
|
|
10
16
|
auth?: (operation: string, payload: any, options: any) => Promise<boolean>;
|
|
11
|
-
|
|
17
|
+
allowedOptions?: string[];
|
|
18
|
+
}): (request: any, socket: any, head: any) => Promise<void>;
|
|
12
19
|
export type GraffyStore = import("@graffy/core").default;
|