@calimero-network/mero-js 25.0.3 → 25.0.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/dist/index.cjs CHANGED
@@ -836,6 +836,14 @@ function createRetryableMethod(method, retryOptions = {}) {
836
836
  };
837
837
  }
838
838
 
839
+ // src/http-client/path-segment.ts
840
+ function pathSegment(id) {
841
+ if (id === "" || id === "." || id === "..") {
842
+ throw new TypeError(`invalid request path segment: ${JSON.stringify(id)}`);
843
+ }
844
+ return encodeURIComponent(id);
845
+ }
846
+
839
847
  // src/auth-api/auth-client.ts
840
848
  var AuthApiClient = class {
841
849
  constructor(httpClient) {
@@ -937,7 +945,7 @@ var AuthApiClient = class {
937
945
  return response.data;
938
946
  }
939
947
  async deleteRootKey(keyId) {
940
- return this.httpClient.delete(`/admin/keys/${keyId}`);
948
+ return this.httpClient.delete(`/admin/keys/${pathSegment(keyId)}`);
941
949
  }
942
950
  // Client Management Endpoints
943
951
  async listClientKeys() {
@@ -954,18 +962,18 @@ var AuthApiClient = class {
954
962
  }
955
963
  async deleteClientKey(keyId, clientId) {
956
964
  return this.httpClient.delete(
957
- `/admin/keys/${keyId}/clients/${clientId}`
965
+ `/admin/keys/${pathSegment(keyId)}/clients/${pathSegment(clientId)}`
958
966
  );
959
967
  }
960
968
  // Permission Management Endpoints
961
969
  async getKeyPermissions(keyId) {
962
970
  return this.httpClient.get(
963
- `/admin/keys/${keyId}/permissions`
971
+ `/admin/keys/${pathSegment(keyId)}/permissions`
964
972
  );
965
973
  }
966
974
  async updateKeyPermissions(keyId, changes) {
967
975
  return this.httpClient.put(
968
- `/admin/keys/${keyId}/permissions`,
976
+ `/admin/keys/${pathSegment(keyId)}/permissions`,
969
977
  { add: changes.add, remove: changes.remove }
970
978
  );
971
979
  }
@@ -1387,13 +1395,13 @@ var AdminApiClient = class {
1387
1395
  return unwrap(await this.httpClient.post("/admin-api/install-dev-application", request));
1388
1396
  }
1389
1397
  async uninstallApplication(appId) {
1390
- return unwrap(await this.httpClient.delete(`/admin-api/applications/${appId}`));
1398
+ return unwrap(await this.httpClient.delete(`/admin-api/applications/${pathSegment(appId)}`));
1391
1399
  }
1392
1400
  async listApplications() {
1393
1401
  return unwrap(await this.httpClient.get("/admin-api/applications"));
1394
1402
  }
1395
1403
  async getApplication(appId) {
1396
- return unwrap(await this.httpClient.get(`/admin-api/applications/${appId}`));
1404
+ return unwrap(await this.httpClient.get(`/admin-api/applications/${pathSegment(appId)}`));
1397
1405
  }
1398
1406
  /**
1399
1407
  * Installed-blob inventory for an application — one entry per locally installed
@@ -1402,7 +1410,7 @@ var AdminApiClient = class {
1402
1410
  */
1403
1411
  async listApplicationVersions(applicationId) {
1404
1412
  return unwrap(
1405
- await this.httpClient.get(`/admin-api/applications/${applicationId}/versions`)
1413
+ await this.httpClient.get(`/admin-api/applications/${pathSegment(applicationId)}/versions`)
1406
1414
  );
1407
1415
  }
1408
1416
  /**
@@ -1413,7 +1421,7 @@ var AdminApiClient = class {
1413
1421
  const query = serviceName ? `?service_name=${encodeURIComponent(serviceName)}` : "";
1414
1422
  return unwrap(
1415
1423
  await this.httpClient.get(
1416
- `/admin-api/applications/${applicationId}/abi${query}`
1424
+ `/admin-api/applications/${pathSegment(applicationId)}/abi${query}`
1417
1425
  )
1418
1426
  );
1419
1427
  }
@@ -1426,13 +1434,13 @@ var AdminApiClient = class {
1426
1434
  }
1427
1435
  async listPackageVersions(packageName) {
1428
1436
  const r = await this.httpClient.get(
1429
- `/admin-api/packages/${encodeURIComponent(packageName)}/versions`
1437
+ `/admin-api/packages/${pathSegment(packageName)}/versions`
1430
1438
  );
1431
1439
  return r?.data ?? r;
1432
1440
  }
1433
1441
  async getLatestPackageVersion(packageName) {
1434
1442
  return this.httpClient.get(
1435
- `/admin-api/packages/${encodeURIComponent(packageName)}/latest`
1443
+ `/admin-api/packages/${pathSegment(packageName)}/latest`
1436
1444
  );
1437
1445
  }
1438
1446
  // ---- Context Management ----
@@ -1441,43 +1449,43 @@ var AdminApiClient = class {
1441
1449
  return unwrap(await this.httpClient.post("/admin-api/contexts", body));
1442
1450
  }
1443
1451
  async deleteContext(contextId) {
1444
- return unwrap(await this.httpClient.delete(`/admin-api/contexts/${contextId}`));
1452
+ return unwrap(await this.httpClient.delete(`/admin-api/contexts/${pathSegment(contextId)}`));
1445
1453
  }
1446
1454
  async getContexts() {
1447
1455
  return unwrap(await this.httpClient.get("/admin-api/contexts"));
1448
1456
  }
1449
1457
  async getContext(contextId) {
1450
- return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}`));
1458
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${pathSegment(contextId)}`));
1451
1459
  }
1452
1460
  async getContextsForApplication(applicationId) {
1453
- return unwrap(await this.httpClient.get(`/admin-api/contexts/for-application/${applicationId}`));
1461
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/for-application/${pathSegment(applicationId)}`));
1454
1462
  }
1455
1463
  // ---- Context Identity ----
1456
1464
  async generateContextIdentity() {
1457
1465
  return unwrap(await this.httpClient.post("/admin-api/identity/context", {}));
1458
1466
  }
1459
1467
  async getContextIdentities(contextId) {
1460
- return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}/identities`));
1468
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${pathSegment(contextId)}/identities`));
1461
1469
  }
1462
1470
  async getContextIdentitiesOwned(contextId) {
1463
- return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}/identities-owned`));
1471
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${pathSegment(contextId)}/identities-owned`));
1464
1472
  }
1465
1473
  // ---- Context join (group membership) ----
1466
1474
  async joinContext(contextId) {
1467
1475
  return unwrap(
1468
- await this.httpClient.post(`/admin-api/contexts/${contextId}/join`, {})
1476
+ await this.httpClient.post(`/admin-api/contexts/${pathSegment(contextId)}/join`, {})
1469
1477
  );
1470
1478
  }
1471
1479
  // ---- Context group / storage / sync ----
1472
1480
  async getContextGroup(contextId) {
1473
- return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}/group`));
1481
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${pathSegment(contextId)}/group`));
1474
1482
  }
1475
1483
  async getContextStorage(contextId) {
1476
- return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}/storage`));
1484
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${pathSegment(contextId)}/storage`));
1477
1485
  }
1478
1486
  /** Sync one context, or every context when `contextId` is omitted. */
1479
1487
  async syncContext(contextId) {
1480
- const path = contextId ? `/admin-api/contexts/sync/${contextId}` : "/admin-api/contexts/sync";
1488
+ const path = contextId ? `/admin-api/contexts/sync/${pathSegment(contextId)}` : "/admin-api/contexts/sync";
1481
1489
  await this.httpClient.post(path, {});
1482
1490
  }
1483
1491
  /**
@@ -1487,7 +1495,7 @@ var AdminApiClient = class {
1487
1495
  */
1488
1496
  async resyncContext(contextId, request = {}) {
1489
1497
  const r = await this.httpClient.post(
1490
- `/admin-api/contexts/${contextId}/resync`,
1498
+ `/admin-api/contexts/${pathSegment(contextId)}/resync`,
1491
1499
  request
1492
1500
  );
1493
1501
  return r ?? { contextId, resyncStarted: true };
@@ -1501,10 +1509,10 @@ var AdminApiClient = class {
1501
1509
  );
1502
1510
  }
1503
1511
  async updateContextApplication(contextId, request) {
1504
- await this.httpClient.post(`/admin-api/contexts/${contextId}/application`, request);
1512
+ await this.httpClient.post(`/admin-api/contexts/${pathSegment(contextId)}/application`, request);
1505
1513
  }
1506
1514
  async getContextsWithExecutorsForApplication(applicationId) {
1507
- const r = await this.httpClient.get(`/admin-api/contexts/with-executors/for-application/${applicationId}`);
1515
+ const r = await this.httpClient.get(`/admin-api/contexts/with-executors/for-application/${pathSegment(applicationId)}`);
1508
1516
  if (Array.isArray(r)) return r;
1509
1517
  return r?.contexts ?? r?.data ?? [];
1510
1518
  }
@@ -1526,7 +1534,7 @@ var AdminApiClient = class {
1526
1534
  }
1527
1535
  async deleteBlob(blobId) {
1528
1536
  const body = await this.httpClient.delete(
1529
- `/admin-api/blobs/${blobId}`
1537
+ `/admin-api/blobs/${pathSegment(blobId)}`
1530
1538
  );
1531
1539
  return { blobId: body.blob_id, deleted: body.deleted };
1532
1540
  }
@@ -1544,9 +1552,9 @@ var AdminApiClient = class {
1544
1552
  * side from drifting from the write side.
1545
1553
  */
1546
1554
  blobPath(blobId, options) {
1547
- if (!options?.contextId) return `/admin-api/blobs/${blobId}`;
1555
+ if (!options?.contextId) return `/admin-api/blobs/${pathSegment(blobId)}`;
1548
1556
  const params = new URLSearchParams({ context_id: options.contextId });
1549
- return `/admin-api/blobs/${blobId}?${params.toString()}`;
1557
+ return `/admin-api/blobs/${pathSegment(blobId)}?${params.toString()}`;
1550
1558
  }
1551
1559
  /**
1552
1560
  * Download a blob's raw bytes. `GET /admin-api/blobs/:id` streams the blob
@@ -1611,7 +1619,7 @@ var AdminApiClient = class {
1611
1619
  async lookupContextAlias(name) {
1612
1620
  return unwrap(
1613
1621
  await this.httpClient.post(
1614
- `/admin-api/alias/lookup/context/${encodeURIComponent(name)}`,
1622
+ `/admin-api/alias/lookup/context/${pathSegment(name)}`,
1615
1623
  {}
1616
1624
  )
1617
1625
  );
@@ -1619,7 +1627,7 @@ var AdminApiClient = class {
1619
1627
  async lookupApplicationAlias(name) {
1620
1628
  return unwrap(
1621
1629
  await this.httpClient.post(
1622
- `/admin-api/alias/lookup/application/${encodeURIComponent(name)}`,
1630
+ `/admin-api/alias/lookup/application/${pathSegment(name)}`,
1623
1631
  {}
1624
1632
  )
1625
1633
  );
@@ -1627,7 +1635,7 @@ var AdminApiClient = class {
1627
1635
  async deleteContextAlias(name) {
1628
1636
  return unwrap(
1629
1637
  await this.httpClient.post(
1630
- `/admin-api/alias/delete/context/${encodeURIComponent(name)}`,
1638
+ `/admin-api/alias/delete/context/${pathSegment(name)}`,
1631
1639
  {}
1632
1640
  )
1633
1641
  );
@@ -1635,7 +1643,7 @@ var AdminApiClient = class {
1635
1643
  async deleteApplicationAlias(name) {
1636
1644
  return unwrap(
1637
1645
  await this.httpClient.post(
1638
- `/admin-api/alias/delete/application/${encodeURIComponent(name)}`,
1646
+ `/admin-api/alias/delete/application/${pathSegment(name)}`,
1639
1647
  {}
1640
1648
  )
1641
1649
  );
@@ -1657,7 +1665,7 @@ var AdminApiClient = class {
1657
1665
  async lookupDeviceAlias(name) {
1658
1666
  return unwrap(
1659
1667
  await this.httpClient.post(
1660
- `/admin-api/alias/lookup/device/${encodeURIComponent(name)}`,
1668
+ `/admin-api/alias/lookup/device/${pathSegment(name)}`,
1661
1669
  {}
1662
1670
  )
1663
1671
  );
@@ -1665,7 +1673,7 @@ var AdminApiClient = class {
1665
1673
  async deleteDeviceAlias(name) {
1666
1674
  return unwrap(
1667
1675
  await this.httpClient.post(
1668
- `/admin-api/alias/delete/device/${encodeURIComponent(name)}`,
1676
+ `/admin-api/alias/delete/device/${pathSegment(name)}`,
1669
1677
  {}
1670
1678
  )
1671
1679
  );
@@ -1678,7 +1686,7 @@ var AdminApiClient = class {
1678
1686
  return unwrap(await this.httpClient.get("/admin-api/namespaces"));
1679
1687
  }
1680
1688
  async getNamespace(namespaceId) {
1681
- return unwrap(await this.httpClient.get(`/admin-api/namespaces/${namespaceId}`));
1689
+ return unwrap(await this.httpClient.get(`/admin-api/namespaces/${pathSegment(namespaceId)}`));
1682
1690
  }
1683
1691
  /**
1684
1692
  * Who this node is — the account it writes as, the device it is, the key it
@@ -1743,7 +1751,7 @@ var AdminApiClient = class {
1743
1751
  async performIntent(contextId, request) {
1744
1752
  return unwrap(
1745
1753
  await this.httpClient.post(
1746
- `/admin-api/contexts/${contextId}/intents`,
1754
+ `/admin-api/contexts/${pathSegment(contextId)}/intents`,
1747
1755
  request
1748
1756
  )
1749
1757
  );
@@ -1760,7 +1768,7 @@ var AdminApiClient = class {
1760
1768
  async queryContext(contextId, request) {
1761
1769
  return unwrap(
1762
1770
  await this.httpClient.post(
1763
- `/admin-api/contexts/${contextId}/query`,
1771
+ `/admin-api/contexts/${pathSegment(contextId)}/query`,
1764
1772
  request
1765
1773
  )
1766
1774
  );
@@ -1789,12 +1797,12 @@ var AdminApiClient = class {
1789
1797
  async getIntentRelay(contextId) {
1790
1798
  return unwrap(
1791
1799
  await this.httpClient.get(
1792
- `/admin-api/contexts/${contextId}/intents`
1800
+ `/admin-api/contexts/${pathSegment(contextId)}/intents`
1793
1801
  )
1794
1802
  );
1795
1803
  }
1796
1804
  async listNamespacesForApplication(applicationId) {
1797
- return unwrap(await this.httpClient.get(`/admin-api/namespaces/for-application/${applicationId}`));
1805
+ return unwrap(await this.httpClient.get(`/admin-api/namespaces/for-application/${pathSegment(applicationId)}`));
1798
1806
  }
1799
1807
  async createNamespace(request) {
1800
1808
  return unwrap(
@@ -1803,7 +1811,7 @@ var AdminApiClient = class {
1803
1811
  }
1804
1812
  async deleteNamespace(namespaceId) {
1805
1813
  return unwrap(
1806
- await this.httpClient.request(`/admin-api/namespaces/${namespaceId}`, {
1814
+ await this.httpClient.request(`/admin-api/namespaces/${pathSegment(namespaceId)}`, {
1807
1815
  method: "DELETE",
1808
1816
  body: "{}",
1809
1817
  headers: { "Content-Type": "application/json" }
@@ -1813,7 +1821,7 @@ var AdminApiClient = class {
1813
1821
  async createNamespaceInvitation(namespaceId, request) {
1814
1822
  return unwrap(
1815
1823
  await this.httpClient.post(
1816
- `/admin-api/namespaces/${namespaceId}/invite`,
1824
+ `/admin-api/namespaces/${pathSegment(namespaceId)}/invite`,
1817
1825
  request ?? {}
1818
1826
  )
1819
1827
  );
@@ -1838,7 +1846,7 @@ var AdminApiClient = class {
1838
1846
  async admitJoin(namespaceId, request) {
1839
1847
  return unwrap(
1840
1848
  await this.httpClient.post(
1841
- `/admin-api/namespaces/${namespaceId}/admit`,
1849
+ `/admin-api/namespaces/${pathSegment(namespaceId)}/admit`,
1842
1850
  request,
1843
1851
  { timeoutMs: 65e3 }
1844
1852
  )
@@ -1847,7 +1855,7 @@ var AdminApiClient = class {
1847
1855
  async joinNamespace(namespaceId, request) {
1848
1856
  const data = unwrap(
1849
1857
  await this.httpClient.post(
1850
- `/admin-api/namespaces/${namespaceId}/join`,
1858
+ `/admin-api/namespaces/${pathSegment(namespaceId)}/join`,
1851
1859
  request,
1852
1860
  { timeoutMs: 65e3 }
1853
1861
  )
@@ -1894,13 +1902,13 @@ var AdminApiClient = class {
1894
1902
  async createGroupInNamespace(namespaceId, request) {
1895
1903
  return unwrap(
1896
1904
  await this.httpClient.post(
1897
- `/admin-api/namespaces/${namespaceId}/groups`,
1905
+ `/admin-api/namespaces/${pathSegment(namespaceId)}/groups`,
1898
1906
  request ?? {}
1899
1907
  )
1900
1908
  );
1901
1909
  }
1902
1910
  async listNamespaceGroups(namespaceId) {
1903
- return unwrap(await this.httpClient.get(`/admin-api/namespaces/${namespaceId}/groups`));
1911
+ return unwrap(await this.httpClient.get(`/admin-api/namespaces/${pathSegment(namespaceId)}/groups`));
1904
1912
  }
1905
1913
  // ---- Account Devices & Pairing ----
1906
1914
  /**
@@ -1962,7 +1970,7 @@ var AdminApiClient = class {
1962
1970
  async relinkAccountDevice(deviceId, request) {
1963
1971
  return unwrap(
1964
1972
  await this.httpClient.post(
1965
- `/admin-api/account/devices/${deviceId}/relink`,
1973
+ `/admin-api/account/devices/${pathSegment(deviceId)}/relink`,
1966
1974
  request ?? {}
1967
1975
  )
1968
1976
  );
@@ -1977,7 +1985,7 @@ var AdminApiClient = class {
1977
1985
  async rescopeAccountDevice(deviceId, request) {
1978
1986
  return unwrap(
1979
1987
  await this.httpClient.put(
1980
- `/admin-api/account/devices/${encodeURIComponent(deviceId)}/scope`,
1988
+ `/admin-api/account/devices/${pathSegment(deviceId)}/scope`,
1981
1989
  request
1982
1990
  )
1983
1991
  );
@@ -2028,7 +2036,7 @@ var AdminApiClient = class {
2028
2036
  async labelAccountDevice(deviceId, request) {
2029
2037
  return unwrap(
2030
2038
  await this.httpClient.put(
2031
- `/admin-api/account/devices/${encodeURIComponent(deviceId)}/label`,
2039
+ `/admin-api/account/devices/${pathSegment(deviceId)}/label`,
2032
2040
  request
2033
2041
  )
2034
2042
  );
@@ -2092,7 +2100,7 @@ var AdminApiClient = class {
2092
2100
  async revokeAccountDevice(namespaceId, request) {
2093
2101
  return unwrap(
2094
2102
  await this.httpClient.post(
2095
- `/admin-api/namespaces/${namespaceId}/account/revoke`,
2103
+ `/admin-api/namespaces/${pathSegment(namespaceId)}/account/revoke`,
2096
2104
  request
2097
2105
  )
2098
2106
  );
@@ -2115,14 +2123,14 @@ var AdminApiClient = class {
2115
2123
  async linkAccountDevice(namespaceId, request) {
2116
2124
  return unwrap(
2117
2125
  await this.httpClient.post(
2118
- `/admin-api/namespaces/${namespaceId}/account/link-device`,
2126
+ `/admin-api/namespaces/${pathSegment(namespaceId)}/account/link-device`,
2119
2127
  request
2120
2128
  )
2121
2129
  );
2122
2130
  }
2123
2131
  // ---- Group Management ----
2124
2132
  async getGroupInfo(groupId) {
2125
- return unwrap(await this.httpClient.get(`/admin-api/groups/${groupId}`));
2133
+ return unwrap(await this.httpClient.get(`/admin-api/groups/${pathSegment(groupId)}`));
2126
2134
  }
2127
2135
  /** Thin wrapper over {@link getGroupInfo}: returns the group's `defaultCapabilities` bitmask. */
2128
2136
  async getDefaultCapabilities(groupId) {
@@ -2134,7 +2142,7 @@ var AdminApiClient = class {
2134
2142
  }
2135
2143
  async deleteGroup(groupId) {
2136
2144
  return unwrap(
2137
- await this.httpClient.request(`/admin-api/groups/${groupId}`, {
2145
+ await this.httpClient.request(`/admin-api/groups/${pathSegment(groupId)}`, {
2138
2146
  method: "DELETE",
2139
2147
  body: "{}",
2140
2148
  headers: { "Content-Type": "application/json" }
@@ -2143,7 +2151,7 @@ var AdminApiClient = class {
2143
2151
  }
2144
2152
  async listGroupMembers(groupId) {
2145
2153
  const response = await this.httpClient.get(
2146
- `/admin-api/groups/${groupId}/members`
2154
+ `/admin-api/groups/${pathSegment(groupId)}/members`
2147
2155
  );
2148
2156
  if (!Array.isArray(response?.members)) {
2149
2157
  const safeId = String(groupId).replace(/[\r\n\t\s]/g, "").slice(0, 64);
@@ -2181,7 +2189,7 @@ var AdminApiClient = class {
2181
2189
  if (options?.offset !== void 0) params.set("offset", String(options.offset));
2182
2190
  if (options?.limit !== void 0) params.set("limit", String(options.limit));
2183
2191
  const query = params.toString();
2184
- const base = `/admin-api/groups/${encodeURIComponent(groupId)}/member-devices`;
2192
+ const base = `/admin-api/groups/${pathSegment(groupId)}/member-devices`;
2185
2193
  const response = await this.httpClient.get(
2186
2194
  query ? `${base}?${query}` : base
2187
2195
  );
@@ -2233,7 +2241,7 @@ var AdminApiClient = class {
2233
2241
  * hex (exactly 64). The node accepts either; the two never collide.
2234
2242
  */
2235
2243
  async getWarrantNonce(contextId, authorDeviceKey) {
2236
- const path = `/admin-api/contexts/${encodeURIComponent(contextId)}/warrant-nonce/${encodeURIComponent(authorDeviceKey)}`;
2244
+ const path = `/admin-api/contexts/${pathSegment(contextId)}/warrant-nonce/${pathSegment(authorDeviceKey)}`;
2237
2245
  let raw;
2238
2246
  try {
2239
2247
  raw = await this.httpClient.get(path, { parse: "text" });
@@ -2273,7 +2281,7 @@ var AdminApiClient = class {
2273
2281
  * value passed as `authorProof` to the relay client.
2274
2282
  */
2275
2283
  async getWarrantNonceAsAuthor(contextId, authorProof) {
2276
- const path = `/admin-api/contexts/${encodeURIComponent(contextId)}/warrant-nonce`;
2284
+ const path = `/admin-api/contexts/${pathSegment(contextId)}/warrant-nonce`;
2277
2285
  let raw;
2278
2286
  try {
2279
2287
  raw = await this.httpClient.post(path, { authorProof }, { parse: "text" });
@@ -2295,7 +2303,7 @@ var AdminApiClient = class {
2295
2303
  if (options.offset !== void 0) params.set("offset", String(options.offset));
2296
2304
  if (options.limit !== void 0) params.set("limit", String(options.limit));
2297
2305
  const query = params.toString();
2298
- const path = `/admin-api/groups/${groupId}/member-devices${query ? `?${query}` : ""}`;
2306
+ const path = `/admin-api/groups/${pathSegment(groupId)}/member-devices${query ? `?${query}` : ""}`;
2299
2307
  return this.httpClient.get(path);
2300
2308
  }
2301
2309
  /**
@@ -2306,19 +2314,19 @@ var AdminApiClient = class {
2306
2314
  async sealToAccount(groupId, account, request) {
2307
2315
  return unwrap(
2308
2316
  await this.httpClient.post(
2309
- `/admin-api/groups/${groupId}/accounts/${account}/seal`,
2317
+ `/admin-api/groups/${pathSegment(groupId)}/accounts/${pathSegment(account)}/seal`,
2310
2318
  request
2311
2319
  )
2312
2320
  );
2313
2321
  }
2314
2322
  async listGroupContexts(groupId) {
2315
- return unwrap(await this.httpClient.get(`/admin-api/groups/${groupId}/contexts`));
2323
+ return unwrap(await this.httpClient.get(`/admin-api/groups/${pathSegment(groupId)}/contexts`));
2316
2324
  }
2317
2325
  async addGroupMembers(groupId, request) {
2318
- await this.httpClient.post(`/admin-api/groups/${groupId}/members`, request);
2326
+ await this.httpClient.post(`/admin-api/groups/${pathSegment(groupId)}/members`, request);
2319
2327
  }
2320
2328
  async removeGroupMembers(groupId, request) {
2321
- await this.httpClient.post(`/admin-api/groups/${groupId}/members/remove`, request);
2329
+ await this.httpClient.post(`/admin-api/groups/${pathSegment(groupId)}/members/remove`, request);
2322
2330
  }
2323
2331
  /**
2324
2332
  * `identity` is the member's ACCOUNT (64 hex), as returned by
@@ -2326,7 +2334,7 @@ var AdminApiClient = class {
2326
2334
  * 64 hex, so nothing but the source tells them apart.
2327
2335
  */
2328
2336
  async updateMemberRole(groupId, identity, request) {
2329
- await this.httpClient.put(`/admin-api/groups/${groupId}/members/${identity}/role`, request);
2337
+ await this.httpClient.put(`/admin-api/groups/${pathSegment(groupId)}/members/${pathSegment(identity)}/role`, request);
2330
2338
  }
2331
2339
  /**
2332
2340
  * `identity` is the member's ACCOUNT (64 hex), as returned by
@@ -2336,7 +2344,7 @@ var AdminApiClient = class {
2336
2344
  async getMemberCapabilities(groupId, identity) {
2337
2345
  return unwrap(
2338
2346
  await this.httpClient.get(
2339
- `/admin-api/groups/${groupId}/members/${identity}/capabilities`
2347
+ `/admin-api/groups/${pathSegment(groupId)}/members/${pathSegment(identity)}/capabilities`
2340
2348
  )
2341
2349
  );
2342
2350
  }
@@ -2346,10 +2354,10 @@ var AdminApiClient = class {
2346
2354
  * 64 hex, so nothing but the source tells them apart.
2347
2355
  */
2348
2356
  async setMemberCapabilities(groupId, identity, request) {
2349
- await this.httpClient.put(`/admin-api/groups/${groupId}/members/${identity}/capabilities`, request);
2357
+ await this.httpClient.put(`/admin-api/groups/${pathSegment(groupId)}/members/${pathSegment(identity)}/capabilities`, request);
2350
2358
  }
2351
2359
  async setDefaultCapabilities(groupId, request) {
2352
- await this.httpClient.put(`/admin-api/groups/${groupId}/settings/default-capabilities`, request);
2360
+ await this.httpClient.put(`/admin-api/groups/${pathSegment(groupId)}/settings/default-capabilities`, request);
2353
2361
  }
2354
2362
  /**
2355
2363
  * Make every member admitted to `groupId` from now on able to relay a
@@ -2431,13 +2439,13 @@ var AdminApiClient = class {
2431
2439
  return { changed: true, capabilities: next };
2432
2440
  }
2433
2441
  async setSubgroupVisibility(groupId, request) {
2434
- await this.httpClient.put(`/admin-api/groups/${groupId}/settings/subgroup-visibility`, request);
2442
+ await this.httpClient.put(`/admin-api/groups/${pathSegment(groupId)}/settings/subgroup-visibility`, request);
2435
2443
  }
2436
2444
  async setTeeAdmissionPolicy(groupId, request) {
2437
- await this.httpClient.put(`/admin-api/groups/${groupId}/settings/tee-admission-policy`, request);
2445
+ await this.httpClient.put(`/admin-api/groups/${pathSegment(groupId)}/settings/tee-admission-policy`, request);
2438
2446
  }
2439
2447
  async getTeeAdmissionPolicy(groupId) {
2440
- const response = await this.httpClient.get(`/admin-api/groups/${groupId}/settings/tee-admission-policy`);
2448
+ const response = await this.httpClient.get(`/admin-api/groups/${pathSegment(groupId)}/settings/tee-admission-policy`);
2441
2449
  return response.data ?? response;
2442
2450
  }
2443
2451
  /**
@@ -2447,7 +2455,7 @@ var AdminApiClient = class {
2447
2455
  * route for this policy.
2448
2456
  */
2449
2457
  async setTeeAuthoringPolicy(groupId, request) {
2450
- await this.httpClient.put(`/admin-api/groups/${groupId}/settings/tee-authoring-policy`, request);
2458
+ await this.httpClient.put(`/admin-api/groups/${pathSegment(groupId)}/settings/tee-authoring-policy`, request);
2451
2459
  }
2452
2460
  /**
2453
2461
  * Turns TEE authorship off in the namespace: every TEE loses its authority,
@@ -2459,7 +2467,7 @@ var AdminApiClient = class {
2459
2467
  * (`teeAuthoringPolicyOp([])`); omit it on a node that holds that root.
2460
2468
  */
2461
2469
  async disableTeeAuthoringPolicy(groupId, options = {}) {
2462
- const path = `/admin-api/groups/${groupId}/settings/tee-authoring-policy`;
2470
+ const path = `/admin-api/groups/${pathSegment(groupId)}/settings/tee-authoring-policy`;
2463
2471
  if (options.rootProof === void 0) {
2464
2472
  await this.httpClient.delete(path);
2465
2473
  return;
@@ -2477,14 +2485,14 @@ var AdminApiClient = class {
2477
2485
  * {@link RootGuardedOpRequest}.
2478
2486
  */
2479
2487
  async transferOwnership(groupId, request) {
2480
- await this.httpClient.post(`/admin-api/groups/${groupId}/transfer-ownership`, request);
2488
+ await this.httpClient.post(`/admin-api/groups/${pathSegment(groupId)}/transfer-ownership`, request);
2481
2489
  }
2482
2490
  /**
2483
2491
  * Repoint the namespace's admin pin at `newAdmin`, a member of its root.
2484
2492
  * Owner-only, with the owner account's root proof; see {@link RootGuardedOpRequest}.
2485
2493
  */
2486
2494
  async changeNamespaceAdmin(namespaceId, request) {
2487
- await this.httpClient.post(`/admin-api/namespaces/${namespaceId}/admin`, request);
2495
+ await this.httpClient.post(`/admin-api/namespaces/${pathSegment(namespaceId)}/admin`, request);
2488
2496
  }
2489
2497
  /**
2490
2498
  * Delete a group through the owner-only path: it must hold no contexts, and
@@ -2492,20 +2500,20 @@ var AdminApiClient = class {
2492
2500
  * admin-level cascading delete.
2493
2501
  */
2494
2502
  async ownerDeleteGroup(groupId, request = {}) {
2495
- await this.httpClient.post(`/admin-api/groups/${groupId}/owner-delete`, request);
2503
+ await this.httpClient.post(`/admin-api/groups/${pathSegment(groupId)}/owner-delete`, request);
2496
2504
  }
2497
2505
  // ---- Group / member / context metadata ----
2498
2506
  async setGroupMetadata(groupId, request) {
2499
- await this.httpClient.put(`/admin-api/groups/${groupId}/metadata`, request);
2507
+ await this.httpClient.put(`/admin-api/groups/${pathSegment(groupId)}/metadata`, request);
2500
2508
  }
2501
2509
  async getGroupMetadata(groupId) {
2502
2510
  const response = await this.httpClient.get(
2503
- `/admin-api/groups/${groupId}/metadata`
2511
+ `/admin-api/groups/${pathSegment(groupId)}/metadata`
2504
2512
  );
2505
2513
  return response?.data ?? null;
2506
2514
  }
2507
2515
  async setMemberMetadata(groupId, identity, request) {
2508
- await this.httpClient.put(`/admin-api/groups/${groupId}/members/${identity}/metadata`, request);
2516
+ await this.httpClient.put(`/admin-api/groups/${pathSegment(groupId)}/members/${pathSegment(identity)}/metadata`, request);
2509
2517
  }
2510
2518
  /**
2511
2519
  * `identity` is the member's ACCOUNT (64 hex), as returned by
@@ -2514,28 +2522,28 @@ var AdminApiClient = class {
2514
2522
  */
2515
2523
  async getMemberMetadata(groupId, identity) {
2516
2524
  const response = await this.httpClient.get(
2517
- `/admin-api/groups/${groupId}/members/${identity}/metadata`
2525
+ `/admin-api/groups/${pathSegment(groupId)}/members/${pathSegment(identity)}/metadata`
2518
2526
  );
2519
2527
  return response?.data ?? null;
2520
2528
  }
2521
2529
  async setContextMetadata(groupId, contextId, request) {
2522
- await this.httpClient.put(`/admin-api/groups/${groupId}/contexts/${contextId}/metadata`, request);
2530
+ await this.httpClient.put(`/admin-api/groups/${pathSegment(groupId)}/contexts/${pathSegment(contextId)}/metadata`, request);
2523
2531
  }
2524
2532
  async getContextMetadata(groupId, contextId) {
2525
2533
  const response = await this.httpClient.get(
2526
- `/admin-api/groups/${groupId}/contexts/${contextId}/metadata`
2534
+ `/admin-api/groups/${pathSegment(groupId)}/contexts/${pathSegment(contextId)}/metadata`
2527
2535
  );
2528
2536
  return response?.data ?? null;
2529
2537
  }
2530
2538
  async syncGroup(groupId) {
2531
- return unwrap(await this.httpClient.post(`/admin-api/groups/${groupId}/sync`, {}));
2539
+ return unwrap(await this.httpClient.post(`/admin-api/groups/${pathSegment(groupId)}/sync`, {}));
2532
2540
  }
2533
2541
  async upgradeGroup(groupId, request) {
2534
- return unwrap(await this.httpClient.post(`/admin-api/groups/${groupId}/upgrade`, request));
2542
+ return unwrap(await this.httpClient.post(`/admin-api/groups/${pathSegment(groupId)}/upgrade`, request));
2535
2543
  }
2536
2544
  async getGroupUpgradeStatus(groupId) {
2537
2545
  return unwrap(
2538
- await this.httpClient.get(`/admin-api/groups/${groupId}/upgrade/status`)
2546
+ await this.httpClient.get(`/admin-api/groups/${pathSegment(groupId)}/upgrade/status`)
2539
2547
  );
2540
2548
  }
2541
2549
  /**
@@ -2544,43 +2552,41 @@ var AdminApiClient = class {
2544
2552
  * envelope to unwrap here (unlike most admin reads).
2545
2553
  */
2546
2554
  async getMigrationStatus(namespaceId) {
2547
- const id = encodeURIComponent(namespaceId);
2548
- return this.httpClient.get(`/admin-api/groups/${id}/migration-status`);
2555
+ return this.httpClient.get(`/admin-api/groups/${pathSegment(namespaceId)}/migration-status`);
2549
2556
  }
2550
2557
  /** Per-group cascade-migration snapshots for a namespace. */
2551
2558
  async getCascadeStatus(namespaceId) {
2552
- const id = encodeURIComponent(namespaceId);
2553
2559
  return unwrap(
2554
- await this.httpClient.get(`/admin-api/groups/${id}/cascade-status`)
2560
+ await this.httpClient.get(`/admin-api/groups/${pathSegment(namespaceId)}/cascade-status`)
2555
2561
  );
2556
2562
  }
2557
2563
  async retryGroupUpgrade(groupId) {
2558
2564
  return unwrap(
2559
2565
  await this.httpClient.post(
2560
- `/admin-api/groups/${groupId}/upgrade/retry`,
2566
+ `/admin-api/groups/${pathSegment(groupId)}/upgrade/retry`,
2561
2567
  {}
2562
2568
  )
2563
2569
  );
2564
2570
  }
2565
2571
  /** Move `childGroupId` under `request.newParentId`. */
2566
2572
  async reparentGroup(childGroupId, request) {
2567
- const r = await this.httpClient.post(`/admin-api/groups/${childGroupId}/reparent`, request);
2573
+ const r = await this.httpClient.post(`/admin-api/groups/${pathSegment(childGroupId)}/reparent`, request);
2568
2574
  return r?.data ?? r;
2569
2575
  }
2570
2576
  async listSubgroups(groupId) {
2571
2577
  const response = await this.httpClient.get(
2572
- `/admin-api/groups/${groupId}/subgroups`
2578
+ `/admin-api/groups/${pathSegment(groupId)}/subgroups`
2573
2579
  );
2574
2580
  return response.subgroups ?? response.data ?? [];
2575
2581
  }
2576
2582
  async detachContextFromGroup(groupId, contextId) {
2577
- await this.httpClient.post(`/admin-api/groups/${groupId}/contexts/${contextId}/remove`, {});
2583
+ await this.httpClient.post(`/admin-api/groups/${pathSegment(groupId)}/contexts/${pathSegment(contextId)}/remove`, {});
2578
2584
  }
2579
2585
  // ---- Group Invitation & Join ----
2580
2586
  async createGroupInvitation(groupId, request) {
2581
2587
  return unwrap(
2582
2588
  await this.httpClient.post(
2583
- `/admin-api/groups/${groupId}/invite`,
2589
+ `/admin-api/groups/${pathSegment(groupId)}/invite`,
2584
2590
  request ?? {}
2585
2591
  )
2586
2592
  );
@@ -2593,7 +2599,7 @@ var AdminApiClient = class {
2593
2599
  async joinSubgroupInheritance(groupId) {
2594
2600
  return unwrap(
2595
2601
  await this.httpClient.post(
2596
- `/admin-api/groups/${groupId}/join-via-inheritance`,
2602
+ `/admin-api/groups/${pathSegment(groupId)}/join-via-inheritance`,
2597
2603
  {}
2598
2604
  )
2599
2605
  );
@@ -2635,27 +2641,27 @@ var AdminApiClient = class {
2635
2641
  }
2636
2642
  /** Leave a group (POST /admin-api/groups/:group_id/leave). */
2637
2643
  async leaveGroup(groupId) {
2638
- await this.httpClient.post(`/admin-api/groups/${groupId}/leave`, {});
2644
+ await this.httpClient.post(`/admin-api/groups/${pathSegment(groupId)}/leave`, {});
2639
2645
  }
2640
2646
  /** Leave a context (POST /admin-api/contexts/:context_id/leave). */
2641
2647
  async leaveContext(contextId) {
2642
- await this.httpClient.post(`/admin-api/contexts/${contextId}/leave`, {});
2648
+ await this.httpClient.post(`/admin-api/contexts/${pathSegment(contextId)}/leave`, {});
2643
2649
  }
2644
2650
  /** Leave a namespace (POST /admin-api/namespaces/:namespace_id/leave). */
2645
2651
  async leaveNamespace(namespaceId) {
2646
- await this.httpClient.post(`/admin-api/namespaces/${namespaceId}/leave`, {});
2652
+ await this.httpClient.post(`/admin-api/namespaces/${pathSegment(namespaceId)}/leave`, {});
2647
2653
  }
2648
2654
  /** Issue a group ownership proof (POST /admin-api/groups/:group_id/issue-ownership-proof). */
2649
2655
  async issueOwnershipProof(groupId, request) {
2650
2656
  return this.httpClient.post(
2651
- `/admin-api/groups/${groupId}/issue-ownership-proof`,
2657
+ `/admin-api/groups/${pathSegment(groupId)}/issue-ownership-proof`,
2652
2658
  request
2653
2659
  );
2654
2660
  }
2655
2661
  /** Issue a namespace ownership proof (POST /admin-api/groups/:group_id/issue-namespace-ownership-proof). */
2656
2662
  async issueNamespaceOwnershipProof(groupId, request) {
2657
2663
  return this.httpClient.post(
2658
- `/admin-api/groups/${groupId}/issue-namespace-ownership-proof`,
2664
+ `/admin-api/groups/${pathSegment(groupId)}/issue-namespace-ownership-proof`,
2659
2665
  request
2660
2666
  );
2661
2667
  }
@@ -2668,11 +2674,11 @@ var AdminApiClient = class {
2668
2674
  * 64 hex, so nothing but the source tells them apart.
2669
2675
  */
2670
2676
  async setMemberAutoFollow(groupId, identity, request) {
2671
- await this.httpClient.put(`/admin-api/groups/${groupId}/members/${identity}/auto-follow`, request);
2677
+ await this.httpClient.put(`/admin-api/groups/${pathSegment(groupId)}/members/${pathSegment(identity)}/auto-follow`, request);
2672
2678
  }
2673
2679
  /** Abort a namespace migration (POST /admin-api/groups/:namespace_id/migration/abort). */
2674
2680
  async abortMigration(namespaceId) {
2675
- return this.httpClient.post(`/admin-api/groups/${namespaceId}/migration/abort`, {});
2681
+ return this.httpClient.post(`/admin-api/groups/${pathSegment(namespaceId)}/migration/abort`, {});
2676
2682
  }
2677
2683
  };
2678
2684
 
@@ -8211,7 +8217,7 @@ var RelayClient = class {
8211
8217
  async describe(contextId) {
8212
8218
  const body = await this.json(
8213
8219
  "GET",
8214
- `/admin-api/contexts/${encodeURIComponent(contextId)}/intents`
8220
+ `/admin-api/contexts/${pathSegment(contextId)}/intents`
8215
8221
  );
8216
8222
  return body.data;
8217
8223
  }
@@ -8248,7 +8254,7 @@ var RelayClient = class {
8248
8254
  });
8249
8255
  const body = await this.json(
8250
8256
  "POST",
8251
- `/admin-api/contexts/${encodeURIComponent(contextId)}/intents`,
8257
+ `/admin-api/contexts/${pathSegment(contextId)}/intents`,
8252
8258
  { method, argsJson, warrant, authorProof: this.config.authorProof }
8253
8259
  );
8254
8260
  return { rootHash: body.data.rootHash, returns: body.data.returns ?? null };
@@ -8262,7 +8268,7 @@ var RelayClient = class {
8262
8268
  async presenceIntent(contextId, body) {
8263
8269
  await this.jsonOnce(
8264
8270
  "POST",
8265
- `/admin-api/contexts/${encodeURIComponent(contextId)}/presence-intents`,
8271
+ `/admin-api/contexts/${pathSegment(contextId)}/presence-intents`,
8266
8272
  body
8267
8273
  );
8268
8274
  }
@@ -8276,7 +8282,7 @@ var RelayClient = class {
8276
8282
  const query = opts.author ? `?author=${encodeURIComponent(opts.author)}` : "";
8277
8283
  const body = await this.json(
8278
8284
  "GET",
8279
- `/admin-api/groups/${encodeURIComponent(groupId)}/context-intents${query}`
8285
+ `/admin-api/groups/${pathSegment(groupId)}/context-intents${query}`
8280
8286
  );
8281
8287
  return body.data;
8282
8288
  }
@@ -8340,7 +8346,7 @@ var RelayClient = class {
8340
8346
  });
8341
8347
  const body = await this.json(
8342
8348
  "POST",
8343
- `/admin-api/groups/${encodeURIComponent(input.groupId)}/context-intents`,
8349
+ `/admin-api/groups/${pathSegment(input.groupId)}/context-intents`,
8344
8350
  { warrant, authorProof: this.config.authorProof, initArgs }
8345
8351
  );
8346
8352
  return {
@@ -8356,7 +8362,7 @@ var RelayClient = class {
8356
8362
  async describeGovernance(groupId) {
8357
8363
  const body = await this.json(
8358
8364
  "GET",
8359
- `/admin-api/groups/${encodeURIComponent(groupId)}/governance-intents`
8365
+ `/admin-api/groups/${pathSegment(groupId)}/governance-intents`
8360
8366
  );
8361
8367
  return body.data;
8362
8368
  }
@@ -8488,7 +8494,7 @@ var RelayClient = class {
8488
8494
  return result;
8489
8495
  }
8490
8496
  async postGovernance(groupId, op, warrant) {
8491
- const body = await this.json("POST", `/admin-api/groups/${encodeURIComponent(groupId)}/governance-intents`, {
8497
+ const body = await this.json("POST", `/admin-api/groups/${pathSegment(groupId)}/governance-intents`, {
8492
8498
  warrant,
8493
8499
  authorProof: this.config.authorProof,
8494
8500
  op: hex(op.bytes)
@@ -10191,15 +10197,70 @@ function base643(bytes) {
10191
10197
  return btoa(binary);
10192
10198
  }
10193
10199
 
10200
+ // src/cloud/link-state.ts
10201
+ var KEY2 = "calimero.cloud.link.pending";
10202
+ var STATE_PARAM = "link_state";
10203
+ var LIFETIME_MS = 10 * 60 * 1e3;
10204
+ var ACCOUNT_HEX = /^[0-9a-f]{64}$/;
10205
+ var MAX_PENDING = 20;
10206
+ function readPending(now) {
10207
+ try {
10208
+ const raw = localStorage.getItem(KEY2);
10209
+ const pending = raw ? JSON.parse(raw) : {};
10210
+ return Object.fromEntries(
10211
+ Object.entries(pending).filter(
10212
+ ([, p]) => p && typeof p.expiresAt === "number" && p.expiresAt > now
10213
+ )
10214
+ );
10215
+ } catch {
10216
+ return {};
10217
+ }
10218
+ }
10219
+ function writePending(pending) {
10220
+ try {
10221
+ localStorage.setItem(KEY2, JSON.stringify(pending));
10222
+ } catch {
10223
+ }
10224
+ }
10225
+ function beginLinkState(account, returnTo) {
10226
+ if (!ACCOUNT_HEX.test(account)) {
10227
+ throw new Error("accountId must be 64 lowercase hex characters");
10228
+ }
10229
+ const url = new URL(returnTo);
10230
+ const now = Date.now();
10231
+ const pending = readPending(now);
10232
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
10233
+ const state = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join(
10234
+ ""
10235
+ );
10236
+ pending[state] = { account, expiresAt: now + LIFETIME_MS };
10237
+ writePending(Object.fromEntries(Object.entries(pending).slice(-MAX_PENDING)));
10238
+ url.searchParams.set(STATE_PARAM, state);
10239
+ return url.toString();
10240
+ }
10241
+ function takeLinkState(search) {
10242
+ const state = new URLSearchParams(search).get(STATE_PARAM);
10243
+ const pending = readPending(Date.now());
10244
+ if (!state || !Object.prototype.hasOwnProperty.call(pending, state))
10245
+ return null;
10246
+ const entry = pending[state];
10247
+ delete pending[state];
10248
+ writePending(pending);
10249
+ return entry;
10250
+ }
10251
+ function withoutLinkState(search) {
10252
+ const params = new URLSearchParams(search);
10253
+ params.delete(STATE_PARAM);
10254
+ const rest = params.toString();
10255
+ return rest ? `?${rest}` : "";
10256
+ }
10257
+
10194
10258
  // src/cloud/link-redirect.ts
10195
10259
  var DEFAULT_CLOUD_WEB_URL = "https://cloud.calimero.network";
10196
10260
  function cloudLinkUrl(options) {
10197
- if (!/^[0-9a-f]{64}$/.test(options.accountId)) {
10198
- throw new Error("accountId must be 64 lowercase hex characters");
10199
- }
10200
10261
  const url = new URL(options.cloudWebUrl ?? DEFAULT_CLOUD_WEB_URL);
10201
10262
  url.searchParams.set("link-account", options.accountId);
10202
- url.searchParams.set("callback-url", options.returnTo);
10263
+ url.searchParams.set("callback-url", beginLinkState(options.accountId, options.returnTo));
10203
10264
  return url.toString();
10204
10265
  }
10205
10266
  function beginCloudLink(options) {
@@ -10210,6 +10271,7 @@ function readCloudLinkCallback(location2 = window.location) {
10210
10271
  const params = new URLSearchParams(location2.hash.replace(/^#/, ""));
10211
10272
  const error = params.get("error");
10212
10273
  if (error) {
10274
+ takeLinkState(location2.search ?? "");
10213
10275
  stripFragment();
10214
10276
  throw new Error(
10215
10277
  error === "denied" ? "the account link was declined at the cloud" : `the cloud refused this link: ${error}`
@@ -10218,12 +10280,18 @@ function readCloudLinkCallback(location2 = window.location) {
10218
10280
  const grant = params.get("grant");
10219
10281
  const account = params.get("account");
10220
10282
  if (!grant || !account) return null;
10283
+ if (takeLinkState(location2.search ?? "")?.account !== account) {
10284
+ stripFragment();
10285
+ throw new Error(
10286
+ "this account link answers a request this browser did not make - start the link again from here"
10287
+ );
10288
+ }
10221
10289
  stripFragment();
10222
10290
  return { grant, account };
10223
10291
  }
10224
10292
  function stripFragment() {
10225
10293
  if (typeof history === "undefined" || !history.replaceState) return;
10226
- history.replaceState(null, "", location.pathname + location.search);
10294
+ history.replaceState(null, "", location.pathname + withoutLinkState(location.search));
10227
10295
  }
10228
10296
  async function completeCloudLink(options) {
10229
10297
  const root = await resolveRoot(options.root);
@@ -10243,7 +10311,7 @@ async function completeCloudLink(options) {
10243
10311
  var DEVICE_PARAM = "enrol-device";
10244
10312
  var KEM_PARAM = "enrol-kem";
10245
10313
  var CALLBACK_PARAM = "callback-url";
10246
- var STATE_PARAM = "state";
10314
+ var STATE_PARAM2 = "state";
10247
10315
  function deviceEnrolmentUrl(options) {
10248
10316
  assertHex(options.devicePublicKey, "devicePublicKey");
10249
10317
  assertHex(options.kemPublicKey, "kemPublicKey");
@@ -10253,7 +10321,7 @@ function deviceEnrolmentUrl(options) {
10253
10321
  url.searchParams.set(DEVICE_PARAM, options.devicePublicKey);
10254
10322
  url.searchParams.set(KEM_PARAM, options.kemPublicKey);
10255
10323
  url.searchParams.set(CALLBACK_PARAM, options.returnTo);
10256
- if (options.state) url.searchParams.set(STATE_PARAM, options.state);
10324
+ if (options.state) url.searchParams.set(STATE_PARAM2, options.state);
10257
10325
  return url.toString();
10258
10326
  }
10259
10327
  function beginDeviceEnrolment(options) {
@@ -10420,7 +10488,7 @@ async function resolveRelayFromInvitation(input) {
10420
10488
  relayUrl: chosen.relayUrl,
10421
10489
  // The cloud's ready-made URL when it gave one, so the path is never rebuilt
10422
10490
  // from a guess; the fallback is the documented route on the same origin.
10423
- admitUrl: chosen.admitUrl ?? `${chosen.relayUrl.replace(/\/+$/, "")}/admin-api/namespaces/${input.namespaceId}/admit`,
10491
+ admitUrl: chosen.admitUrl ?? `${chosen.relayUrl.replace(/\/+$/, "")}/admin-api/namespaces/${pathSegment(input.namespaceId)}/admit`,
10424
10492
  admitterAccount: chosen.account,
10425
10493
  peerId: chosen.peerId,
10426
10494
  writable: chosen.canExecute,
@@ -10658,7 +10726,7 @@ async function joinWithNode(input) {
10658
10726
  };
10659
10727
  }
10660
10728
  const body = { invitation: input.invitation, signedOp };
10661
- const url = input.admitUrl ?? `${origin(input.nodeUrl)}/admin-api/namespaces/${input.namespaceId}/admit`;
10729
+ const url = input.admitUrl ?? `${origin(input.nodeUrl)}/admin-api/namespaces/${pathSegment(input.namespaceId)}/admit`;
10662
10730
  const doFetch = input.deps?.fetch ?? fetch;
10663
10731
  let response;
10664
10732
  try {
@@ -11351,7 +11419,6 @@ function joinOf(row) {
11351
11419
  reportedAt: typeof join.reported_at === "string" ? join.reported_at : null
11352
11420
  };
11353
11421
  }
11354
- var ACCOUNT_HEX = /^[0-9a-f]{64}$/;
11355
11422
  function accountHexOrNull(value) {
11356
11423
  return typeof value === "string" && ACCOUNT_HEX.test(value) ? value : null;
11357
11424
  }
@@ -11571,7 +11638,7 @@ var CloudClient = class {
11571
11638
  async getNamespaceRelays(namespaceId) {
11572
11639
  const body = await this.request(
11573
11640
  "GET",
11574
- `/api/cloud/me/namespaces/${encodeURIComponent(namespaceId)}/relays`
11641
+ `/api/cloud/me/namespaces/${pathSegment(namespaceId)}/relays`
11575
11642
  );
11576
11643
  return (body.relays ?? []).map((row) => {
11577
11644
  const relayUrl = row.relay_url ?? null;
@@ -11629,7 +11696,7 @@ var CloudClient = class {
11629
11696
  * a node holding the namespace, so there is no hosted admitter.
11630
11697
  */
11631
11698
  async getNamespaceRouting(namespaceId) {
11632
- const body = await this.request("GET", `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/admitters`, {
11699
+ const body = await this.request("GET", `/api/cloud/namespaces/${pathSegment(namespaceId)}/admitters`, {
11633
11700
  anonymous: true,
11634
11701
  headers: await this.routingProof(namespaceId)
11635
11702
  });
@@ -11673,7 +11740,7 @@ var CloudClient = class {
11673
11740
  * challenge somewhere this client cannot reach the key.
11674
11741
  */
11675
11742
  async getRoutingChallenge(namespaceId) {
11676
- const body = await this.request("GET", `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/challenge`, {
11743
+ const body = await this.request("GET", `/api/cloud/namespaces/${pathSegment(namespaceId)}/challenge`, {
11677
11744
  anonymous: true
11678
11745
  });
11679
11746
  return {
@@ -11704,7 +11771,7 @@ var CloudClient = class {
11704
11771
  * the account from the certificate and refuses a mismatch.
11705
11772
  */
11706
11773
  async getAccountRelaysChallenge(accountId) {
11707
- const body = await this.request("GET", `/api/cloud/accounts/${encodeURIComponent(accountId)}/challenge`, {
11774
+ const body = await this.request("GET", `/api/cloud/accounts/${pathSegment(accountId)}/challenge`, {
11708
11775
  anonymous: true
11709
11776
  });
11710
11777
  return {
@@ -11740,7 +11807,7 @@ var CloudClient = class {
11740
11807
  );
11741
11808
  }
11742
11809
  const challenge = await this.getAccountRelaysChallenge(accountId);
11743
- const body = await this.request("GET", `/api/cloud/accounts/${encodeURIComponent(accountId)}/relays`, {
11810
+ const body = await this.request("GET", `/api/cloud/accounts/${pathSegment(accountId)}/relays`, {
11744
11811
  anonymous: true,
11745
11812
  headers: await routingProofHeaders(challenge, this.routingCredential)
11746
11813
  });
@@ -11864,7 +11931,7 @@ var CloudClient = class {
11864
11931
  const body = options.admitterAddrs === void 0 ? {} : { admitter_addrs: [...options.admitterAddrs] };
11865
11932
  return this.request(
11866
11933
  "POST",
11867
- `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/enable-ha`,
11934
+ `/api/cloud/namespaces/${pathSegment(namespaceId)}/enable-ha`,
11868
11935
  { body }
11869
11936
  );
11870
11937
  }
@@ -11877,7 +11944,7 @@ var CloudClient = class {
11877
11944
  async enableFoundedNamespaceHa(namespaceId, ownershipProof) {
11878
11945
  return this.request(
11879
11946
  "POST",
11880
- `/api/cloud/me/namespaces/${encodeURIComponent(namespaceId)}/enable-ha`,
11947
+ `/api/cloud/me/namespaces/${pathSegment(namespaceId)}/enable-ha`,
11881
11948
  { body: { groups: [], ownership_proof: ownershipProof } }
11882
11949
  );
11883
11950
  }
@@ -11914,7 +11981,7 @@ var CloudClient = class {
11914
11981
  try {
11915
11982
  return await this.request(
11916
11983
  "POST",
11917
- `/api/cloud/accounts/${encodeURIComponent(account)}/namespaces/${encodeURIComponent(namespace)}/enable-ha`,
11984
+ `/api/cloud/accounts/${pathSegment(account)}/namespaces/${pathSegment(namespace)}/enable-ha`,
11918
11985
  { body: { ownership_proof: proof }, anonymous: true }
11919
11986
  );
11920
11987
  } catch (err) {
@@ -11939,7 +12006,7 @@ var CloudClient = class {
11939
12006
  async disableNamespaceHa(namespaceId) {
11940
12007
  return this.request(
11941
12008
  "POST",
11942
- `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/disable-ha`,
12009
+ `/api/cloud/namespaces/${pathSegment(namespaceId)}/disable-ha`,
11943
12010
  { body: {} }
11944
12011
  );
11945
12012
  }
@@ -12079,7 +12146,7 @@ var CloudClient = class {
12079
12146
  const portal = options.portalUrl.replace(/\/+$/, "");
12080
12147
  const query = new URLSearchParams({
12081
12148
  "link-account": options.accountId,
12082
- "callback-url": options.callbackUrl
12149
+ "callback-url": beginLinkState(options.accountId, options.callbackUrl)
12083
12150
  });
12084
12151
  return { url: `${portal}/?${query.toString()}`, accountId: options.accountId };
12085
12152
  }
@@ -12089,14 +12156,19 @@ var CloudClient = class {
12089
12156
  * Returns `{}` when there is nothing to read, so it is safe to call on every
12090
12157
  * load — which is how a redirected app finds out it has been sent back.
12091
12158
  *
12159
+ * A grant is only returned when it answers a handoff this browser began, for the
12160
+ * account that handoff named; anything else comes back as
12161
+ * `error: 'unexpected_callback'`, because the redirect URL can be forwarded.
12162
+ *
12092
12163
  * @param href defaults to the current location, when there is one.
12093
12164
  */
12094
12165
  static readAccountLinkCallback(href) {
12095
12166
  const source = href ?? (typeof window === "undefined" ? "" : window.location.href);
12096
12167
  if (!source) return {};
12097
12168
  let hash;
12169
+ let search;
12098
12170
  try {
12099
- hash = new URL(source).hash;
12171
+ ({ hash, search } = new URL(source));
12100
12172
  } catch {
12101
12173
  return {};
12102
12174
  }
@@ -12105,6 +12177,10 @@ var CloudClient = class {
12105
12177
  const accountId = params.get("account");
12106
12178
  const error = params.get("error");
12107
12179
  const out = {};
12180
+ if (error) takeLinkState(search);
12181
+ if (grant && takeLinkState(search)?.account !== accountId) {
12182
+ return { error: "unexpected_callback" };
12183
+ }
12108
12184
  if (grant) out.grant = grant;
12109
12185
  if (accountId) out.accountId = accountId;
12110
12186
  if (error) out.error = error;