@dokebi/sse 0.4.4 → 0.4.5
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/AI.md +1 -1
- package/dist/gateway.js +20 -2
- package/dist/plugin.js +1 -1
- package/package.json +5 -5
package/AI.md
CHANGED
|
@@ -135,7 +135,7 @@ events.close(); events.reconnect();
|
|
|
135
135
|
|
|
136
136
|
## 5a. Seal hosts (D-0062)
|
|
137
137
|
|
|
138
|
-
On streams of a seal host (@dokebi/app `domains.<host>.seal: true`) or of a `seal: true` web route, **the data of every event** (including `dokebi-open`, `dokebi-reload` and `dokebi-restart`) is sealed with that stream's path (`E:<ts>:<b64>`, dokebi-web `sealOf`). Event names and ids stay plain. Plain streams share one buffer; only sealed streams seal per receiver. A failed seal ends that stream (`SEAL_ENCODE_FAILED`; never sent in plain text). Browser: `connectEvents({ ..., seal: currentSealBridge() })` (`@dokebi/seal/http`; undefined on plain pages, so the same code works). With a bridge, plain data is refused as `SEAL_FAILED`; without one, sealed data is refused as `SEAL_BRIDGE_MISSING` (neither reaches the handler).
|
|
138
|
+
On streams of a seal host (@dokebi/app `domains.<host>.seal: true`) or of a `seal: true` web route, **the data of every event** (including `dokebi-open`, `dokebi-reload` and `dokebi-restart`) is sealed with that stream's path (`E:<ts>:<b64>`, dokebi-web `sealOf`). Event names and ids stay plain. Plain streams share one buffer; only sealed streams seal per receiver. A failed seal ends that stream (`SEAL_ENCODE_FAILED`; never sent in plain text); when it is the first frame (`dokebi-open`, `dokebi-reload`) that cannot be sealed, the request is refused with 500 `SEAL_FAILED` before the stream head. Browser: `connectEvents({ ..., seal: currentSealBridge() })` (`@dokebi/seal/http`; undefined on plain pages, so the same code works). With a bridge, plain data is refused as `SEAL_FAILED`; without one, sealed data is refused as `SEAL_BRIDGE_MISSING` (neither reaches the handler).
|
|
139
139
|
|
|
140
140
|
## 6. Several workers and servers
|
|
141
141
|
|
package/dist/gateway.js
CHANGED
|
@@ -341,6 +341,18 @@ export class Gateway {
|
|
|
341
341
|
// D-0062: a stream of a seal host seals the data of every event, its first ones included.
|
|
342
342
|
const seal = this.web.sealOf(request);
|
|
343
343
|
const data = (json) => (seal ? seal.encode(json) : json);
|
|
344
|
+
// The first frame is made before the reply is hijacked: a seal that fails here still has a
|
|
345
|
+
// reply to refuse with. Made after the head (audit R9-g11-2), the exception left a hijacked
|
|
346
|
+
// response that was never ended: no stream entry, no ping, nothing drain could end.
|
|
347
|
+
const first = (event, value) => {
|
|
348
|
+
try {
|
|
349
|
+
return frame(undefined, event, data(JSON.stringify(value)));
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
this.#throttle.log('error', 'SSE first frame seal failed; stream refused', 'SEAL_ENCODE_FAILED', error, { path: route.path, routeId: registered.id });
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
};
|
|
344
356
|
if (route.version !== undefined) {
|
|
345
357
|
const sent = single(query.dokebiVersion) ?? single(request.headers['x-dokebi-version']);
|
|
346
358
|
if (sent !== route.version) {
|
|
@@ -349,12 +361,18 @@ export class Gateway {
|
|
|
349
361
|
expected: route.version,
|
|
350
362
|
received: typeof sent === 'string' ? sent.slice(0, 64) : null,
|
|
351
363
|
});
|
|
364
|
+
const reload = first(RELOAD_EVENT, { version: route.version });
|
|
365
|
+
if (reload === undefined)
|
|
366
|
+
return refuse(500, { error: 'SEAL_FAILED' });
|
|
352
367
|
reply.hijack();
|
|
353
368
|
res.writeHead(200, headers);
|
|
354
|
-
res.end(`retry: ${this.options.reloadRetryMs}\n${
|
|
369
|
+
res.end(`retry: ${this.options.reloadRetryMs}\n${reload}`);
|
|
355
370
|
return;
|
|
356
371
|
}
|
|
357
372
|
}
|
|
373
|
+
const opening = first(OPEN_EVENT, { retryMs: this.options.retryMs });
|
|
374
|
+
if (opening === undefined)
|
|
375
|
+
return refuse(500, { error: 'SEAL_FAILED' });
|
|
358
376
|
const header = single(request.headers['last-event-id']);
|
|
359
377
|
const asked = header ?? single(query.lastEventId);
|
|
360
378
|
const lastEventId = typeof asked === 'string' && asked.length <= MAX_TEXT && isLine(asked) ? asked : undefined;
|
|
@@ -366,7 +384,7 @@ export class Gateway {
|
|
|
366
384
|
res.writeHead(200, headers);
|
|
367
385
|
// The reconnect hint first, and the same value as data for clients that manage reconnects
|
|
368
386
|
// themselves (EventSource does not expose `retry:`).
|
|
369
|
-
res.write(`retry: ${this.options.retryMs}\n${
|
|
387
|
+
res.write(`retry: ${this.options.retryMs}\n${opening}`);
|
|
370
388
|
const entry = this.#add(res, identity, route, rooms, lastEventId, seal);
|
|
371
389
|
res.once('close', () => this.#closed(entry));
|
|
372
390
|
if (identity.authorization) {
|
package/dist/plugin.js
CHANGED
|
@@ -109,7 +109,7 @@ class SseRuntime {
|
|
|
109
109
|
/** Factory input: `{ app, options? }`. Options default to `app.config['dokebi-sse']`. */
|
|
110
110
|
export const createPlugin = definePlugin({
|
|
111
111
|
name: 'dokebi-sse',
|
|
112
|
-
version: '0.4.
|
|
112
|
+
version: '0.4.5',
|
|
113
113
|
core: '^0.4.2',
|
|
114
114
|
// The declaration (not parseOptions) so core logs OPTION_DEPRECATED for old keys (bus, closeTimeoutMs).
|
|
115
115
|
options: declaration,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dokebi/sse",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "Server-sent events for @dokebi/web: authenticated streams, rooms, cross-worker fan-out over @dokebi/event-bus, and a browser client",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dokebi",
|
|
@@ -69,11 +69,11 @@
|
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@dokebi/cluster": "0.4.2",
|
|
72
|
-
"@dokebi/config": "0.4.
|
|
73
|
-
"@dokebi/event-bus": "0.4.
|
|
72
|
+
"@dokebi/config": "0.4.6",
|
|
73
|
+
"@dokebi/event-bus": "0.4.3",
|
|
74
74
|
"@dokebi/health": "0.4.1",
|
|
75
|
-
"@dokebi/nats": "0.4.
|
|
76
|
-
"@dokebi/web": "0.4.
|
|
75
|
+
"@dokebi/nats": "0.4.2",
|
|
76
|
+
"@dokebi/web": "0.4.7",
|
|
77
77
|
"react": "19.3.0",
|
|
78
78
|
"react-test-renderer": "19.3.0",
|
|
79
79
|
"vite": "8.3.4",
|