@graffy/client 0.15.8-alpha.3 → 0.15.9
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 +308 -1
- package/index.mjs +303 -1
- package/package.json +4 -4
- package/types/Socket.d.ts +8 -0
- package/types/httpClient.d.ts +6 -0
- package/types/index.d.ts +1 -0
- package/types/wsClient.d.ts +6 -0
- package/index.d.ts +0 -29
package/index.cjs
CHANGED
|
@@ -1 +1,308 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
|
+
var __publicField = (obj, key, value) => {
|
|
5
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
6
|
+
return value;
|
|
7
|
+
};
|
|
8
|
+
var common = require("@graffy/common");
|
|
9
|
+
var stream = require("@graffy/stream");
|
|
10
|
+
var debug = require("debug");
|
|
11
|
+
function _interopDefaultLegacy(e) {
|
|
12
|
+
return e && typeof e === "object" && "default" in e ? e : { "default": e };
|
|
13
|
+
}
|
|
14
|
+
var debug__default = /* @__PURE__ */ _interopDefaultLegacy(debug);
|
|
15
|
+
function getOptionsParam(options) {
|
|
16
|
+
if (!options)
|
|
17
|
+
return "";
|
|
18
|
+
return encodeURIComponent(common.serialize(options));
|
|
19
|
+
}
|
|
20
|
+
const aggregateQueries = {};
|
|
21
|
+
class AggregateQuery {
|
|
22
|
+
constructor(url) {
|
|
23
|
+
__publicField(this, "combinedQuery", []);
|
|
24
|
+
__publicField(this, "readers", []);
|
|
25
|
+
__publicField(this, "timer", null);
|
|
26
|
+
this.url = url;
|
|
27
|
+
}
|
|
28
|
+
add(query) {
|
|
29
|
+
common.add(this.combinedQuery, query);
|
|
30
|
+
if (this.timer)
|
|
31
|
+
clearTimeout(this.timer);
|
|
32
|
+
this.timer = setTimeout(() => this.doFetch(), 0);
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
this.readers.push({ query, resolve, reject });
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async doFetch() {
|
|
38
|
+
delete aggregateQueries[this.url];
|
|
39
|
+
const response = await fetch(this.url, {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: { "Content-Type": "application/json" },
|
|
42
|
+
body: common.serialize(this.combinedQuery)
|
|
43
|
+
});
|
|
44
|
+
if (response.status !== 200) {
|
|
45
|
+
const message = await response.text();
|
|
46
|
+
const err = new Error(message);
|
|
47
|
+
for (const reader of this.readers) {
|
|
48
|
+
reader.reject(err);
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const data = await response.json();
|
|
53
|
+
for (const reader of this.readers) {
|
|
54
|
+
reader.resolve(data);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function makeQuery(url, query) {
|
|
59
|
+
if (!aggregateQueries[url])
|
|
60
|
+
aggregateQueries[url] = new AggregateQuery(url);
|
|
61
|
+
return aggregateQueries[url].add(query);
|
|
62
|
+
}
|
|
63
|
+
const httpClient = (baseUrl, { getOptions = async () => {
|
|
64
|
+
}, watch, connInfoPath = "connection" } = {}) => (store) => {
|
|
65
|
+
store.onWrite(connInfoPath, ({ url }) => {
|
|
66
|
+
baseUrl = url;
|
|
67
|
+
return { url };
|
|
68
|
+
});
|
|
69
|
+
store.onRead(connInfoPath, () => ({ url: baseUrl }));
|
|
70
|
+
store.on("read", async (query, options) => {
|
|
71
|
+
if (!fetch)
|
|
72
|
+
throw Error("client.fetch.unavailable");
|
|
73
|
+
const optionsParam = getOptionsParam(await getOptions("read", options));
|
|
74
|
+
const url = `${baseUrl}?opts=${optionsParam}&op=read`;
|
|
75
|
+
return makeQuery(url, query);
|
|
76
|
+
});
|
|
77
|
+
store.on("watch", async function* (query, options) {
|
|
78
|
+
if (watch === "none")
|
|
79
|
+
throw Error("client.no_watch");
|
|
80
|
+
if (watch === "hang") {
|
|
81
|
+
yield* stream.makeStream((push) => {
|
|
82
|
+
push(void 0);
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!EventSource)
|
|
87
|
+
throw Error("client.sse.unavailable");
|
|
88
|
+
const optionsParam = getOptionsParam(await getOptions("watch", options));
|
|
89
|
+
const url = `${baseUrl}?q=${common.encodeUrl(query)}&opts=${optionsParam}`;
|
|
90
|
+
const source = new EventSource(url);
|
|
91
|
+
yield* stream.makeStream((push, end) => {
|
|
92
|
+
source.onmessage = ({ data }) => {
|
|
93
|
+
push(common.deserialize(data));
|
|
94
|
+
};
|
|
95
|
+
source.onerror = (e) => {
|
|
96
|
+
end(Error("client.sse.transport: " + e.message));
|
|
97
|
+
};
|
|
98
|
+
source.addEventListener("graffyerror", (e) => {
|
|
99
|
+
end(Error("server." + e.data));
|
|
100
|
+
});
|
|
101
|
+
return () => {
|
|
102
|
+
source.close();
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
store.on("write", async (change, options) => {
|
|
107
|
+
if (!fetch)
|
|
108
|
+
throw Error("client.fetch.unavailable");
|
|
109
|
+
const optionsParam = getOptionsParam(await getOptions("write", options));
|
|
110
|
+
const url = `${baseUrl}?opts=${optionsParam}&op=write`;
|
|
111
|
+
return fetch(url, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: { "Content-Type": "application/json" },
|
|
114
|
+
body: common.serialize(change)
|
|
115
|
+
}).then((res) => {
|
|
116
|
+
if (res.status === 200)
|
|
117
|
+
return res.json();
|
|
118
|
+
return res.text().then((message) => {
|
|
119
|
+
throw Error("server." + message);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
const log = debug__default["default"]("graffy:client:socket");
|
|
125
|
+
log.log = console.log.bind(console);
|
|
126
|
+
const MIN_DELAY = 1e3;
|
|
127
|
+
const MAX_DELAY = 3e5;
|
|
128
|
+
const DELAY_GROWTH = 1.5;
|
|
129
|
+
const INTERVAL = 2e3;
|
|
130
|
+
const PING_TIMEOUT = 4e4;
|
|
131
|
+
const RESET_TIMEOUT = 1e4;
|
|
132
|
+
function Socket(url, { onUnhandled, onStatusChange } = {}) {
|
|
133
|
+
const handlers = {};
|
|
134
|
+
const buffer = [];
|
|
135
|
+
let isOpen = false;
|
|
136
|
+
let isConnecting = false;
|
|
137
|
+
let socket;
|
|
138
|
+
let lastAlive;
|
|
139
|
+
let lastAttempt;
|
|
140
|
+
let attempts = 0;
|
|
141
|
+
let connectTimer;
|
|
142
|
+
let aliveTimer;
|
|
143
|
+
function start(params, callback) {
|
|
144
|
+
const id = common.makeId();
|
|
145
|
+
const request = [id, ...params];
|
|
146
|
+
handlers[id] = { request, callback };
|
|
147
|
+
if (isAlive())
|
|
148
|
+
send(request);
|
|
149
|
+
return id;
|
|
150
|
+
}
|
|
151
|
+
function stop(id, params) {
|
|
152
|
+
delete handlers[id];
|
|
153
|
+
if (params)
|
|
154
|
+
send([id, ...params]);
|
|
155
|
+
}
|
|
156
|
+
function connect() {
|
|
157
|
+
log("Trying to connect to", url);
|
|
158
|
+
isOpen = false;
|
|
159
|
+
isConnecting = true;
|
|
160
|
+
lastAttempt = Date.now();
|
|
161
|
+
attempts++;
|
|
162
|
+
socket = new WebSocket(url);
|
|
163
|
+
socket.onmessage = received;
|
|
164
|
+
socket.onerror = closed;
|
|
165
|
+
socket.onclose = closed;
|
|
166
|
+
socket.onopen = opened;
|
|
167
|
+
}
|
|
168
|
+
function received(event) {
|
|
169
|
+
const [id, ...data] = common.deserialize(event.data);
|
|
170
|
+
setAlive();
|
|
171
|
+
if (id === ":ping") {
|
|
172
|
+
send([":pong"]);
|
|
173
|
+
} else if (handlers[id]) {
|
|
174
|
+
handlers[id].callback(...data);
|
|
175
|
+
} else {
|
|
176
|
+
onUnhandled && onUnhandled(id, ...data);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function closed(_event) {
|
|
180
|
+
log("Closed");
|
|
181
|
+
if (isOpen && onStatusChange)
|
|
182
|
+
onStatusChange(false);
|
|
183
|
+
const wasOpen = isOpen;
|
|
184
|
+
isOpen = false;
|
|
185
|
+
isConnecting = false;
|
|
186
|
+
lastAttempt = Date.now();
|
|
187
|
+
if (wasOpen && !attempts) {
|
|
188
|
+
connect();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
maybeConnect();
|
|
192
|
+
}
|
|
193
|
+
function maybeConnect() {
|
|
194
|
+
const connDelay = lastAttempt + Math.min(MAX_DELAY, MIN_DELAY * Math.pow(DELAY_GROWTH, attempts)) - Date.now();
|
|
195
|
+
log("Will reconnect in", connDelay, "ms");
|
|
196
|
+
if (connDelay <= 0) {
|
|
197
|
+
connect();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
clearTimeout(connectTimer);
|
|
201
|
+
connectTimer = setTimeout(connect, connDelay);
|
|
202
|
+
}
|
|
203
|
+
function opened() {
|
|
204
|
+
log("Connected", buffer.length, Object.keys(handlers).length);
|
|
205
|
+
isOpen = true;
|
|
206
|
+
isConnecting = false;
|
|
207
|
+
lastAttempt = Date.now();
|
|
208
|
+
setAlive();
|
|
209
|
+
if (onStatusChange)
|
|
210
|
+
onStatusChange(true);
|
|
211
|
+
for (const id in handlers)
|
|
212
|
+
send(handlers[id].request);
|
|
213
|
+
while (buffer.length)
|
|
214
|
+
send(buffer.shift());
|
|
215
|
+
}
|
|
216
|
+
function setAlive() {
|
|
217
|
+
lastAlive = Date.now();
|
|
218
|
+
log("Set alive", lastAlive - lastAttempt);
|
|
219
|
+
if (lastAlive - lastAttempt > RESET_TIMEOUT)
|
|
220
|
+
attempts = 0;
|
|
221
|
+
}
|
|
222
|
+
function isAlive() {
|
|
223
|
+
log("Liveness check", isOpen ? "open" : "closed", Date.now() - lastAlive);
|
|
224
|
+
clearTimeout(aliveTimer);
|
|
225
|
+
aliveTimer = setTimeout(isAlive, INTERVAL);
|
|
226
|
+
if (!isOpen) {
|
|
227
|
+
if (!isConnecting)
|
|
228
|
+
maybeConnect();
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
if (Date.now() - lastAlive < PING_TIMEOUT)
|
|
232
|
+
return true;
|
|
233
|
+
socket.close();
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
function send(req) {
|
|
237
|
+
if (isAlive()) {
|
|
238
|
+
socket.send(common.serialize(req));
|
|
239
|
+
} else {
|
|
240
|
+
buffer.push(req);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
connect();
|
|
244
|
+
aliveTimer = setTimeout(isAlive, INTERVAL);
|
|
245
|
+
return {
|
|
246
|
+
start,
|
|
247
|
+
stop,
|
|
248
|
+
isAlive
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
const wsClient = (url, { getOptions = () => {
|
|
252
|
+
}, watch, connInfoPath = "connection" } = {}) => (store) => {
|
|
253
|
+
if (!WebSocket)
|
|
254
|
+
throw Error("client.websocket.unavailable");
|
|
255
|
+
const socket = new Socket(url, { onUnhandled, onStatusChange });
|
|
256
|
+
let status = false;
|
|
257
|
+
const statusWatcher = common.makeWatcher();
|
|
258
|
+
function onUnhandled(id) {
|
|
259
|
+
socket.stop(id, ["unwatch"]);
|
|
260
|
+
}
|
|
261
|
+
function onStatusChange(newStatus) {
|
|
262
|
+
status = newStatus;
|
|
263
|
+
statusWatcher.write({ status });
|
|
264
|
+
}
|
|
265
|
+
function once(op, payload, options) {
|
|
266
|
+
return new Promise((resolve, reject) => {
|
|
267
|
+
const id = socket.start([op, payload, getOptions(op, options) || {}], (error, result) => {
|
|
268
|
+
socket.stop(id);
|
|
269
|
+
error ? reject(Error("server." + error)) : resolve(result);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
store.onWrite(connInfoPath, () => {
|
|
274
|
+
status = socket.isAlive();
|
|
275
|
+
return { status };
|
|
276
|
+
});
|
|
277
|
+
store.onRead(connInfoPath, () => ({ status }));
|
|
278
|
+
store.onWatch(connInfoPath, () => statusWatcher.watch({ status }));
|
|
279
|
+
store.on("read", (query, options) => once("read", query, options));
|
|
280
|
+
store.on("write", (change, options) => once("write", change, options));
|
|
281
|
+
store.on("watch", (query, options) => {
|
|
282
|
+
if (watch === "none")
|
|
283
|
+
throw Error("client.no_watch");
|
|
284
|
+
const op = "watch";
|
|
285
|
+
return stream.makeStream((push, end) => {
|
|
286
|
+
const id = socket.start([op, query, getOptions(op, options) || {}], (error, result) => {
|
|
287
|
+
if (error) {
|
|
288
|
+
socket.stop(id);
|
|
289
|
+
end(Error("server." + error));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
push(result);
|
|
293
|
+
});
|
|
294
|
+
return () => {
|
|
295
|
+
socket.stop(id, ["unwatch"]);
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
};
|
|
300
|
+
const WSRE = /^wss?:\/\//;
|
|
301
|
+
function GraffyClient(baseUrl, options) {
|
|
302
|
+
if (WSRE.test(baseUrl)) {
|
|
303
|
+
return wsClient(baseUrl, options);
|
|
304
|
+
} else {
|
|
305
|
+
return httpClient(baseUrl, options);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
module.exports = GraffyClient;
|
package/index.mjs
CHANGED
|
@@ -1 +1,303 @@
|
|
|
1
|
-
var
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => {
|
|
4
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
|
+
return value;
|
|
6
|
+
};
|
|
7
|
+
import { encodeUrl, deserialize, serialize, add, makeId, makeWatcher } from "@graffy/common";
|
|
8
|
+
import { makeStream } from "@graffy/stream";
|
|
9
|
+
import debug from "debug";
|
|
10
|
+
function getOptionsParam(options) {
|
|
11
|
+
if (!options)
|
|
12
|
+
return "";
|
|
13
|
+
return encodeURIComponent(serialize(options));
|
|
14
|
+
}
|
|
15
|
+
const aggregateQueries = {};
|
|
16
|
+
class AggregateQuery {
|
|
17
|
+
constructor(url) {
|
|
18
|
+
__publicField(this, "combinedQuery", []);
|
|
19
|
+
__publicField(this, "readers", []);
|
|
20
|
+
__publicField(this, "timer", null);
|
|
21
|
+
this.url = url;
|
|
22
|
+
}
|
|
23
|
+
add(query) {
|
|
24
|
+
add(this.combinedQuery, query);
|
|
25
|
+
if (this.timer)
|
|
26
|
+
clearTimeout(this.timer);
|
|
27
|
+
this.timer = setTimeout(() => this.doFetch(), 0);
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
this.readers.push({ query, resolve, reject });
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async doFetch() {
|
|
33
|
+
delete aggregateQueries[this.url];
|
|
34
|
+
const response = await fetch(this.url, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: { "Content-Type": "application/json" },
|
|
37
|
+
body: serialize(this.combinedQuery)
|
|
38
|
+
});
|
|
39
|
+
if (response.status !== 200) {
|
|
40
|
+
const message = await response.text();
|
|
41
|
+
const err = new Error(message);
|
|
42
|
+
for (const reader of this.readers) {
|
|
43
|
+
reader.reject(err);
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const data = await response.json();
|
|
48
|
+
for (const reader of this.readers) {
|
|
49
|
+
reader.resolve(data);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function makeQuery(url, query) {
|
|
54
|
+
if (!aggregateQueries[url])
|
|
55
|
+
aggregateQueries[url] = new AggregateQuery(url);
|
|
56
|
+
return aggregateQueries[url].add(query);
|
|
57
|
+
}
|
|
58
|
+
const httpClient = (baseUrl, { getOptions = async () => {
|
|
59
|
+
}, watch, connInfoPath = "connection" } = {}) => (store) => {
|
|
60
|
+
store.onWrite(connInfoPath, ({ url }) => {
|
|
61
|
+
baseUrl = url;
|
|
62
|
+
return { url };
|
|
63
|
+
});
|
|
64
|
+
store.onRead(connInfoPath, () => ({ url: baseUrl }));
|
|
65
|
+
store.on("read", async (query, options) => {
|
|
66
|
+
if (!fetch)
|
|
67
|
+
throw Error("client.fetch.unavailable");
|
|
68
|
+
const optionsParam = getOptionsParam(await getOptions("read", options));
|
|
69
|
+
const url = `${baseUrl}?opts=${optionsParam}&op=read`;
|
|
70
|
+
return makeQuery(url, query);
|
|
71
|
+
});
|
|
72
|
+
store.on("watch", async function* (query, options) {
|
|
73
|
+
if (watch === "none")
|
|
74
|
+
throw Error("client.no_watch");
|
|
75
|
+
if (watch === "hang") {
|
|
76
|
+
yield* makeStream((push) => {
|
|
77
|
+
push(void 0);
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!EventSource)
|
|
82
|
+
throw Error("client.sse.unavailable");
|
|
83
|
+
const optionsParam = getOptionsParam(await getOptions("watch", options));
|
|
84
|
+
const url = `${baseUrl}?q=${encodeUrl(query)}&opts=${optionsParam}`;
|
|
85
|
+
const source = new EventSource(url);
|
|
86
|
+
yield* makeStream((push, end) => {
|
|
87
|
+
source.onmessage = ({ data }) => {
|
|
88
|
+
push(deserialize(data));
|
|
89
|
+
};
|
|
90
|
+
source.onerror = (e) => {
|
|
91
|
+
end(Error("client.sse.transport: " + e.message));
|
|
92
|
+
};
|
|
93
|
+
source.addEventListener("graffyerror", (e) => {
|
|
94
|
+
end(Error("server." + e.data));
|
|
95
|
+
});
|
|
96
|
+
return () => {
|
|
97
|
+
source.close();
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
store.on("write", async (change, options) => {
|
|
102
|
+
if (!fetch)
|
|
103
|
+
throw Error("client.fetch.unavailable");
|
|
104
|
+
const optionsParam = getOptionsParam(await getOptions("write", options));
|
|
105
|
+
const url = `${baseUrl}?opts=${optionsParam}&op=write`;
|
|
106
|
+
return fetch(url, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: { "Content-Type": "application/json" },
|
|
109
|
+
body: serialize(change)
|
|
110
|
+
}).then((res) => {
|
|
111
|
+
if (res.status === 200)
|
|
112
|
+
return res.json();
|
|
113
|
+
return res.text().then((message) => {
|
|
114
|
+
throw Error("server." + message);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
const log = debug("graffy:client:socket");
|
|
120
|
+
log.log = console.log.bind(console);
|
|
121
|
+
const MIN_DELAY = 1e3;
|
|
122
|
+
const MAX_DELAY = 3e5;
|
|
123
|
+
const DELAY_GROWTH = 1.5;
|
|
124
|
+
const INTERVAL = 2e3;
|
|
125
|
+
const PING_TIMEOUT = 4e4;
|
|
126
|
+
const RESET_TIMEOUT = 1e4;
|
|
127
|
+
function Socket(url, { onUnhandled, onStatusChange } = {}) {
|
|
128
|
+
const handlers = {};
|
|
129
|
+
const buffer = [];
|
|
130
|
+
let isOpen = false;
|
|
131
|
+
let isConnecting = false;
|
|
132
|
+
let socket;
|
|
133
|
+
let lastAlive;
|
|
134
|
+
let lastAttempt;
|
|
135
|
+
let attempts = 0;
|
|
136
|
+
let connectTimer;
|
|
137
|
+
let aliveTimer;
|
|
138
|
+
function start(params, callback) {
|
|
139
|
+
const id = makeId();
|
|
140
|
+
const request = [id, ...params];
|
|
141
|
+
handlers[id] = { request, callback };
|
|
142
|
+
if (isAlive())
|
|
143
|
+
send(request);
|
|
144
|
+
return id;
|
|
145
|
+
}
|
|
146
|
+
function stop(id, params) {
|
|
147
|
+
delete handlers[id];
|
|
148
|
+
if (params)
|
|
149
|
+
send([id, ...params]);
|
|
150
|
+
}
|
|
151
|
+
function connect() {
|
|
152
|
+
log("Trying to connect to", url);
|
|
153
|
+
isOpen = false;
|
|
154
|
+
isConnecting = true;
|
|
155
|
+
lastAttempt = Date.now();
|
|
156
|
+
attempts++;
|
|
157
|
+
socket = new WebSocket(url);
|
|
158
|
+
socket.onmessage = received;
|
|
159
|
+
socket.onerror = closed;
|
|
160
|
+
socket.onclose = closed;
|
|
161
|
+
socket.onopen = opened;
|
|
162
|
+
}
|
|
163
|
+
function received(event) {
|
|
164
|
+
const [id, ...data] = deserialize(event.data);
|
|
165
|
+
setAlive();
|
|
166
|
+
if (id === ":ping") {
|
|
167
|
+
send([":pong"]);
|
|
168
|
+
} else if (handlers[id]) {
|
|
169
|
+
handlers[id].callback(...data);
|
|
170
|
+
} else {
|
|
171
|
+
onUnhandled && onUnhandled(id, ...data);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function closed(_event) {
|
|
175
|
+
log("Closed");
|
|
176
|
+
if (isOpen && onStatusChange)
|
|
177
|
+
onStatusChange(false);
|
|
178
|
+
const wasOpen = isOpen;
|
|
179
|
+
isOpen = false;
|
|
180
|
+
isConnecting = false;
|
|
181
|
+
lastAttempt = Date.now();
|
|
182
|
+
if (wasOpen && !attempts) {
|
|
183
|
+
connect();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
maybeConnect();
|
|
187
|
+
}
|
|
188
|
+
function maybeConnect() {
|
|
189
|
+
const connDelay = lastAttempt + Math.min(MAX_DELAY, MIN_DELAY * Math.pow(DELAY_GROWTH, attempts)) - Date.now();
|
|
190
|
+
log("Will reconnect in", connDelay, "ms");
|
|
191
|
+
if (connDelay <= 0) {
|
|
192
|
+
connect();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
clearTimeout(connectTimer);
|
|
196
|
+
connectTimer = setTimeout(connect, connDelay);
|
|
197
|
+
}
|
|
198
|
+
function opened() {
|
|
199
|
+
log("Connected", buffer.length, Object.keys(handlers).length);
|
|
200
|
+
isOpen = true;
|
|
201
|
+
isConnecting = false;
|
|
202
|
+
lastAttempt = Date.now();
|
|
203
|
+
setAlive();
|
|
204
|
+
if (onStatusChange)
|
|
205
|
+
onStatusChange(true);
|
|
206
|
+
for (const id in handlers)
|
|
207
|
+
send(handlers[id].request);
|
|
208
|
+
while (buffer.length)
|
|
209
|
+
send(buffer.shift());
|
|
210
|
+
}
|
|
211
|
+
function setAlive() {
|
|
212
|
+
lastAlive = Date.now();
|
|
213
|
+
log("Set alive", lastAlive - lastAttempt);
|
|
214
|
+
if (lastAlive - lastAttempt > RESET_TIMEOUT)
|
|
215
|
+
attempts = 0;
|
|
216
|
+
}
|
|
217
|
+
function isAlive() {
|
|
218
|
+
log("Liveness check", isOpen ? "open" : "closed", Date.now() - lastAlive);
|
|
219
|
+
clearTimeout(aliveTimer);
|
|
220
|
+
aliveTimer = setTimeout(isAlive, INTERVAL);
|
|
221
|
+
if (!isOpen) {
|
|
222
|
+
if (!isConnecting)
|
|
223
|
+
maybeConnect();
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
if (Date.now() - lastAlive < PING_TIMEOUT)
|
|
227
|
+
return true;
|
|
228
|
+
socket.close();
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
function send(req) {
|
|
232
|
+
if (isAlive()) {
|
|
233
|
+
socket.send(serialize(req));
|
|
234
|
+
} else {
|
|
235
|
+
buffer.push(req);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
connect();
|
|
239
|
+
aliveTimer = setTimeout(isAlive, INTERVAL);
|
|
240
|
+
return {
|
|
241
|
+
start,
|
|
242
|
+
stop,
|
|
243
|
+
isAlive
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const wsClient = (url, { getOptions = () => {
|
|
247
|
+
}, watch, connInfoPath = "connection" } = {}) => (store) => {
|
|
248
|
+
if (!WebSocket)
|
|
249
|
+
throw Error("client.websocket.unavailable");
|
|
250
|
+
const socket = new Socket(url, { onUnhandled, onStatusChange });
|
|
251
|
+
let status = false;
|
|
252
|
+
const statusWatcher = makeWatcher();
|
|
253
|
+
function onUnhandled(id) {
|
|
254
|
+
socket.stop(id, ["unwatch"]);
|
|
255
|
+
}
|
|
256
|
+
function onStatusChange(newStatus) {
|
|
257
|
+
status = newStatus;
|
|
258
|
+
statusWatcher.write({ status });
|
|
259
|
+
}
|
|
260
|
+
function once(op, payload, options) {
|
|
261
|
+
return new Promise((resolve, reject) => {
|
|
262
|
+
const id = socket.start([op, payload, getOptions(op, options) || {}], (error, result) => {
|
|
263
|
+
socket.stop(id);
|
|
264
|
+
error ? reject(Error("server." + error)) : resolve(result);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
store.onWrite(connInfoPath, () => {
|
|
269
|
+
status = socket.isAlive();
|
|
270
|
+
return { status };
|
|
271
|
+
});
|
|
272
|
+
store.onRead(connInfoPath, () => ({ status }));
|
|
273
|
+
store.onWatch(connInfoPath, () => statusWatcher.watch({ status }));
|
|
274
|
+
store.on("read", (query, options) => once("read", query, options));
|
|
275
|
+
store.on("write", (change, options) => once("write", change, options));
|
|
276
|
+
store.on("watch", (query, options) => {
|
|
277
|
+
if (watch === "none")
|
|
278
|
+
throw Error("client.no_watch");
|
|
279
|
+
const op = "watch";
|
|
280
|
+
return makeStream((push, end) => {
|
|
281
|
+
const id = socket.start([op, query, getOptions(op, options) || {}], (error, result) => {
|
|
282
|
+
if (error) {
|
|
283
|
+
socket.stop(id);
|
|
284
|
+
end(Error("server." + error));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
push(result);
|
|
288
|
+
});
|
|
289
|
+
return () => {
|
|
290
|
+
socket.stop(id, ["unwatch"]);
|
|
291
|
+
};
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
const WSRE = /^wss?:\/\//;
|
|
296
|
+
function GraffyClient(baseUrl, options) {
|
|
297
|
+
if (WSRE.test(baseUrl)) {
|
|
298
|
+
return wsClient(baseUrl, options);
|
|
299
|
+
} else {
|
|
300
|
+
return httpClient(baseUrl, options);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
export { GraffyClient as default };
|
package/package.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"name": "@graffy/client",
|
|
3
3
|
"description": "Graffy client library for the browser.",
|
|
4
4
|
"author": "aravind (https://github.com/aravindet)",
|
|
5
|
-
"version": "0.15.
|
|
5
|
+
"version": "0.15.9",
|
|
6
6
|
"main": "./index.cjs",
|
|
7
7
|
"exports": {
|
|
8
8
|
"import": "./index.mjs",
|
|
9
9
|
"require": "./index.cjs"
|
|
10
10
|
},
|
|
11
11
|
"module": "./index.mjs",
|
|
12
|
-
"types": "./index.d.ts",
|
|
12
|
+
"types": "./types/index.d.ts",
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
15
|
"url": "git+https://github.com/usegraffy/graffy.git"
|
|
16
16
|
},
|
|
17
17
|
"license": "Apache-2.0",
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@graffy/common": "0.15.
|
|
20
|
-
"@graffy/stream": "0.15.
|
|
19
|
+
"@graffy/common": "0.15.9",
|
|
20
|
+
"@graffy/stream": "0.15.9",
|
|
21
21
|
"debug": "^4.3.2"
|
|
22
22
|
}
|
|
23
23
|
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function GraffyClient(baseUrl: any, options: any): (store: any) => void;
|
package/index.d.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
declare module "httpClient" {
|
|
2
|
-
export default httpClient;
|
|
3
|
-
function httpClient(baseUrl: any, { getOptions, watch, connInfoPath }?: {
|
|
4
|
-
getOptions?: () => Promise<void>;
|
|
5
|
-
watch: any;
|
|
6
|
-
connInfoPath?: string;
|
|
7
|
-
}): (store: any) => void;
|
|
8
|
-
}
|
|
9
|
-
declare module "Socket" {
|
|
10
|
-
export default function Socket(url: any, { onUnhandled, onStatusChange }?: {
|
|
11
|
-
onUnhandled: any;
|
|
12
|
-
onStatusChange: any;
|
|
13
|
-
}): {
|
|
14
|
-
start: (params: any, callback: any) => any;
|
|
15
|
-
stop: (id: any, params: any) => void;
|
|
16
|
-
isAlive: () => boolean;
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
declare module "wsClient" {
|
|
20
|
-
export default wsClient;
|
|
21
|
-
function wsClient(url: any, { getOptions, watch, connInfoPath }?: {
|
|
22
|
-
getOptions?: () => void;
|
|
23
|
-
watch: any;
|
|
24
|
-
connInfoPath?: string;
|
|
25
|
-
}): (store: any) => void;
|
|
26
|
-
}
|
|
27
|
-
declare module "index" {
|
|
28
|
-
export default function GraffyClient(baseUrl: any, options: any): (store: any) => void;
|
|
29
|
-
}
|