@calimero-network/mero-js 23.4.1 → 23.4.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/dist/ephemeral/index.js +10 -0
- package/dist/events/sse.d.ts +17 -0
- package/dist/events/sse.js +44 -0
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +3 -3
- package/dist/index.cjs +42 -1
- package/dist/index.cjs.map +2 -2
- package/dist/index.mjs +42 -1
- package/dist/index.mjs.map +2 -2
- package/dist/namespace-op/namespace-op.d.ts +8 -1
- package/dist/namespace-op/namespace-op.js +8 -1
- package/package.json +1 -1
package/dist/ephemeral/index.js
CHANGED
|
@@ -81,6 +81,16 @@ export function subscribePresence(sse, contextId, handler, codec = jsonCodec())
|
|
|
81
81
|
handler(entry);
|
|
82
82
|
};
|
|
83
83
|
sse.on('event', listener);
|
|
84
|
+
// On a context this connection already holds, the node's one-time replay
|
|
85
|
+
// went to whichever listener came first: seed this one from what the
|
|
86
|
+
// client has kept, as the replay would have. (A context not held yet is
|
|
87
|
+
// replayed by the node on the subscribe below.) Optional: a test double
|
|
88
|
+
// or another event source may not keep presence.
|
|
89
|
+
if (sse.isSubscribed?.(contextId)) {
|
|
90
|
+
for (const data of sse.presenceSeed?.(contextId) ?? []) {
|
|
91
|
+
listener({ contextId, type: 'Ephemeral', data });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
84
94
|
// Errors surface via the SSE client's own 'error' event; nothing more to
|
|
85
95
|
// do with them here.
|
|
86
96
|
void sse.connect().catch(() => undefined);
|
package/dist/events/sse.d.ts
CHANGED
|
@@ -41,6 +41,14 @@ export declare class SseClient {
|
|
|
41
41
|
private reconnectTimer;
|
|
42
42
|
private subscribedContextIds;
|
|
43
43
|
private subscribedGroupIds;
|
|
44
|
+
/**
|
|
45
|
+
* The current presence of each subscribed context, as the node's replay and
|
|
46
|
+
* its live deltas left it: author → that author's last `Ephemeral` data and
|
|
47
|
+
* when it arrived. The node replays a context's presence once, when the
|
|
48
|
+
* context is first subscribed on this connection, so a presence listener
|
|
49
|
+
* that comes later would never see it; {@link presenceSeed} gives it this.
|
|
50
|
+
*/
|
|
51
|
+
private presence;
|
|
44
52
|
private closed;
|
|
45
53
|
private listeners;
|
|
46
54
|
private fetchImpl;
|
|
@@ -87,6 +95,15 @@ export declare class SseClient {
|
|
|
87
95
|
contextIds?: string[];
|
|
88
96
|
groupIds?: string[];
|
|
89
97
|
}): Promise<void>;
|
|
98
|
+
/** Whether this connection holds `contextId`'s events. */
|
|
99
|
+
isSubscribed(contextId: string): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* The presence currently known for `contextId`, as `Ephemeral` event data,
|
|
102
|
+
* each entry's `ageMs` advanced by how long ago it arrived. Empty for a
|
|
103
|
+
* context this connection does not hold.
|
|
104
|
+
*/
|
|
105
|
+
presenceSeed(contextId: string): Array<Record<string, unknown>>;
|
|
106
|
+
private notePresence;
|
|
90
107
|
unsubscribe(idsOrOpts: string[] | {
|
|
91
108
|
contextIds?: string[];
|
|
92
109
|
groupIds?: string[];
|
package/dist/events/sse.js
CHANGED
|
@@ -39,6 +39,14 @@ export class SseClient {
|
|
|
39
39
|
this.reconnectTimer = null;
|
|
40
40
|
this.subscribedContextIds = new Set();
|
|
41
41
|
this.subscribedGroupIds = new Set();
|
|
42
|
+
/**
|
|
43
|
+
* The current presence of each subscribed context, as the node's replay and
|
|
44
|
+
* its live deltas left it: author → that author's last `Ephemeral` data and
|
|
45
|
+
* when it arrived. The node replays a context's presence once, when the
|
|
46
|
+
* context is first subscribed on this connection, so a presence listener
|
|
47
|
+
* that comes later would never see it; {@link presenceSeed} gives it this.
|
|
48
|
+
*/
|
|
49
|
+
this.presence = new Map();
|
|
42
50
|
this.closed = false;
|
|
43
51
|
this.listeners = { connect: [], event: [], error: [] };
|
|
44
52
|
this.baseUrl = opts.baseUrl.replace(/\/+$/, '');
|
|
@@ -227,6 +235,9 @@ export class SseClient {
|
|
|
227
235
|
if (msg.type === 'connect' && msg.session_id) {
|
|
228
236
|
this.sessionId = msg.session_id;
|
|
229
237
|
this.emit('connect', msg.session_id);
|
|
238
|
+
// A new connection gets a new replay when it re-subscribes; what the
|
|
239
|
+
// old one said may name authors who have since gone.
|
|
240
|
+
this.presence.clear();
|
|
230
241
|
// Re-subscribe after reconnect
|
|
231
242
|
if (this.subscribedContextIds.size > 0 || this.subscribedGroupIds.size > 0) {
|
|
232
243
|
this.sendSubscription('subscribe', {
|
|
@@ -252,6 +263,8 @@ export class SseClient {
|
|
|
252
263
|
}
|
|
253
264
|
// Context event message
|
|
254
265
|
if (msg.result.contextId) {
|
|
266
|
+
if (msg.result.type === 'Ephemeral')
|
|
267
|
+
this.notePresence(msg.result.contextId, eventData);
|
|
255
268
|
this.emit('event', {
|
|
256
269
|
contextId: msg.result.contextId,
|
|
257
270
|
// Forward the event tag (sibling of `data` in core's flattened
|
|
@@ -289,12 +302,43 @@ export class SseClient {
|
|
|
289
302
|
await this.sendSubscription('subscribe', { contextIds: newContextIds, groupIds: newGroupIds });
|
|
290
303
|
}
|
|
291
304
|
}
|
|
305
|
+
/** Whether this connection holds `contextId`'s events. */
|
|
306
|
+
isSubscribed(contextId) {
|
|
307
|
+
return this.subscribedContextIds.has(contextId);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* The presence currently known for `contextId`, as `Ephemeral` event data,
|
|
311
|
+
* each entry's `ageMs` advanced by how long ago it arrived. Empty for a
|
|
312
|
+
* context this connection does not hold.
|
|
313
|
+
*/
|
|
314
|
+
presenceSeed(contextId) {
|
|
315
|
+
const now = Date.now();
|
|
316
|
+
return [...(this.presence.get(contextId)?.values() ?? [])].map(({ data, at }) => ({
|
|
317
|
+
...data,
|
|
318
|
+
ageMs: (typeof data.ageMs === 'number' ? data.ageMs : 0) + (now - at),
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
notePresence(contextId, data) {
|
|
322
|
+
if (!this.subscribedContextIds.has(contextId))
|
|
323
|
+
return;
|
|
324
|
+
const d = data;
|
|
325
|
+
if (!d || typeof d.author !== 'string')
|
|
326
|
+
return;
|
|
327
|
+
let byAuthor = this.presence.get(contextId);
|
|
328
|
+
if (!byAuthor)
|
|
329
|
+
this.presence.set(contextId, (byAuthor = new Map()));
|
|
330
|
+
if (d.removed)
|
|
331
|
+
byAuthor.delete(d.author);
|
|
332
|
+
else
|
|
333
|
+
byAuthor.set(d.author, { data: d, at: Date.now() });
|
|
334
|
+
}
|
|
292
335
|
async unsubscribe(idsOrOpts) {
|
|
293
336
|
const opts = Array.isArray(idsOrOpts) ? { contextIds: idsOrOpts } : idsOrOpts;
|
|
294
337
|
const hadContextIds = (opts.contextIds ?? []).filter(id => this.subscribedContextIds.has(id));
|
|
295
338
|
const hadGroupIds = (opts.groupIds ?? []).filter(id => this.subscribedGroupIds.has(id));
|
|
296
339
|
for (const id of opts.contextIds ?? []) {
|
|
297
340
|
this.subscribedContextIds.delete(id);
|
|
341
|
+
this.presence.delete(id);
|
|
298
342
|
}
|
|
299
343
|
for (const id of opts.groupIds ?? []) {
|
|
300
344
|
this.subscribedGroupIds.delete(id);
|