@serfab/cadre-core 1.13.0 → 1.15.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.
Files changed (230) hide show
  1. package/README.md +293 -274
  2. package/dist/cadre-invite-protocol.d.ts +383 -0
  3. package/dist/cadre-invite-protocol.js +774 -0
  4. package/dist/cadre-invite-protocol.js.map +1 -0
  5. package/dist/cadre-join-request.d.ts +42 -0
  6. package/dist/cadre-join-request.js +144 -0
  7. package/dist/cadre-join-request.js.map +1 -0
  8. package/dist/cadre-node.d.ts +849 -210
  9. package/dist/cadre-node.js +1574 -374
  10. package/dist/cadre-node.js.map +1 -1
  11. package/dist/claim-proof.d.ts +45 -0
  12. package/dist/claim-proof.js +132 -0
  13. package/dist/claim-proof.js.map +1 -0
  14. package/dist/control-authorization.d.ts +80 -19
  15. package/dist/control-authorization.js +55 -13
  16. package/dist/control-authorization.js.map +1 -1
  17. package/dist/control-cohort.js +1 -1
  18. package/dist/control-cohort.js.map +1 -1
  19. package/dist/control-database.d.ts +605 -108
  20. package/dist/control-database.js +1299 -280
  21. package/dist/control-database.js.map +1 -1
  22. package/dist/control-protocol-guard.d.ts +142 -0
  23. package/dist/control-protocol-guard.js +293 -0
  24. package/dist/control-protocol-guard.js.map +1 -0
  25. package/dist/control-schema.d.ts +1 -1
  26. package/dist/control-schema.js +817 -1067
  27. package/dist/control-schema.js.map +1 -1
  28. package/dist/control-stream.d.ts +4 -0
  29. package/dist/control-stream.js +22 -7
  30. package/dist/control-stream.js.map +1 -1
  31. package/dist/delegate-admission.d.ts +1 -1
  32. package/dist/delegate-admission.js +1 -1
  33. package/dist/device-token.d.ts +11 -9
  34. package/dist/device-token.js +14 -12
  35. package/dist/device-token.js.map +1 -1
  36. package/dist/ed25519-key.d.ts +22 -9
  37. package/dist/ed25519-key.js +47 -10
  38. package/dist/ed25519-key.js.map +1 -1
  39. package/dist/fetch-impl.d.ts +12 -0
  40. package/dist/fetch-impl.js +23 -0
  41. package/dist/fetch-impl.js.map +1 -0
  42. package/dist/file-durable-slot.js +2 -1
  43. package/dist/file-durable-slot.js.map +1 -1
  44. package/dist/file-safe-name.d.ts +25 -0
  45. package/dist/file-safe-name.js +29 -0
  46. package/dist/file-safe-name.js.map +1 -0
  47. package/dist/formation-approval.js +5 -11
  48. package/dist/formation-approval.js.map +1 -1
  49. package/dist/fs-atomic.d.ts +0 -19
  50. package/dist/fs-atomic.js +3 -24
  51. package/dist/fs-atomic.js.map +1 -1
  52. package/dist/index.d.ts +22 -10
  53. package/dist/index.js +54 -11
  54. package/dist/index.js.map +1 -1
  55. package/dist/invitation-bootstrap.d.ts +50 -12
  56. package/dist/invitation-bootstrap.js +76 -13
  57. package/dist/invitation-bootstrap.js.map +1 -1
  58. package/dist/joined-strand-store.d.ts +7 -0
  59. package/dist/joined-strand-store.js +9 -0
  60. package/dist/joined-strand-store.js.map +1 -1
  61. package/dist/key-store-file.js +2 -1
  62. package/dist/key-store-file.js.map +1 -1
  63. package/dist/link-budget.d.ts +25 -33
  64. package/dist/link-budget.js +25 -35
  65. package/dist/link-budget.js.map +1 -1
  66. package/dist/machine-removal.d.ts +78 -0
  67. package/dist/machine-removal.js +166 -0
  68. package/dist/machine-removal.js.map +1 -0
  69. package/dist/membership-connection-gater.d.ts +191 -151
  70. package/dist/membership-connection-gater.js +236 -173
  71. package/dist/membership-connection-gater.js.map +1 -1
  72. package/dist/node-claim-payload.d.ts +74 -0
  73. package/dist/node-claim-payload.js +224 -0
  74. package/dist/node-claim-payload.js.map +1 -0
  75. package/dist/node-default-transports.d.ts +27 -0
  76. package/dist/node-default-transports.js +35 -0
  77. package/dist/node-default-transports.js.map +1 -0
  78. package/dist/node-transports.d.ts +30 -0
  79. package/dist/node-transports.js +40 -0
  80. package/dist/node-transports.js.map +1 -0
  81. package/dist/owner-anchor-sync.d.ts +22 -0
  82. package/dist/owner-anchor-sync.js +143 -0
  83. package/dist/owner-anchor-sync.js.map +1 -0
  84. package/dist/peer-authorization.d.ts +113 -18
  85. package/dist/peer-authorization.js +225 -29
  86. package/dist/peer-authorization.js.map +1 -1
  87. package/dist/peer-dial.d.ts +143 -27
  88. package/dist/peer-dial.js +276 -31
  89. package/dist/peer-dial.js.map +1 -1
  90. package/dist/peer-join-backfill.d.ts +26 -2
  91. package/dist/peer-join-backfill.js +60 -12
  92. package/dist/peer-join-backfill.js.map +1 -1
  93. package/dist/peer-record.d.ts +17 -52
  94. package/dist/peer-record.js +20 -58
  95. package/dist/peer-record.js.map +1 -1
  96. package/dist/pending-join-runner.d.ts +66 -58
  97. package/dist/pending-join-runner.js +136 -145
  98. package/dist/pending-join-runner.js.map +1 -1
  99. package/dist/primary-lan-address.d.ts +8 -0
  100. package/dist/primary-lan-address.js +46 -0
  101. package/dist/primary-lan-address.js.map +1 -0
  102. package/dist/provider/client.d.ts +131 -0
  103. package/dist/provider/client.js +226 -0
  104. package/dist/provider/client.js.map +1 -0
  105. package/dist/provider/descriptor.d.ts +41 -0
  106. package/dist/provider/descriptor.js +103 -0
  107. package/dist/provider/descriptor.js.map +1 -0
  108. package/dist/provider/index.d.ts +9 -0
  109. package/dist/provider/index.js +10 -0
  110. package/dist/provider/index.js.map +1 -0
  111. package/dist/provider/oauth.d.ts +125 -0
  112. package/dist/provider/oauth.js +199 -0
  113. package/dist/provider/oauth.js.map +1 -0
  114. package/dist/provider/rent-node.d.ts +101 -0
  115. package/dist/provider/rent-node.js +220 -0
  116. package/dist/provider/rent-node.js.map +1 -0
  117. package/dist/push-fanout.d.ts +8 -2
  118. package/dist/push-fanout.js +8 -2
  119. package/dist/push-fanout.js.map +1 -1
  120. package/dist/relay-addrs.d.ts +9 -4
  121. package/dist/relay-addrs.js +9 -4
  122. package/dist/relay-addrs.js.map +1 -1
  123. package/dist/relay-reservation.d.ts +51 -16
  124. package/dist/relay-reservation.js +107 -24
  125. package/dist/relay-reservation.js.map +1 -1
  126. package/dist/seed-bootstrap.d.ts +165 -123
  127. package/dist/seed-bootstrap.js +313 -275
  128. package/dist/seed-bootstrap.js.map +1 -1
  129. package/dist/seed-trust-policy.d.ts +98 -37
  130. package/dist/seed-trust-policy.js +188 -2
  131. package/dist/seed-trust-policy.js.map +1 -1
  132. package/dist/strand-addr-protocol.d.ts +24 -26
  133. package/dist/strand-addr-protocol.js +19 -26
  134. package/dist/strand-addr-protocol.js.map +1 -1
  135. package/dist/strand-commit-durability.d.ts +54 -0
  136. package/dist/strand-commit-durability.js +74 -0
  137. package/dist/strand-commit-durability.js.map +1 -0
  138. package/dist/strand-database.d.ts +23 -8
  139. package/dist/strand-database.js +41 -13
  140. package/dist/strand-database.js.map +1 -1
  141. package/dist/strand-first-sync-gate.d.ts +7 -1
  142. package/dist/strand-first-sync-gate.js +7 -1
  143. package/dist/strand-first-sync-gate.js.map +1 -1
  144. package/dist/strand-formation-protocol.js +5 -2
  145. package/dist/strand-formation-protocol.js.map +1 -1
  146. package/dist/strand-fret-addrs.d.ts +19 -1
  147. package/dist/strand-fret-addrs.js +27 -9
  148. package/dist/strand-fret-addrs.js.map +1 -1
  149. package/dist/strand-instance-manager.d.ts +81 -19
  150. package/dist/strand-instance-manager.js +216 -105
  151. package/dist/strand-instance-manager.js.map +1 -1
  152. package/dist/strand-move.d.ts +124 -0
  153. package/dist/strand-move.js +438 -0
  154. package/dist/strand-move.js.map +1 -0
  155. package/dist/strand-network-config.js +7 -6
  156. package/dist/strand-network-config.js.map +1 -1
  157. package/dist/strand-peer-redial.d.ts +113 -0
  158. package/dist/strand-peer-redial.js +236 -0
  159. package/dist/strand-peer-redial.js.map +1 -0
  160. package/dist/strand-revocation-enforcer.d.ts +11 -11
  161. package/dist/strand-revocation-enforcer.js +11 -11
  162. package/dist/strand-wake-protocol.d.ts +13 -16
  163. package/dist/strand-wake-protocol.js +23 -27
  164. package/dist/strand-wake-protocol.js.map +1 -1
  165. package/dist/trusted-owner-store.d.ts +35 -5
  166. package/dist/trusted-owner-store.js +50 -8
  167. package/dist/trusted-owner-store.js.map +1 -1
  168. package/dist/types.d.ts +512 -89
  169. package/dist/types.js +10 -0
  170. package/dist/types.js.map +1 -1
  171. package/package.json +19 -10
  172. package/src/cadre-invite-protocol.ts +1052 -0
  173. package/src/cadre-join-request.ts +167 -0
  174. package/src/cadre-node.ts +1703 -386
  175. package/src/claim-proof.ts +147 -0
  176. package/src/control-authorization.ts +203 -117
  177. package/src/control-cohort.ts +1 -1
  178. package/src/control-database.ts +4663 -3453
  179. package/src/control-protocol-guard.ts +348 -0
  180. package/src/control-schema.ts +828 -1078
  181. package/src/control-stream.ts +26 -7
  182. package/src/delegate-admission.ts +1 -1
  183. package/src/device-token.ts +14 -10
  184. package/src/ed25519-key.ts +47 -10
  185. package/src/fetch-impl.ts +23 -0
  186. package/src/file-durable-slot.ts +64 -63
  187. package/src/file-safe-name.ts +32 -0
  188. package/src/formation-approval.ts +604 -610
  189. package/src/fs-atomic.ts +92 -117
  190. package/src/index.ts +789 -648
  191. package/src/invitation-bootstrap.ts +91 -13
  192. package/src/joined-strand-store.ts +508 -498
  193. package/src/key-store-file.ts +142 -141
  194. package/src/link-budget.ts +25 -36
  195. package/src/machine-removal.ts +216 -0
  196. package/src/membership-connection-gater.ts +259 -191
  197. package/src/node-claim-payload.ts +259 -0
  198. package/src/node-default-transports.ts +38 -0
  199. package/src/node-transports.ts +49 -0
  200. package/src/owner-anchor-sync.ts +171 -0
  201. package/src/peer-authorization.ts +258 -29
  202. package/src/peer-dial.ts +604 -315
  203. package/src/peer-join-backfill.ts +62 -11
  204. package/src/peer-record.ts +159 -197
  205. package/src/pending-join-runner.ts +729 -735
  206. package/src/primary-lan-address.ts +47 -0
  207. package/src/provider/client.ts +326 -0
  208. package/src/provider/descriptor.ts +129 -0
  209. package/src/provider/index.ts +59 -0
  210. package/src/provider/oauth.ts +285 -0
  211. package/src/provider/rent-node.ts +275 -0
  212. package/src/push-fanout.ts +8 -2
  213. package/src/relay-addrs.ts +9 -4
  214. package/src/relay-reservation.ts +1211 -1121
  215. package/src/seed-bootstrap.ts +1502 -1448
  216. package/src/seed-trust-policy.ts +353 -140
  217. package/src/strand-addr-protocol.ts +26 -35
  218. package/src/strand-commit-durability.ts +116 -0
  219. package/src/strand-database.ts +46 -14
  220. package/src/strand-first-sync-gate.ts +7 -1
  221. package/src/strand-formation-protocol.ts +1039 -1036
  222. package/src/strand-fret-addrs.ts +179 -145
  223. package/src/strand-instance-manager.ts +2026 -1883
  224. package/src/strand-move.ts +585 -0
  225. package/src/strand-network-config.ts +7 -6
  226. package/src/strand-peer-redial.ts +307 -0
  227. package/src/strand-revocation-enforcer.ts +11 -11
  228. package/src/strand-wake-protocol.ts +23 -30
  229. package/src/trusted-owner-store.ts +242 -171
  230. package/src/types.ts +2687 -2242
@@ -0,0 +1,103 @@
1
+ /**
2
+ * The provider descriptor: what a hosting provider says about itself (`GET /descriptor`,
3
+ * `docs/architecture.md` → "Provider descriptor and sign-in"), as an app's store screen
4
+ * renders it and as the sign-in and the provider client read it. The parser refuses, with a
5
+ * plain message, a document the rest of this directory could not work with.
6
+ *
7
+ * Imports only the package's `fetch` helper: it loads on React Native and in the browser as
8
+ * well as Node.
9
+ */
10
+ import { requireFetch } from '../fetch-impl.js';
11
+ /**
12
+ * Read a descriptor document. Refuses: any version but 1; a missing or blank `id` or `name`;
13
+ * an `apiBaseUrl`, `oauth.authorizeUrl`, `oauth.tokenUrl` or `plansUrl` that is not an
14
+ * absolute URL, or is `http` at a host other than loopback, since the node code and the tokens
15
+ * travel over them; a `termsUrl` or `icon` that is not an absolute web URL; `regions` or
16
+ * `oauth.scopes` that are not string lists. A descriptor without `oauth` is refused too:
17
+ * no app could sign in to that provider.
18
+ */
19
+ export function parseProviderDescriptor(json) {
20
+ const doc = requireObject(json, 'A provider descriptor');
21
+ if (doc.v !== 1)
22
+ throw new Error(`A provider descriptor must carry v: 1 (this one carries ${JSON.stringify(doc.v)})`);
23
+ const id = requireText(doc.id, 'id');
24
+ const name = requireText(doc.name, 'name');
25
+ const apiBaseUrl = requireApiUrl(doc.apiBaseUrl, 'apiBaseUrl');
26
+ const plansUrl = requireApiUrl(doc.plansUrl, 'plansUrl');
27
+ const oauth = requireObject(doc.oauth, 'A provider descriptor\'s oauth block');
28
+ const parsed = {
29
+ v: 1,
30
+ id,
31
+ name,
32
+ apiBaseUrl,
33
+ oauth: {
34
+ authorizeUrl: requireApiUrl(oauth.authorizeUrl, 'oauth.authorizeUrl'),
35
+ tokenUrl: requireApiUrl(oauth.tokenUrl, 'oauth.tokenUrl'),
36
+ scopes: requireStrings(oauth.scopes, 'oauth.scopes'),
37
+ },
38
+ plansUrl,
39
+ };
40
+ if (doc.termsUrl !== undefined)
41
+ parsed.termsUrl = requireWebUrl(doc.termsUrl, 'termsUrl');
42
+ if (doc.icon !== undefined)
43
+ parsed.icon = requireWebUrl(doc.icon, 'icon');
44
+ if (doc.regions !== undefined)
45
+ parsed.regions = requireStrings(doc.regions, 'regions');
46
+ return parsed;
47
+ }
48
+ /** `GET url`, parsed with {@link parseProviderDescriptor}, for an app that holds a URL rather than the document. */
49
+ export async function fetchProviderDescriptor(url, fetchImpl) {
50
+ const doFetch = requireFetch(fetchImpl);
51
+ const response = await doFetch(url, { headers: { accept: 'application/json' } });
52
+ if (!response.ok) {
53
+ throw new Error(`The provider descriptor at ${url} answered ${response.status}`);
54
+ }
55
+ return parseProviderDescriptor(await response.json());
56
+ }
57
+ /** Hosts at which an API URL may be plain `http`: a dev run on this machine. */
58
+ const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);
59
+ function requireObject(value, what) {
60
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
61
+ throw new Error(`${what} must be a JSON object`);
62
+ }
63
+ return value;
64
+ }
65
+ function requireText(value, field) {
66
+ if (typeof value !== 'string' || value.trim() === '') {
67
+ throw new Error(`A provider descriptor's ${field} must be a non-blank string`);
68
+ }
69
+ return value;
70
+ }
71
+ function requireStrings(value, field) {
72
+ if (!Array.isArray(value) || !value.every((entry) => typeof entry === 'string')) {
73
+ throw new Error(`A provider descriptor's ${field} must be a list of strings`);
74
+ }
75
+ return [...value];
76
+ }
77
+ /** An absolute `http(s)` URL, as a link a store screen opens. */
78
+ function requireWebUrl(value, field) {
79
+ const text = requireText(value, field);
80
+ const url = parseUrl(text, field);
81
+ if (url.protocol !== 'https:' && url.protocol !== 'http:') {
82
+ throw new Error(`A provider descriptor's ${field} must be an http or https URL (${text} is not)`);
83
+ }
84
+ return text;
85
+ }
86
+ /** {@link requireWebUrl}, and `https` unless the host is loopback: tokens and the node code travel over it. */
87
+ function requireApiUrl(value, field) {
88
+ const text = requireWebUrl(value, field);
89
+ const url = new URL(text);
90
+ if (url.protocol === 'http:' && !LOOPBACK_HOSTS.has(url.hostname)) {
91
+ throw new Error(`A provider descriptor's ${field} must be https unless its host is loopback (${text} is plain http at ${url.hostname})`);
92
+ }
93
+ return text;
94
+ }
95
+ function parseUrl(text, field) {
96
+ try {
97
+ return new URL(text);
98
+ }
99
+ catch (error) {
100
+ throw new Error(`A provider descriptor's ${field} must be an absolute URL (${text} is not)`, { cause: error });
101
+ }
102
+ }
103
+ //# sourceMappingURL=descriptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"descriptor.js","sourceRoot":"","sources":["../../src/provider/descriptor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAwBhD;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAa;IACpD,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,EAAE,uBAAuB,CAAC,CAAC;IACzD,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,2DAA2D,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACtH,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,aAAa,CAAC,GAAG,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAC/D,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IACzD,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,KAAK,EAAE,sCAAsC,CAAC,CAAC;IAC/E,MAAM,MAAM,GAAuB;QAClC,CAAC,EAAE,CAAC;QACJ,EAAE;QACF,IAAI;QACJ,UAAU;QACV,KAAK,EAAE;YACN,YAAY,EAAE,aAAa,CAAC,KAAK,CAAC,YAAY,EAAE,oBAAoB,CAAC;YACrE,QAAQ,EAAE,aAAa,CAAC,KAAK,CAAC,QAAQ,EAAE,gBAAgB,CAAC;YACzD,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,cAAc,CAAC;SACpD;QACD,QAAQ;KACR,CAAC;IACF,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS;QAAE,MAAM,CAAC,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC1F,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS;QAAE,MAAM,CAAC,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1E,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS;QAAE,MAAM,CAAC,OAAO,GAAG,cAAc,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACvF,OAAO,MAAM,CAAC;AACf,CAAC;AAED,oHAAoH;AACpH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,GAAW,EAAE,SAAwB;IAClF,MAAM,OAAO,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAC;IACjF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,aAAa,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,uBAAuB,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,gFAAgF;AAChF,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;AAEpE,SAAS,aAAa,CAAC,KAAc,EAAE,IAAY;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,wBAAwB,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,KAAgC,CAAC;AACzC,CAAC;AAED,SAAS,WAAW,CAAC,KAAc,EAAE,KAAa;IACjD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,2BAA2B,KAAK,6BAA6B,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED,SAAS,cAAc,CAAC,KAAc,EAAE,KAAa;IACpD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,EAAE,CAAC;QAClG,MAAM,IAAI,KAAK,CAAC,2BAA2B,KAAK,4BAA4B,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;AACnB,CAAC;AAED,iEAAiE;AACjE,SAAS,aAAa,CAAC,KAAc,EAAE,KAAa;IACnD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAClC,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,2BAA2B,KAAK,kCAAkC,IAAI,UAAU,CAAC,CAAC;IACnG,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED,+GAA+G;AAC/G,SAAS,aAAa,CAAC,KAAc,EAAE,KAAa;IACnD,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,KAAK,CAAC,2BAA2B,KAAK,+CAA+C,IAAI,qBAAqB,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC;IAC1I,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,KAAa;IAC5C,IAAI,CAAC;QACJ,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,2BAA2B,KAAK,6BAA6B,IAAI,UAAU,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAChH,CAAC;AACF,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Everything an app needs to rent a node from a hosting provider: the provider's descriptor,
3
+ * the sign-in handshake, the typed API client, and `rentNode` / `finishRental`. Every module
4
+ * here loads on React Native and in the browser as well as Node.
5
+ */
6
+ export { parseProviderDescriptor, fetchProviderDescriptor, type ProviderDescriptor } from './descriptor.js';
7
+ export { ProviderAuthError, pkceChallenge, createPkce, createAuthorizationState, authorizationUrl, readAuthorizationRedirect, exchangeAuthorizationCode, refreshTokens, createTokenSource, TOKEN_REFRESH_MARGIN_MS, type Pkce, type ProviderTokens, type AuthorizationUrlOptions, type AuthorizationRedirect, type ExchangeAuthorizationCodeOptions, type RefreshTokensOptions, type TokenSource, type TokenSourceOptions, } from './oauth.js';
8
+ export { createProviderClient, ProviderApiError, type ProviderClient, type ProviderClientOptions, type ContainerJoinRequest, type ContainerJoinState, type ContainerStopReason, type RentedContainer, type RentedContainerStatus, type RentedContainerHealth, type RentedContainerStatusReport, type CreateContainerBody, type SubscriptionStatus, type BillingStatus, type NodeCodeAnswer, } from './client.js';
9
+ export { rentNode, finishRental, RentNodeError, DEFAULT_NODE_CODE_DEADLINE_MS, NODE_CODE_POLL_MS, RENTAL_INVITATION_TTL_MS, type RentNodeJoin, type RentNodeStep, type RentNodeOptions, type RentNodeResult, type RentalFailed, type RentNodeErrorCode, type RentingNode, } from './rent-node.js';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Everything an app needs to rent a node from a hosting provider: the provider's descriptor,
3
+ * the sign-in handshake, the typed API client, and `rentNode` / `finishRental`. Every module
4
+ * here loads on React Native and in the browser as well as Node.
5
+ */
6
+ export { parseProviderDescriptor, fetchProviderDescriptor } from './descriptor.js';
7
+ export { ProviderAuthError, pkceChallenge, createPkce, createAuthorizationState, authorizationUrl, readAuthorizationRedirect, exchangeAuthorizationCode, refreshTokens, createTokenSource, TOKEN_REFRESH_MARGIN_MS, } from './oauth.js';
8
+ export { createProviderClient, ProviderApiError, } from './client.js';
9
+ export { rentNode, finishRental, RentNodeError, DEFAULT_NODE_CODE_DEADLINE_MS, NODE_CODE_POLL_MS, RENTAL_INVITATION_TTL_MS, } from './rent-node.js';
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/provider/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAA2B,MAAM,iBAAiB,CAAC;AAC5G,OAAO,EACN,iBAAiB,EACjB,aAAa,EACb,UAAU,EACV,wBAAwB,EACxB,gBAAgB,EAChB,yBAAyB,EACzB,yBAAyB,EACzB,aAAa,EACb,iBAAiB,EACjB,uBAAuB,GASvB,MAAM,YAAY,CAAC;AACpB,OAAO,EACN,oBAAoB,EACpB,gBAAgB,GAchB,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,6BAA6B,EAC7B,iBAAiB,EACjB,wBAAwB,GAQxB,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * The app's side of signing in to a hosting provider: OAuth 2 authorization code with PKCE
3
+ * (RFC 6749 §4.1, RFC 7636), the flow for a public client that holds no secret, against the
4
+ * endpoints a provider descriptor names (`docs/architecture.md` → "Provider descriptor and
5
+ * sign-in"). Opening the browser is the app's or its kit's; this module builds the URL it
6
+ * opens, reads the redirect it comes back with, exchanges and refreshes tokens, and keeps a
7
+ * token fresh for the provider client ({@link createTokenSource}).
8
+ *
9
+ * Imports `@noble/hashes` for SHA-256 and the CSPRNG (`crypto.getRandomValues`, which the
10
+ * React Native kit polyfills) and `uint8arrays` for base64url, so it loads on React Native
11
+ * and in the browser as well as Node. Codes and tokens are credentials: none is ever logged.
12
+ */
13
+ import type { ProviderDescriptor } from './descriptor.js';
14
+ /**
15
+ * A sign-in fault, with a machine-readable `code`: the provider's own OAuth error code
16
+ * (`invalid_grant`, `access_denied`, ... per RFC 6749 §5.2) when it answered one;
17
+ * `state-mismatch` or `missing-code` when the redirect could not be read; `invalid_response`
18
+ * when the token endpoint answered something that is not a token response; `reauthorize`
19
+ * when a refresh was refused, the app's cue to run the authorization again.
20
+ */
21
+ export declare class ProviderAuthError extends Error {
22
+ readonly code: string;
23
+ constructor(code: string, description?: string, options?: {
24
+ cause?: unknown;
25
+ });
26
+ }
27
+ /** What a token exchange or refresh yields, with `expiresAt` already in epoch milliseconds. */
28
+ export interface ProviderTokens {
29
+ accessToken: string;
30
+ refreshToken: string;
31
+ /** Epoch ms when `accessToken` stops working. */
32
+ expiresAt: number;
33
+ scopes: string[];
34
+ }
35
+ /** A PKCE pair (RFC 7636): the verifier the app keeps, and the S256 challenge it sends first. */
36
+ export interface Pkce {
37
+ verifier: string;
38
+ challenge: string;
39
+ }
40
+ /** The S256 challenge for `verifier`: base64url(SHA-256(ascii(verifier))), unpadded. */
41
+ export declare function pkceChallenge(verifier: string): string;
42
+ /** A fresh pair: 32 random bytes as a 43-character base64url verifier, and its challenge. */
43
+ export declare function createPkce(): Pkce;
44
+ /** A random `state` for one authorization, which {@link readAuthorizationRedirect} checks the redirect against. */
45
+ export declare function createAuthorizationState(): string;
46
+ export interface AuthorizationUrlOptions {
47
+ /** The client id the app registered with the provider. */
48
+ clientId: string;
49
+ /** One of that client's registered redirect URIs, exactly as registered. */
50
+ redirectUri: string;
51
+ state: string;
52
+ /** The S256 challenge of the verifier the app keeps ({@link createPkce}). */
53
+ challenge: string;
54
+ /** Scopes to ask for; defaults to every scope the descriptor lists. */
55
+ scopes?: readonly string[];
56
+ }
57
+ /** The authorize URL to open in the system browser: `response_type=code`, the client, the redirect URI, the scopes space-joined, the state and the S256 challenge. */
58
+ export declare function authorizationUrl(descriptor: ProviderDescriptor, options: AuthorizationUrlOptions): string;
59
+ /** What the browser came back with: a code to exchange, or the provider's refusal (RFC 6749 §4.1.2.1). */
60
+ export type AuthorizationRedirect = {
61
+ code: string;
62
+ } | {
63
+ error: string;
64
+ description?: string;
65
+ };
66
+ /**
67
+ * Read the redirect URL the browser returned to the app. Throws `ProviderAuthError`
68
+ * (`state-mismatch`) when its `state` is not `expectedState`, whichever outcome it carries,
69
+ * and (`missing-code`) when it carries neither a code nor an error. The code is a credential
70
+ * and never appears in a message or a log line.
71
+ */
72
+ export declare function readAuthorizationRedirect(url: string, options: {
73
+ expectedState: string;
74
+ }): AuthorizationRedirect;
75
+ export interface ExchangeAuthorizationCodeOptions {
76
+ clientId: string;
77
+ /** The redirect URI the authorization was sent with, exactly. */
78
+ redirectUri: string;
79
+ code: string;
80
+ /** The verifier of the challenge the authorization was sent with. */
81
+ verifier: string;
82
+ fetch?: typeof fetch;
83
+ }
84
+ /** `grant_type=authorization_code` at the descriptor's token endpoint, form-encoded; a refusal is a `ProviderAuthError` with the provider's error code. */
85
+ export declare function exchangeAuthorizationCode(descriptor: ProviderDescriptor, options: ExchangeAuthorizationCodeOptions): Promise<ProviderTokens>;
86
+ export interface RefreshTokensOptions {
87
+ clientId: string;
88
+ refreshToken: string;
89
+ fetch?: typeof fetch;
90
+ }
91
+ /** `grant_type=refresh_token`: rotates the pair (the presented one stops working); a refusal is a `ProviderAuthError` with the provider's error code. */
92
+ export declare function refreshTokens(descriptor: ProviderDescriptor, options: RefreshTokensOptions): Promise<ProviderTokens>;
93
+ /** Hands the provider client a working bearer; see {@link createTokenSource}. */
94
+ export interface TokenSource {
95
+ /** The current access token, refreshed first when it is within a minute of expiry. */
96
+ accessToken(): Promise<string>;
97
+ /**
98
+ * The provider answered 401 to `stale`: refresh and return the new token. A refresh that
99
+ * already replaced `stale` is not repeated, so two requests refused at once cost one
100
+ * rotation.
101
+ */
102
+ invalidate(stale?: string): Promise<string>;
103
+ }
104
+ export interface TokenSourceOptions {
105
+ descriptor: ProviderDescriptor;
106
+ clientId: string;
107
+ /** The tokens the app holds, from the exchange or from its store. */
108
+ tokens: ProviderTokens;
109
+ /** Called with the new pair after every refresh; the old pair has stopped working by then. */
110
+ persist: (tokens: ProviderTokens) => void | Promise<void>;
111
+ fetch?: typeof fetch;
112
+ /** The clock, for tests. */
113
+ now?: () => number;
114
+ }
115
+ /** A token is refreshed this long before it expires, so a request does not leave with one about to lapse. */
116
+ export declare const TOKEN_REFRESH_MARGIN_MS = 60000;
117
+ /**
118
+ * A {@link TokenSource} over one token pair: refreshes within {@link TOKEN_REFRESH_MARGIN_MS}
119
+ * of expiry and on `invalidate`, calls `persist` after every refresh, and shares one refresh
120
+ * among concurrent callers (a rotation spends the presented refresh token, so two at once
121
+ * would leave one caller with a refused pair). A refused refresh throws
122
+ * `ProviderAuthError('reauthorize')`: the pair is gone and the app signs in again; a network
123
+ * failure is rethrown as is, since the pair may still be good.
124
+ */
125
+ export declare function createTokenSource(options: TokenSourceOptions): TokenSource;
@@ -0,0 +1,199 @@
1
+ /**
2
+ * The app's side of signing in to a hosting provider: OAuth 2 authorization code with PKCE
3
+ * (RFC 6749 §4.1, RFC 7636), the flow for a public client that holds no secret, against the
4
+ * endpoints a provider descriptor names (`docs/architecture.md` → "Provider descriptor and
5
+ * sign-in"). Opening the browser is the app's or its kit's; this module builds the URL it
6
+ * opens, reads the redirect it comes back with, exchanges and refreshes tokens, and keeps a
7
+ * token fresh for the provider client ({@link createTokenSource}).
8
+ *
9
+ * Imports `@noble/hashes` for SHA-256 and the CSPRNG (`crypto.getRandomValues`, which the
10
+ * React Native kit polyfills) and `uint8arrays` for base64url, so it loads on React Native
11
+ * and in the browser as well as Node. Codes and tokens are credentials: none is ever logged.
12
+ */
13
+ import debug from 'debug';
14
+ import { sha256 } from '@noble/hashes/sha2.js';
15
+ import { randomBytes } from '@noble/hashes/utils.js';
16
+ import { toString as uint8ArrayToString } from 'uint8arrays';
17
+ import { requireFetch } from '../fetch-impl.js';
18
+ const log = debug('sereus:cadre:provider-oauth');
19
+ /**
20
+ * A sign-in fault, with a machine-readable `code`: the provider's own OAuth error code
21
+ * (`invalid_grant`, `access_denied`, ... per RFC 6749 §5.2) when it answered one;
22
+ * `state-mismatch` or `missing-code` when the redirect could not be read; `invalid_response`
23
+ * when the token endpoint answered something that is not a token response; `reauthorize`
24
+ * when a refresh was refused, the app's cue to run the authorization again.
25
+ */
26
+ export class ProviderAuthError extends Error {
27
+ constructor(code, description, options) {
28
+ super(description ? `${code}: ${description}` : code, options);
29
+ this.code = code;
30
+ this.name = 'ProviderAuthError';
31
+ }
32
+ }
33
+ /** The S256 challenge for `verifier`: base64url(SHA-256(ascii(verifier))), unpadded. */
34
+ export function pkceChallenge(verifier) {
35
+ return uint8ArrayToString(sha256(new TextEncoder().encode(verifier)), 'base64url');
36
+ }
37
+ /** A fresh pair: 32 random bytes as a 43-character base64url verifier, and its challenge. */
38
+ export function createPkce() {
39
+ const verifier = uint8ArrayToString(randomBytes(32), 'base64url');
40
+ return { verifier, challenge: pkceChallenge(verifier) };
41
+ }
42
+ /** A random `state` for one authorization, which {@link readAuthorizationRedirect} checks the redirect against. */
43
+ export function createAuthorizationState() {
44
+ return uint8ArrayToString(randomBytes(16), 'base64url');
45
+ }
46
+ /** The authorize URL to open in the system browser: `response_type=code`, the client, the redirect URI, the scopes space-joined, the state and the S256 challenge. */
47
+ export function authorizationUrl(descriptor, options) {
48
+ const url = new URL(descriptor.oauth.authorizeUrl);
49
+ url.searchParams.set('response_type', 'code');
50
+ url.searchParams.set('client_id', options.clientId);
51
+ url.searchParams.set('redirect_uri', options.redirectUri);
52
+ url.searchParams.set('scope', (options.scopes ?? descriptor.oauth.scopes).join(' '));
53
+ url.searchParams.set('state', options.state);
54
+ url.searchParams.set('code_challenge', options.challenge);
55
+ url.searchParams.set('code_challenge_method', 'S256');
56
+ return url.toString();
57
+ }
58
+ /**
59
+ * Read the redirect URL the browser returned to the app. Throws `ProviderAuthError`
60
+ * (`state-mismatch`) when its `state` is not `expectedState`, whichever outcome it carries,
61
+ * and (`missing-code`) when it carries neither a code nor an error. The code is a credential
62
+ * and never appears in a message or a log line.
63
+ */
64
+ export function readAuthorizationRedirect(url, options) {
65
+ const params = new URL(url).searchParams;
66
+ if (params.get('state') !== options.expectedState) {
67
+ throw new ProviderAuthError('state-mismatch', 'the redirect\'s state is not the one this authorization sent');
68
+ }
69
+ const error = params.get('error');
70
+ if (error !== null) {
71
+ const description = params.get('error_description');
72
+ return description === null ? { error } : { error, description };
73
+ }
74
+ const code = params.get('code');
75
+ if (code === null || code === '') {
76
+ throw new ProviderAuthError('missing-code', 'the redirect carries neither a code nor an error');
77
+ }
78
+ return { code };
79
+ }
80
+ /** `grant_type=authorization_code` at the descriptor's token endpoint, form-encoded; a refusal is a `ProviderAuthError` with the provider's error code. */
81
+ export async function exchangeAuthorizationCode(descriptor, options) {
82
+ return await requestTokens(descriptor, {
83
+ grant_type: 'authorization_code',
84
+ code: options.code,
85
+ code_verifier: options.verifier,
86
+ client_id: options.clientId,
87
+ redirect_uri: options.redirectUri,
88
+ }, options.fetch);
89
+ }
90
+ /** `grant_type=refresh_token`: rotates the pair (the presented one stops working); a refusal is a `ProviderAuthError` with the provider's error code. */
91
+ export async function refreshTokens(descriptor, options) {
92
+ return await requestTokens(descriptor, {
93
+ grant_type: 'refresh_token',
94
+ refresh_token: options.refreshToken,
95
+ client_id: options.clientId,
96
+ }, options.fetch);
97
+ }
98
+ /** A token is refreshed this long before it expires, so a request does not leave with one about to lapse. */
99
+ export const TOKEN_REFRESH_MARGIN_MS = 60000;
100
+ /**
101
+ * A {@link TokenSource} over one token pair: refreshes within {@link TOKEN_REFRESH_MARGIN_MS}
102
+ * of expiry and on `invalidate`, calls `persist` after every refresh, and shares one refresh
103
+ * among concurrent callers (a rotation spends the presented refresh token, so two at once
104
+ * would leave one caller with a refused pair). A refused refresh throws
105
+ * `ProviderAuthError('reauthorize')`: the pair is gone and the app signs in again; a network
106
+ * failure is rethrown as is, since the pair may still be good.
107
+ */
108
+ export function createTokenSource(options) {
109
+ const now = options.now ?? Date.now;
110
+ let current = options.tokens;
111
+ let inFlight = null;
112
+ const refresh = () => {
113
+ if (!inFlight) {
114
+ inFlight = rotate().finally(() => { inFlight = null; });
115
+ }
116
+ return inFlight;
117
+ };
118
+ async function rotate() {
119
+ let next;
120
+ try {
121
+ next = await refreshTokens(options.descriptor, { clientId: options.clientId, refreshToken: current.refreshToken, fetch: options.fetch });
122
+ }
123
+ catch (error) {
124
+ if (error instanceof ProviderAuthError) {
125
+ log('token refresh refused (%s); the app must authorize again', error.code);
126
+ throw new ProviderAuthError('reauthorize', `the provider refused to refresh the token (${error.code})`, { cause: error });
127
+ }
128
+ throw error;
129
+ }
130
+ current = next;
131
+ await options.persist(next);
132
+ log('token refreshed; expires in %ds', Math.round((next.expiresAt - now()) / 1000));
133
+ return next;
134
+ }
135
+ return {
136
+ async accessToken() {
137
+ const fresh = now() < current.expiresAt - TOKEN_REFRESH_MARGIN_MS ? current : await refresh();
138
+ return fresh.accessToken;
139
+ },
140
+ async invalidate(stale) {
141
+ if (stale !== undefined && stale !== current.accessToken)
142
+ return current.accessToken;
143
+ return (await refresh()).accessToken;
144
+ },
145
+ };
146
+ }
147
+ /** One token-endpoint request (RFC 6749 §4.1.3, §6): form-encoded in, a token response or a `{ error, error_description }` refusal out. */
148
+ async function requestTokens(descriptor, form, fetchImpl) {
149
+ const doFetch = requireFetch(fetchImpl);
150
+ const requestedAt = Date.now();
151
+ const response = await doFetch(descriptor.oauth.tokenUrl, {
152
+ method: 'POST',
153
+ headers: { 'content-type': 'application/x-www-form-urlencoded', accept: 'application/json' },
154
+ body: new URLSearchParams(form).toString(),
155
+ });
156
+ const body = await readJson(response);
157
+ if (!response.ok) {
158
+ const error = body && typeof body.error === 'string' ? body.error : 'invalid_response';
159
+ const description = body && typeof body.error_description === 'string'
160
+ ? body.error_description
161
+ : `the token endpoint answered ${response.status}`;
162
+ throw new ProviderAuthError(error, description);
163
+ }
164
+ return tokensFrom(body, requestedAt);
165
+ }
166
+ /** The response's JSON object, or null when the body is not one. */
167
+ async function readJson(response) {
168
+ try {
169
+ const parsed = await response.json();
170
+ return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed) ? parsed : null;
171
+ }
172
+ catch (error) {
173
+ log('token endpoint answered %d with a body that is not JSON: %o', response.status, error);
174
+ return null;
175
+ }
176
+ }
177
+ /** `{ access_token, expires_in, refresh_token, scope }` as {@link ProviderTokens}; `expires_in` counts from when the request left. */
178
+ function tokensFrom(body, requestedAt) {
179
+ const accessToken = body?.access_token;
180
+ const refreshToken = body?.refresh_token;
181
+ const expiresIn = body?.expires_in;
182
+ if (typeof accessToken !== 'string' || accessToken === '') {
183
+ throw new ProviderAuthError('invalid_response', 'the token response carries no access_token');
184
+ }
185
+ if (typeof refreshToken !== 'string' || refreshToken === '') {
186
+ throw new ProviderAuthError('invalid_response', 'the token response carries no refresh_token, which a token source needs to outlive the access token');
187
+ }
188
+ if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn) || expiresIn <= 0) {
189
+ throw new ProviderAuthError('invalid_response', 'the token response carries no positive expires_in');
190
+ }
191
+ const scope = body?.scope;
192
+ return {
193
+ accessToken,
194
+ refreshToken,
195
+ expiresAt: requestedAt + expiresIn * 1000,
196
+ scopes: typeof scope === 'string' ? scope.split(' ').filter((entry) => entry !== '') : [],
197
+ };
198
+ }
199
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/provider/oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Rent a node from a hosting provider and add it to this node's cadre, by whichever of the two
3
+ * join paths fits (`docs/architecture.md` → "Renting a node from an app"):
4
+ *
5
+ * - **invitation**, when the cadre has a member the container can reach from the provider's
6
+ * network (`CadreNode.invitationReach()` reads `anywhere`): mint a single-use, non-owner
7
+ * invitation and create the container with it; the container joins by itself and the app
8
+ * watches `getContainer(id).join`.
9
+ * - **claim**, otherwise (a phone that is the cadre's only member, or members reachable only on
10
+ * their LAN, which the provider's container is never on): create the container, poll for its
11
+ * node code, and claim it from this node.
12
+ *
13
+ * Nothing here persists anything: which container belongs to which cadre is the app's record,
14
+ * and {@link finishRental} is how an app resumes a rental it was killed in the middle of. The
15
+ * node code carries the claim secret, so neither the code nor the decoded payload is ever
16
+ * logged, only the container id and the peer id.
17
+ */
18
+ import type { CadreNode } from '../cadre-node.js';
19
+ import { type ProviderClient } from './client.js';
20
+ export type RentNodeJoin = 'claim' | 'invitation';
21
+ /** Progress, as `RentNodeOptions.onProgress` reports it; `claiming` can last a while, see {@link rentNode}. */
22
+ export type RentNodeStep = 'creating' | 'waiting-for-code' | 'claiming' | 'inviting' | 'done';
23
+ export interface RentNodeOptions {
24
+ /** Default `storage`. */
25
+ profile?: 'storage' | 'transaction';
26
+ /** Called at each step; an exception it throws is logged and ignored. */
27
+ onProgress?: (step: RentNodeStep) => void;
28
+ /**
29
+ * How long to wait for the node code before giving up with `node-not-ready`; default
30
+ * {@link DEFAULT_NODE_CODE_DEADLINE_MS}. The container exists by then and
31
+ * {@link finishRental} resumes it.
32
+ */
33
+ nodeCodeDeadlineMs?: number;
34
+ /** How often the node code is asked for while it is not ready; default {@link NODE_CODE_POLL_MS}. */
35
+ nodeCodePollMs?: number;
36
+ }
37
+ export interface RentNodeResult {
38
+ containerId: string;
39
+ join: RentNodeJoin;
40
+ /** The node's peer id, known at once on the claim path and once the container reported it on the invitation path. */
41
+ peerId?: string;
42
+ }
43
+ /** {@link finishRental} on an invitation container whose node could not redeem: delete the container and rent again. */
44
+ export interface RentalFailed {
45
+ containerId: string;
46
+ outcome: 'failed';
47
+ error: string;
48
+ retryable: boolean;
49
+ }
50
+ export type RentNodeErrorCode =
51
+ /** This node cannot act as an owner of its cadre, so it can neither invite nor claim. */
52
+ 'not-an-owner'
53
+ /** The node code was not ready by the deadline; the container exists, `finishRental` resumes it. */
54
+ | 'node-not-ready'
55
+ /** The container is `stopped` or `error`, or the provider reported it so while the code was awaited. */
56
+ | 'container-gone'
57
+ /** A claim container that another cadre's owner claimed. */
58
+ | 'claimed-by-another'
59
+ /** A container `rentNode` did not create: it carries no join. */
60
+ | 'not-a-rental';
61
+ export declare class RentNodeError extends Error {
62
+ readonly code: RentNodeErrorCode;
63
+ readonly containerId?: string | undefined;
64
+ constructor(code: RentNodeErrorCode, containerId?: string | undefined);
65
+ }
66
+ /** The `CadreNode` surface renting needs; a hand-written object implementing it is how tests avoid a node. */
67
+ export type RentingNode = Pick<CadreNode, 'partyId' | 'isOwner' | 'invitationReach' | 'createCadreInvitation' | 'withdrawCadreInvitation' | 'claimNode'>;
68
+ /**
69
+ * The provider's enrolment window is 60 s and an image pull can precede it. Bounds the
70
+ * container's start, not an exchange over the link, so it is not derived from the link budget.
71
+ */
72
+ export declare const DEFAULT_NODE_CODE_DEADLINE_MS = 120000;
73
+ export declare const NODE_CODE_POLL_MS = 2000;
74
+ /** Lifetime of the invitation a rental mints: long enough for the container to start and redeem, short enough to limit a leaked bundle. */
75
+ export declare const RENTAL_INVITATION_TTL_MS: number;
76
+ /**
77
+ * Rent a node and add it to `node`'s cadre; see the module doc for the path choice.
78
+ *
79
+ * The claim path's `claimNode` can take up to the per-peer dial limit plus the seed request
80
+ * when no address answers (its doc: 114.5 s at the default link); no timeout is added on top
81
+ * of it, and `onProgress('claiming')` is the app's cue to show a slow hint. Two calls at once
82
+ * on one node both mint or both create; serializing them is the app's.
83
+ *
84
+ * @throws {RentNodeError} `not-an-owner`, or `node-not-ready` once the container exists.
85
+ * @throws {ProviderApiError} when the provider refused a request (a lapsed subscription is `403 QUOTA_EXCEEDED`).
86
+ * @throws {ClaimRefusedError} or {PeerUnreachableError} from `claimNode`, as it throws them.
87
+ */
88
+ export declare function rentNode(node: RentingNode, client: ProviderClient, options?: RentNodeOptions): Promise<RentNodeResult>;
89
+ /**
90
+ * Resume a rental: for an app that was killed mid-flow or hit the node-code deadline.
91
+ *
92
+ * - a `stopped` or `error` container → `RentNodeError('container-gone')`;
93
+ * - a claim container not yet claimed → the code, then `claimNode`, as {@link rentNode} would;
94
+ * - a claim container claimed into this cadre → done. The provider withholds the code once a
95
+ * claim is folded, so the claim is not re-run; one claimed by another cadre's owner →
96
+ * `RentNodeError('claimed-by-another')`;
97
+ * - an invitation container → done while `pending` or `accepted` (the app watches `join`), or
98
+ * the failed outcome, after which the app deletes the container and rents again (the claim
99
+ * path is chosen then if the member that answered is gone).
100
+ */
101
+ export declare function finishRental(node: RentingNode, client: ProviderClient, containerId: string, options?: RentNodeOptions): Promise<RentNodeResult | RentalFailed>;