@calimero-network/mero-js 13.0.0 → 13.2.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 CHANGED
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  CAPABILITIES: () => CAPABILITIES,
26
26
  CloudClient: () => CloudClient,
27
27
  DEFAULT_LOCAL_NODE_PORTS: () => DEFAULT_LOCAL_NODE_PORTS,
28
+ EphemeralClient: () => EphemeralClient,
28
29
  HTTPError: () => HTTPError,
29
30
  LocalStorageTokenStore: () => LocalStorageTokenStore,
30
31
  MemoryTokenStore: () => MemoryTokenStore,
@@ -54,10 +55,14 @@ __export(index_exports, {
54
55
  createUniversalHttpClient: () => createUniversalHttpClient,
55
56
  discoverLocalNodes: () => discoverLocalNodes,
56
57
  hasCap: () => hasCap,
58
+ jsonCodec: () => jsonCodec,
57
59
  localNodeUrl: () => localNodeUrl,
58
60
  nodeEndpoint: () => nodeEndpoint,
59
61
  parseAuthCallback: () => parseAuthCallback,
60
62
  probeNodeHealth: () => probeNodeHealth,
63
+ sameAccount: () => sameAccount,
64
+ toAccountBase58: () => toAccountBase58,
65
+ toAccountHex: () => toAccountHex,
61
66
  withCap: () => withCap,
62
67
  withRetry: () => withRetry,
63
68
  withoutCap: () => withoutCap
@@ -1538,35 +1543,33 @@ var RpcError = class extends Error {
1538
1543
  this.type = type;
1539
1544
  }
1540
1545
  };
1546
+ async function jsonRpcCall(httpClient, method, params) {
1547
+ const response = await httpClient.post("/jsonrpc", {
1548
+ jsonrpc: "2.0",
1549
+ id: 1,
1550
+ method,
1551
+ params
1552
+ });
1553
+ if (response.error) {
1554
+ const err = response.error;
1555
+ throw new RpcError(err.code ?? -1, err.message ?? err.type ?? "RPC error", err.data, err.type);
1556
+ }
1557
+ return response.result;
1558
+ }
1541
1559
  var RpcClient = class {
1542
1560
  constructor(opts) {
1543
1561
  this.httpClient = opts.httpClient;
1544
1562
  }
1545
1563
  async execute(params) {
1546
- const body = {
1547
- jsonrpc: "2.0",
1548
- id: 1,
1549
- method: "execute",
1550
- params: {
1551
- contextId: params.contextId,
1552
- method: params.method,
1553
- argsJson: params.argsJson ?? {}
1554
- }
1555
- };
1556
- const response = await this.httpClient.post(
1557
- "/jsonrpc",
1558
- body
1559
- );
1560
- if (response.error) {
1561
- const err = response.error;
1562
- const code = err.code ?? -1;
1563
- const message = err.message ?? err.type ?? "RPC error";
1564
- throw new RpcError(code, message, err.data, err.type);
1565
- }
1566
- if (response.result && "output" in response.result) {
1567
- return response.result.output;
1564
+ const result = await jsonRpcCall(this.httpClient, "execute", {
1565
+ contextId: params.contextId,
1566
+ method: params.method,
1567
+ argsJson: params.argsJson ?? {}
1568
+ });
1569
+ if (result && "output" in result) {
1570
+ return result.output;
1568
1571
  }
1569
- return response.result;
1572
+ return result;
1570
1573
  }
1571
1574
  /**
1572
1575
  * One-tap owner-driven convert: re-signs the caller's identity-gated entries
@@ -1595,6 +1598,7 @@ function isGroupMigrationEvent(event) {
1595
1598
  }
1596
1599
 
1597
1600
  // src/events/sse.ts
1601
+ var TERMINAL_AUTH_ERRORS2 = /* @__PURE__ */ new Set(["token_reuse", "token_revoked"]);
1598
1602
  var SseClient = class {
1599
1603
  constructor(opts) {
1600
1604
  this.sessionId = null;
@@ -1672,7 +1676,8 @@ var SseClient = class {
1672
1676
  this.abortController = new AbortController();
1673
1677
  try {
1674
1678
  const token = await this.getAuthToken();
1675
- const response = await fetch(`${this.baseUrl}/sse`, {
1679
+ const url = `${this.baseUrl}/sse`;
1680
+ const response = await fetch(url, {
1676
1681
  headers: {
1677
1682
  "Authorization": `Bearer ${token}`,
1678
1683
  "Accept": "text/event-stream"
@@ -1680,7 +1685,25 @@ var SseClient = class {
1680
1685
  signal: this.abortController.signal
1681
1686
  });
1682
1687
  if (!response.ok) {
1683
- throw new Error(`SSE connection failed: ${response.status}`);
1688
+ const authError = response.headers.get("x-auth-error");
1689
+ const bodyText = await response.text().catch(() => void 0);
1690
+ if (response.status === 401 && authError && TERMINAL_AUTH_ERRORS2.has(authError)) {
1691
+ throw new AuthRevokedError(
1692
+ authError,
1693
+ response.status,
1694
+ response.statusText,
1695
+ url,
1696
+ response.headers,
1697
+ bodyText
1698
+ );
1699
+ }
1700
+ throw new HTTPError(
1701
+ response.status,
1702
+ response.statusText,
1703
+ url,
1704
+ response.headers,
1705
+ bodyText
1706
+ );
1684
1707
  }
1685
1708
  if (!response.body) {
1686
1709
  throw new Error("SSE response has no body");
@@ -2057,6 +2080,72 @@ var _WsClient = class _WsClient {
2057
2080
  _WsClient.MAX_BACKOFF_MS = 3e4;
2058
2081
  var WsClient = _WsClient;
2059
2082
 
2083
+ // src/ephemeral/codec.ts
2084
+ function jsonCodec() {
2085
+ return {
2086
+ encode(value) {
2087
+ return Array.from(new TextEncoder().encode(JSON.stringify(value)));
2088
+ },
2089
+ decode(bytes) {
2090
+ return JSON.parse(new TextDecoder().decode(new Uint8Array(bytes)));
2091
+ }
2092
+ };
2093
+ }
2094
+
2095
+ // src/ephemeral/index.ts
2096
+ var EphemeralClient = class {
2097
+ constructor(opts) {
2098
+ this.httpClient = opts.httpClient;
2099
+ this.sse = opts.sse;
2100
+ }
2101
+ /**
2102
+ * Publish your own presence slice, replacing it wholesale.
2103
+ *
2104
+ * Rejects with an `RpcError` (not a bare `Error`), so a typed failure such as
2105
+ * an oversized slice keeps its `type` and `data` — e.g. `SliceTooLarge` with
2106
+ * the offending size — and is `instanceof RpcError` like every other RPC in
2107
+ * this SDK.
2108
+ */
2109
+ async set(contextId, state, codec = jsonCodec()) {
2110
+ await jsonRpcCall(this.httpClient, "set_ephemeral", {
2111
+ contextId,
2112
+ state: codec.encode(state)
2113
+ });
2114
+ }
2115
+ /**
2116
+ * Observe presence changes for a context.
2117
+ *
2118
+ * This adds NO transport — it is a typed filter over the existing SSE event
2119
+ * stream, which already carries `{ contextId, type, data }`. On subscribing,
2120
+ * the node replays that context's current presence to this connection as
2121
+ * ordinary `Ephemeral` events before any live deltas; a replayed entry
2122
+ * carries `ageMs`, a live delta does not (never synthesized as `0` — absent
2123
+ * and zero mean different things). Returns an unsubscribe function.
2124
+ */
2125
+ subscribe(contextId, handler, codec = jsonCodec()) {
2126
+ const listener = (event) => {
2127
+ const e = event;
2128
+ if (e.type !== "Ephemeral" || e.contextId !== contextId) return;
2129
+ const data = e.data;
2130
+ if (!data?.author) return;
2131
+ const removed = Boolean(data.removed);
2132
+ const entry = {
2133
+ author: data.author,
2134
+ state: removed || data.state === void 0 ? void 0 : codec.decode(data.state),
2135
+ removed
2136
+ };
2137
+ if (data.ageMs !== void 0) entry.ageMs = data.ageMs;
2138
+ handler(entry);
2139
+ };
2140
+ this.sse.on("event", listener);
2141
+ void this.sse.connect().catch(() => void 0);
2142
+ void this.sse.subscribe([contextId]).catch(() => void 0);
2143
+ return () => {
2144
+ this.sse.off("event", listener);
2145
+ };
2146
+ }
2147
+ };
2148
+
2060
2149
  // src/mero-js.ts
2061
2150
  var REFRESH_LOCK_NAME = "mero-js:token-refresh";
2062
2151
  function getLockManager() {
@@ -2175,6 +2264,21 @@ var MeroJs = class {
2175
2264
  }
2176
2265
  return this.sseClient;
2177
2266
  }
2267
+ /**
2268
+ * Get the ephemeral-presence client (lazy initialized).
2269
+ *
2270
+ * Shares this instance's SSE client, so `subscribe` reuses the one event
2271
+ * stream rather than opening a second connection.
2272
+ */
2273
+ get ephemeral() {
2274
+ if (!this.ephemeralClient) {
2275
+ this.ephemeralClient = new EphemeralClient({
2276
+ httpClient: this.httpClient,
2277
+ sse: this.events
2278
+ });
2279
+ }
2280
+ return this.ephemeralClient;
2281
+ }
2178
2282
  /**
2179
2283
  * Get the WebSocket event client (lazy initialized).
2180
2284
  * @experimental Use `events` (SSE) for production. WsClient is experimental.
@@ -2365,6 +2469,93 @@ function createMeroJs(config) {
2365
2469
  return new MeroJs(config);
2366
2470
  }
2367
2471
 
2472
+ // src/account/index.ts
2473
+ var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
2474
+ var HEX_ACCOUNT = /^[0-9a-f]{64}$/i;
2475
+ function encodeBase58(bytes) {
2476
+ if (bytes.length === 0) return "";
2477
+ const digits = [0];
2478
+ for (const byte of bytes) {
2479
+ let carry = byte;
2480
+ for (let i = 0; i < digits.length; i++) {
2481
+ carry += digits[i] << 8;
2482
+ digits[i] = carry % 58;
2483
+ carry = carry / 58 | 0;
2484
+ }
2485
+ while (carry > 0) {
2486
+ digits.push(carry % 58);
2487
+ carry = carry / 58 | 0;
2488
+ }
2489
+ }
2490
+ let out = "";
2491
+ for (const byte of bytes) {
2492
+ if (byte !== 0) break;
2493
+ out += BASE58_ALPHABET[0];
2494
+ }
2495
+ for (let i = digits.length - 1; i >= 0; i--) {
2496
+ out += BASE58_ALPHABET[digits[i]];
2497
+ }
2498
+ return out;
2499
+ }
2500
+ function decodeBase58(value) {
2501
+ if (value.length === 0) return null;
2502
+ const bytes = [0];
2503
+ for (const char of value) {
2504
+ const index = BASE58_ALPHABET.indexOf(char);
2505
+ if (index < 0) return null;
2506
+ let carry = index;
2507
+ for (let i = 0; i < bytes.length; i++) {
2508
+ carry += bytes[i] * 58;
2509
+ bytes[i] = carry & 255;
2510
+ carry >>= 8;
2511
+ }
2512
+ while (carry > 0) {
2513
+ bytes.push(carry & 255);
2514
+ carry >>= 8;
2515
+ }
2516
+ }
2517
+ let leadingZeros = 0;
2518
+ for (const char of value) {
2519
+ if (char !== BASE58_ALPHABET[0]) break;
2520
+ leadingZeros++;
2521
+ }
2522
+ return Uint8Array.from([
2523
+ ...new Array(leadingZeros).fill(0),
2524
+ ...bytes.reverse()
2525
+ ]);
2526
+ }
2527
+ function toHex(bytes) {
2528
+ let out = "";
2529
+ for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
2530
+ return out;
2531
+ }
2532
+ function fromHex(hex) {
2533
+ const bytes = new Uint8Array(hex.length / 2);
2534
+ for (let i = 0; i < bytes.length; i++) {
2535
+ bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
2536
+ }
2537
+ return bytes;
2538
+ }
2539
+ function toAccountHex(id) {
2540
+ const trimmed = (id ?? "").trim();
2541
+ if (!trimmed) return "";
2542
+ if (HEX_ACCOUNT.test(trimmed)) return trimmed.toLowerCase();
2543
+ const decoded = decodeBase58(trimmed);
2544
+ if (!decoded || decoded.length !== 32) return trimmed;
2545
+ return toHex(decoded);
2546
+ }
2547
+ function toAccountBase58(id) {
2548
+ const trimmed = (id ?? "").trim();
2549
+ if (!trimmed) return "";
2550
+ if (!HEX_ACCOUNT.test(trimmed)) return trimmed;
2551
+ return encodeBase58(fromHex(trimmed));
2552
+ }
2553
+ function sameAccount(a, b) {
2554
+ const left = toAccountHex(a);
2555
+ const right = toAccountHex(b);
2556
+ return HEX_ACCOUNT.test(left) && left === right;
2557
+ }
2558
+
2368
2559
  // src/token-store/index.ts
2369
2560
  var MemoryTokenStore = class {
2370
2561
  constructor() {