@calimero-network/mero-react 5.0.0 → 5.1.0
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/index.cjs +180 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -3
- package/dist/index.d.ts +47 -3
- package/dist/index.js +180 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1266,6 +1266,185 @@ function base58ToHex(input) {
|
|
|
1266
1266
|
}
|
|
1267
1267
|
|
|
1268
1268
|
// src/hooks/index.ts
|
|
1269
|
+
var NO_EPHEMERAL_SURFACE_MESSAGE = "useEphemeral: this client has no `mero.ephemeral` surface. Ephemeral presence requires @calimero-network/mero-js >= 9.1.0; upgrade the installed @calimero-network/mero-js (and any lockfile pin) to use it.";
|
|
1270
|
+
var COULD_NOT_RESOLVE_IDENTITY_MESSAGE = "useEphemeral: could not resolve a local context identity, so your own presence cannot be filtered out. Pass includeSelf: true to accept this.";
|
|
1271
|
+
var REPLAY_RECONCILE_MS = 50;
|
|
1272
|
+
function useEphemeral(contextId, options = {}) {
|
|
1273
|
+
const { includeSelf = false } = options;
|
|
1274
|
+
const { mero } = useMero();
|
|
1275
|
+
const ephemeral = mero ? mero.ephemeral ?? null : null;
|
|
1276
|
+
const [peers, setPeers] = react.useState(/* @__PURE__ */ new Map());
|
|
1277
|
+
const [latchedError, setLatchedError] = react.useState(null);
|
|
1278
|
+
const contextKeyRef = react.useRef(contextId);
|
|
1279
|
+
contextKeyRef.current = contextId;
|
|
1280
|
+
const setSourceError = react.useCallback(
|
|
1281
|
+
(source, err, key) => {
|
|
1282
|
+
if (key !== contextKeyRef.current) return;
|
|
1283
|
+
setLatchedError({ contextKey: key, source, error: err });
|
|
1284
|
+
},
|
|
1285
|
+
[]
|
|
1286
|
+
);
|
|
1287
|
+
const clearSourceError = react.useCallback((source, key) => {
|
|
1288
|
+
setLatchedError(
|
|
1289
|
+
(prev) => prev && prev.source === source && prev.contextKey === key ? null : prev
|
|
1290
|
+
);
|
|
1291
|
+
}, []);
|
|
1292
|
+
const surfaceError = react.useMemo(
|
|
1293
|
+
() => mero && !ephemeral ? new Error(NO_EPHEMERAL_SURFACE_MESSAGE) : null,
|
|
1294
|
+
[mero, ephemeral]
|
|
1295
|
+
);
|
|
1296
|
+
const error = surfaceError ?? (latchedError && latchedError.contextKey === contextId && !(latchedError.source === "identity" && includeSelf) ? latchedError.error : null);
|
|
1297
|
+
const receivedAtRef = react.useRef(/* @__PURE__ */ new Map());
|
|
1298
|
+
const codecRef = react.useRef(options.codec);
|
|
1299
|
+
codecRef.current = options.codec;
|
|
1300
|
+
const initialRef = react.useRef(options.initial);
|
|
1301
|
+
initialRef.current = options.initial;
|
|
1302
|
+
const selfRef = react.useRef(null);
|
|
1303
|
+
const lastLocalRef = react.useRef(options.initial);
|
|
1304
|
+
const pendingRef = react.useRef(void 0);
|
|
1305
|
+
const timerRef = react.useRef(null);
|
|
1306
|
+
const lastSentAtRef = react.useRef(Date.now());
|
|
1307
|
+
const replaySeenRef = react.useRef(null);
|
|
1308
|
+
const replayTimerRef = react.useRef(null);
|
|
1309
|
+
const ageOf = react.useCallback((author) => {
|
|
1310
|
+
const at = receivedAtRef.current.get(author);
|
|
1311
|
+
return at === void 0 ? void 0 : Date.now() - at;
|
|
1312
|
+
}, []);
|
|
1313
|
+
react.useEffect(() => {
|
|
1314
|
+
setPeers(/* @__PURE__ */ new Map());
|
|
1315
|
+
receivedAtRef.current = /* @__PURE__ */ new Map();
|
|
1316
|
+
if (timerRef.current) {
|
|
1317
|
+
clearTimeout(timerRef.current);
|
|
1318
|
+
timerRef.current = null;
|
|
1319
|
+
}
|
|
1320
|
+
if (replayTimerRef.current) {
|
|
1321
|
+
clearTimeout(replayTimerRef.current);
|
|
1322
|
+
replayTimerRef.current = null;
|
|
1323
|
+
}
|
|
1324
|
+
replaySeenRef.current = null;
|
|
1325
|
+
pendingRef.current = void 0;
|
|
1326
|
+
lastLocalRef.current = initialRef.current;
|
|
1327
|
+
selfRef.current = null;
|
|
1328
|
+
setLatchedError(null);
|
|
1329
|
+
}, [contextId]);
|
|
1330
|
+
react.useEffect(() => {
|
|
1331
|
+
if (!mero || !contextId) return;
|
|
1332
|
+
const key = contextId;
|
|
1333
|
+
let cancelled = false;
|
|
1334
|
+
mero.admin.getContextIdentitiesOwned(contextId).then((res) => {
|
|
1335
|
+
if (cancelled) return;
|
|
1336
|
+
const self = res.identities?.[0] ?? null;
|
|
1337
|
+
selfRef.current = self;
|
|
1338
|
+
if (!self) {
|
|
1339
|
+
setSourceError("identity", new Error(COULD_NOT_RESOLVE_IDENTITY_MESSAGE), key);
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
clearSourceError("identity", key);
|
|
1343
|
+
if (!includeSelf) {
|
|
1344
|
+
receivedAtRef.current.delete(self);
|
|
1345
|
+
setPeers((prev) => {
|
|
1346
|
+
if (!prev.has(self)) return prev;
|
|
1347
|
+
const next = new Map(prev);
|
|
1348
|
+
next.delete(self);
|
|
1349
|
+
return next;
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
}).catch((err) => {
|
|
1353
|
+
if (!cancelled) {
|
|
1354
|
+
setSourceError("identity", toError(err), key);
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
return () => {
|
|
1358
|
+
cancelled = true;
|
|
1359
|
+
};
|
|
1360
|
+
}, [mero, contextId, includeSelf, setSourceError, clearSourceError]);
|
|
1361
|
+
react.useEffect(() => {
|
|
1362
|
+
if (!ephemeral || !contextId) return;
|
|
1363
|
+
const closeReplayWindow = () => {
|
|
1364
|
+
replayTimerRef.current = null;
|
|
1365
|
+
const seen = replaySeenRef.current;
|
|
1366
|
+
replaySeenRef.current = null;
|
|
1367
|
+
if (!seen) return;
|
|
1368
|
+
setPeers((prev) => {
|
|
1369
|
+
let next = null;
|
|
1370
|
+
for (const author of prev.keys()) {
|
|
1371
|
+
if (seen.has(author)) continue;
|
|
1372
|
+
if (!next) next = new Map(prev);
|
|
1373
|
+
next.delete(author);
|
|
1374
|
+
receivedAtRef.current.delete(author);
|
|
1375
|
+
}
|
|
1376
|
+
return next ?? prev;
|
|
1377
|
+
});
|
|
1378
|
+
};
|
|
1379
|
+
const unsubscribe = ephemeral.subscribe(
|
|
1380
|
+
contextId,
|
|
1381
|
+
(entry) => {
|
|
1382
|
+
const { author, state } = entry;
|
|
1383
|
+
if (entry.ageMs !== void 0) {
|
|
1384
|
+
if (!replaySeenRef.current) replaySeenRef.current = /* @__PURE__ */ new Set();
|
|
1385
|
+
if (replayTimerRef.current) clearTimeout(replayTimerRef.current);
|
|
1386
|
+
replayTimerRef.current = setTimeout(closeReplayWindow, REPLAY_RECONCILE_MS);
|
|
1387
|
+
}
|
|
1388
|
+
if (entry.removed || state === void 0) {
|
|
1389
|
+
replaySeenRef.current?.delete(author);
|
|
1390
|
+
receivedAtRef.current.delete(author);
|
|
1391
|
+
setPeers((prev) => {
|
|
1392
|
+
if (!prev.has(author)) return prev;
|
|
1393
|
+
const next = new Map(prev);
|
|
1394
|
+
next.delete(author);
|
|
1395
|
+
return next;
|
|
1396
|
+
});
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
if (!includeSelf && author === selfRef.current) return;
|
|
1400
|
+
replaySeenRef.current?.add(author);
|
|
1401
|
+
receivedAtRef.current.set(author, Date.now() - (entry.ageMs ?? 0));
|
|
1402
|
+
setPeers((prev) => new Map(prev).set(author, state));
|
|
1403
|
+
},
|
|
1404
|
+
codecRef.current
|
|
1405
|
+
);
|
|
1406
|
+
return () => {
|
|
1407
|
+
if (replayTimerRef.current) {
|
|
1408
|
+
clearTimeout(replayTimerRef.current);
|
|
1409
|
+
replayTimerRef.current = null;
|
|
1410
|
+
}
|
|
1411
|
+
replaySeenRef.current = null;
|
|
1412
|
+
unsubscribe();
|
|
1413
|
+
};
|
|
1414
|
+
}, [ephemeral, contextId, includeSelf]);
|
|
1415
|
+
const publish = react.useCallback((value) => {
|
|
1416
|
+
if (!ephemeral || !contextId) return;
|
|
1417
|
+
const key = contextId;
|
|
1418
|
+
lastSentAtRef.current = Date.now();
|
|
1419
|
+
void ephemeral.set(key, value, codecRef.current).then(
|
|
1420
|
+
() => clearSourceError("publish", key),
|
|
1421
|
+
(err) => setSourceError("publish", toError(err), key)
|
|
1422
|
+
);
|
|
1423
|
+
}, [ephemeral, contextId, setSourceError, clearSourceError]);
|
|
1424
|
+
const setPresence = react.useCallback((partial) => {
|
|
1425
|
+
const next = { ...lastLocalRef.current ?? {}, ...partial };
|
|
1426
|
+
lastLocalRef.current = next;
|
|
1427
|
+
const throttleMs = options.throttleMs ?? 30;
|
|
1428
|
+
if (throttleMs <= 0) {
|
|
1429
|
+
publish(next);
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
pendingRef.current = next;
|
|
1433
|
+
if (timerRef.current) return;
|
|
1434
|
+
const elapsed = Date.now() - lastSentAtRef.current;
|
|
1435
|
+
const delay = Math.max(throttleMs - elapsed, 0);
|
|
1436
|
+
timerRef.current = setTimeout(() => {
|
|
1437
|
+
timerRef.current = null;
|
|
1438
|
+
const queued = pendingRef.current;
|
|
1439
|
+
pendingRef.current = void 0;
|
|
1440
|
+
if (queued !== void 0) publish(queued);
|
|
1441
|
+
}, delay);
|
|
1442
|
+
}, [publish, options.throttleMs]);
|
|
1443
|
+
react.useEffect(() => () => {
|
|
1444
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
1445
|
+
}, []);
|
|
1446
|
+
return { peers, setPresence, ageOf, error };
|
|
1447
|
+
}
|
|
1269
1448
|
function toError(err) {
|
|
1270
1449
|
return err instanceof Error ? err : new Error(String(err));
|
|
1271
1450
|
}
|
|
@@ -2665,6 +2844,7 @@ exports.useDeleteContext = useDeleteContext;
|
|
|
2665
2844
|
exports.useDeleteGroup = useDeleteGroup;
|
|
2666
2845
|
exports.useDeleteNamespace = useDeleteNamespace;
|
|
2667
2846
|
exports.useDetachContextFromGroup = useDetachContextFromGroup;
|
|
2847
|
+
exports.useEphemeral = useEphemeral;
|
|
2668
2848
|
exports.useExecute = useExecute;
|
|
2669
2849
|
exports.useGroupAppVersion = useGroupAppVersion;
|
|
2670
2850
|
exports.useGroupCapabilities = useGroupCapabilities;
|