@mdbase-dev/sdk 0.1.0-beta.168 → 0.1.0-beta.173

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 (148) hide show
  1. package/README.md +91 -3
  2. package/dist/app-host/account-key-host.d.ts +128 -0
  3. package/dist/app-host/account-key-host.js +449 -0
  4. package/dist/app-host/account-key-host.js.map +1 -0
  5. package/dist/app-host/app-environment.d.ts +3 -4
  6. package/dist/app-host/app-environment.js +6 -6
  7. package/dist/app-host/app-environment.js.map +1 -1
  8. package/dist/app-host/app-origin.d.ts +5 -2
  9. package/dist/app-host/app-origin.js +10 -6
  10. package/dist/app-host/app-origin.js.map +1 -1
  11. package/dist/app-host/cloud-copy-bootstrap.js +2 -2
  12. package/dist/app-host/cloud-copy-bootstrap.js.map +1 -1
  13. package/dist/app-host/cloud-copy-host.d.ts +69 -5
  14. package/dist/app-host/cloud-copy-host.js +289 -20
  15. package/dist/app-host/cloud-copy-host.js.map +1 -1
  16. package/dist/app-host/cloud-copy-persistence-idb.d.ts +0 -2
  17. package/dist/app-host/cloud-copy-persistence-idb.js +2 -2
  18. package/dist/app-host/cloud-copy-persistence-idb.js.map +1 -1
  19. package/dist/app-host/cp-authority.js +2 -2
  20. package/dist/app-host/cp-authority.js.map +1 -1
  21. package/dist/app-host/cp-people.js +2 -2
  22. package/dist/app-host/cp-people.js.map +1 -1
  23. package/dist/app-host/device-key-idb.d.ts +0 -2
  24. package/dist/app-host/device-key-idb.js +1 -1
  25. package/dist/app-host/device-key-idb.js.map +1 -1
  26. package/dist/app-host/device-key-provider.d.ts +0 -3
  27. package/dist/app-host/device-key-provider.js +1 -1
  28. package/dist/app-host/device-key-provider.js.map +1 -1
  29. package/dist/app-host/device-registration.js +2 -2
  30. package/dist/app-host/device-registration.js.map +1 -1
  31. package/dist/app-host/http-log.js +2 -2
  32. package/dist/app-host/http-log.js.map +1 -1
  33. package/dist/app-host/index.d.ts +4 -2
  34. package/dist/app-host/index.js +2 -1
  35. package/dist/app-host/index.js.map +1 -1
  36. package/dist/app-host/installation-sign-in.d.ts +16 -1
  37. package/dist/app-host/installation-sign-in.js +134 -27
  38. package/dist/app-host/installation-sign-in.js.map +1 -1
  39. package/dist/app-host/installation-store.d.ts +0 -2
  40. package/dist/app-host/installation-store.js +4 -4
  41. package/dist/app-host/installation-store.js.map +1 -1
  42. package/dist/app-host/log-push.js +2 -2
  43. package/dist/app-host/log-push.js.map +1 -1
  44. package/dist/app-host/noise-idb.d.ts +0 -3
  45. package/dist/app-host/noise-idb.js +1 -1
  46. package/dist/app-host/noise-idb.js.map +1 -1
  47. package/dist/app-host/private-bootstrap.js +2 -2
  48. package/dist/app-host/private-bootstrap.js.map +1 -1
  49. package/dist/app-host/private-persistence-idb.d.ts +0 -2
  50. package/dist/app-host/private-persistence-idb.js +1 -1
  51. package/dist/app-host/private-persistence-idb.js.map +1 -1
  52. package/dist/app-host/private-persistence.js +2 -2
  53. package/dist/app-host/private-persistence.js.map +1 -1
  54. package/dist/app-host/scoped-sign-in-session.d.ts +4 -0
  55. package/dist/app-host/scoped-sign-in-session.js +9 -1
  56. package/dist/app-host/scoped-sign-in-session.js.map +1 -1
  57. package/dist/app-host/unsynced-collection.js +8 -3
  58. package/dist/app-host/unsynced-collection.js.map +1 -1
  59. package/dist/app-host/wasm-runtime.d.ts +14 -0
  60. package/dist/app-host/wasm-runtime.js +45 -0
  61. package/dist/app-host/wasm-runtime.js.map +1 -1
  62. package/dist/app-host/web-collection.d.ts +4 -1
  63. package/dist/app-host/web-collection.js +214 -125
  64. package/dist/app-host/web-collection.js.map +1 -1
  65. package/dist/app-host/web-sign-in-problem.js +6 -1
  66. package/dist/app-host/web-sign-in-problem.js.map +1 -1
  67. package/dist/app-host/web-sign-in-session.d.ts +46 -4
  68. package/dist/app-host/web-sign-in-session.js +88 -15
  69. package/dist/app-host/web-sign-in-session.js.map +1 -1
  70. package/dist/application/asset-loader.d.ts +0 -1
  71. package/dist/application/asset-loader.js +2 -2
  72. package/dist/application/asset-loader.js.map +1 -1
  73. package/dist/application/asset-pins.json +4 -4
  74. package/dist/application/browser-selection.js +1 -1
  75. package/dist/application/browser-selection.js.map +1 -1
  76. package/dist/application/collection-selection.js +11 -6
  77. package/dist/application/collection-selection.js.map +1 -1
  78. package/dist/application/declaration.js +2 -2
  79. package/dist/application/declaration.js.map +1 -1
  80. package/dist/application/entry.d.ts +19 -0
  81. package/dist/application/entry.js +81 -38
  82. package/dist/application/entry.js.map +1 -1
  83. package/dist/application/front-door.d.ts +6 -1
  84. package/dist/application/front-door.js +5 -0
  85. package/dist/application/front-door.js.map +1 -1
  86. package/dist/application/local-build.d.ts +4 -2
  87. package/dist/application/local-build.js +4 -2
  88. package/dist/application/local-build.js.map +1 -1
  89. package/dist/application/manifest.d.ts +1 -0
  90. package/dist/application/manifest.js +8 -3
  91. package/dist/application/manifest.js.map +1 -1
  92. package/dist/application/native-assets.js +3 -4
  93. package/dist/application/native-assets.js.map +1 -1
  94. package/dist/application/packaged-assets.d.ts +1 -1
  95. package/dist/application/packaged-assets.js +6 -3
  96. package/dist/application/packaged-assets.js.map +1 -1
  97. package/dist/application/phase.d.ts +18 -1
  98. package/dist/application/phase.js +9 -1
  99. package/dist/application/phase.js.map +1 -1
  100. package/dist/application/portal-reservation.js +2 -2
  101. package/dist/application/portal-reservation.js.map +1 -1
  102. package/dist/application/registration.d.ts +3 -0
  103. package/dist/application/registration.js +7 -4
  104. package/dist/application/registration.js.map +1 -1
  105. package/dist/application/return-url.d.ts +1 -1
  106. package/dist/application/return-url.js +6 -7
  107. package/dist/application/return-url.js.map +1 -1
  108. package/dist/application/runtime.d.ts +7 -1
  109. package/dist/application/runtime.js +53 -4
  110. package/dist/application/runtime.js.map +1 -1
  111. package/dist/application/startup-timing.d.ts +20 -0
  112. package/dist/application/startup-timing.js +58 -0
  113. package/dist/application/startup-timing.js.map +1 -0
  114. package/dist/application/ui-connections.js +17 -4
  115. package/dist/application/ui-connections.js.map +1 -1
  116. package/dist/application/worker-client.d.ts +3 -0
  117. package/dist/application/worker-client.js +22 -2
  118. package/dist/application/worker-client.js.map +1 -1
  119. package/dist/application/worker-host.js +51 -3
  120. package/dist/application/worker-host.js.map +1 -1
  121. package/dist/application/worker-protocol.d.ts +7 -2
  122. package/dist/application/worker-protocol.js +10 -1
  123. package/dist/application/worker-protocol.js.map +1 -1
  124. package/dist/application/worker-session.d.ts +1 -1
  125. package/dist/application/worker-session.js +5 -1
  126. package/dist/application/worker-session.js.map +1 -1
  127. package/dist/application/worker.js +8394 -2001
  128. package/dist/application/worker.js.map +1 -1
  129. package/dist/browser/mdbase-sdk.js +8064 -3633
  130. package/dist/browser/mdbase-sdk.min.js +78 -78
  131. package/dist/describe-error.d.ts +2 -2
  132. package/dist/describe-error.js +9 -2
  133. package/dist/describe-error.js.map +1 -1
  134. package/dist/index.d.ts +1 -1
  135. package/dist/local-build.d.ts +7 -4
  136. package/dist/local-build.js +7 -4
  137. package/dist/local-build.js.map +1 -1
  138. package/dist/native/provenance.json +5 -5
  139. package/dist/native/runtime.wasm +0 -0
  140. package/dist/private-account.js +13 -18
  141. package/dist/private-account.js.map +1 -1
  142. package/dist/testing/application.d.ts +3 -1
  143. package/dist/testing/application.js +8 -1
  144. package/dist/testing/application.js.map +1 -1
  145. package/dist/testing/test-app.d.ts +9 -0
  146. package/dist/testing/test-app.js +13 -0
  147. package/dist/testing/test-app.js.map +1 -1
  148. package/package.json +1 -1
package/README.md CHANGED
@@ -375,6 +375,72 @@ Not covered yet for Private collections:
375
375
  report it: older servers, or an account whose access needs renewing. Gate
376
376
  owner actions on `role === "owner"`; Connect still checks each action.
377
377
 
378
+ ## Private collections on the app's own replica
379
+
380
+ An app that hosts its own replica through `MdbaseApplication` (`createMdbase`)
381
+ can open an existing Private collection with that replica, as a device of the
382
+ collection, instead of through the relay. The person unlocks it once with the
383
+ account's encryption password or recovery key. Connect only offers this to
384
+ apps it allows to hold a Private key.
385
+
386
+ - **Ask for it.** List `offline.replica` in the declaration's capabilities,
387
+ under `requirements.capabilities.optional` or `required`. Prefer `optional`:
388
+ the app then still signs in and syncs cloud copies where device access is not
389
+ offered, and consent for an app grant on a current account refuses an app
390
+ that lists it as required. The sign-in request asks for device access
391
+ (`requested_private_device`), and the person chooses which Private
392
+ collections to share. A server that does not know the request is asked again
393
+ without it, and one that does not offer device access to this app answers
394
+ with no Private collections; both leave an ordinary cloud-copy sign-in. An
395
+ installation paired before this needs to ask for access again.
396
+ - **List.** Entries in `snapshot.collections` carry `mode: "private"`; absent
397
+ means a cloud copy. A server from before this feature (after a rollback)
398
+ answers the list without modes; the SDK then asks once more with the earlier
399
+ request, so file actions are kept and no Private entry is listed.
400
+ - **Open.** `select(id)` as usual. An approval that shares only Private
401
+ collections names no selected collection, so the app arrives in its chooser;
402
+ when exactly one collection is approved the application opens it by itself,
403
+ as it does a sole cloud copy. A device that already holds the key goes
404
+ straight to `ready`. One that doesn't reads
405
+ `{kind: "needs_unlock", collectionId, displayName, mode, unlocking, problem}`.
406
+ There is no `connection` while locked.
407
+ - **Unlock.** For `mode: "password"`, call
408
+ `mdbase.unlock(collectionId, {password})` or `{recoveryKey}` with what the
409
+ person typed, and keep no copy. It resolves when the attempt has settled: the
410
+ collection continues to `ready` (or `needs_setup`) without a reload, or the
411
+ phase is still `needs_unlock` with `problem` set. `problem` is an
412
+ `MdbaseProblem` with its copy already described; its `key` is one of
413
+ `wrong_secret`, `invalid_recovery_key`, `rate_limited`, `unavailable`,
414
+ `unlock_unavailable` or `unlock_failed`. `mode: "strict"`
415
+ and `mode: "none"` cannot be unlocked here: explain that this collection has
416
+ to be opened on a computer running the mdbase app.
417
+ - **Retries.** Connect limits how often an account's sealed key can be fetched
418
+ (10 an hour, shared by all of the account's devices; `rate_limited` carries
419
+ the wait). A mistyped recovery key (`invalid_recovery_key`) and a password
420
+ shorter than the 12-character minimum (`wrong_secret`) are refused in the
421
+ Worker without any request. After a wrong secret the Worker keeps the sealed
422
+ bundle in memory for the next attempt on the same locked collection, and
423
+ uses it only while Connect's status still reports the same account key
424
+ version; it is dropped when the collection unlocks or closes.
425
+ - **Afterwards.** Once the unlock has applied, reopening needs no secret. A
426
+ restart before it applied returns to `needs_unlock`.
427
+
428
+ The password or recovery key is sent to the Worker as bytes that are wiped in
429
+ the tab as soon as they are posted and in the Worker when the call settles. The
430
+ key it unlocks is used once and wiped; neither is stored. One installation can
431
+ be a device of one Private collection (`private_device_limit` for a second),
432
+ and People is not available for these collections yet: `connection.people`
433
+ is present, and its reads reject with `upgrade_required`
434
+ (`people_unsupported`).
435
+
436
+ To fetch the sealed account key, the app runtime signs one fixed request: the
437
+ account-key fetch proof for its own connector and device, over the server's
438
+ challenge and the signed-in account. It signs nothing else for the account key
439
+ (no change, no strict approval, no caller-chosen digest), and the fetched
440
+ bundle is still sealed under the person's password or recovery key, which both
441
+ kinds of unlock need. An older app runtime without this signer answers
442
+ `unlock_unavailable`, and the collection stays `needs_unlock`.
443
+
378
444
  ## Managed application build environments
379
445
 
380
446
  Application declarations support explicit `lab`, `staging` and `production`
@@ -383,9 +449,31 @@ trust handoff for that exact environment; changing an app build variable cannot
383
449
  retarget a LAB archive. Staging packaging pins Connect to
384
450
  `https://connect-staging.mdbase.dev`, production to `https://connect.mdbase.dev`.
385
451
  CP/log origins and release trust still come only from the verified build context;
386
- missing, expired or crossed trust refuses without a fallback. Staging and
387
- production do not permit HTTP loopback app origins. Isolated `local` builds remain
388
- separate and refuse all managed environments.
452
+ missing, expired or crossed trust refuses without a fallback. Isolated `local`
453
+ builds remain separate and refuse all managed environments.
454
+
455
+ ### Developing on localhost
456
+
457
+ In every managed environment the app's own `origin` may be a loopback HTTP
458
+ origin (`http://localhost:<port>`, `http://127.0.0.1:<port>` or
459
+ `http://[::1]:<port>`), so an app is developed against production with the
460
+ published package:
461
+
462
+ ```ts
463
+ createMdbase({ id, name, origin: "http://localhost:5173", environment: "production", manifest: devManifest });
464
+ ```
465
+
466
+ The manifest's `homepage` and `redirect_uris` use that same origin. Connect
467
+ registers it as its own application, apart from the one published at the HTTPS
468
+ origin: approvals and device storage belong to the exact origin and are not
469
+ shared between the two. The approval screen calls it "an app running on this
470
+ computer" and says its identity is unverified, as it does for a native app.
471
+ Connect's own origins (control plane, log) stay HTTPS outside LAB; no other
472
+ plain-HTTP app origin is accepted.
473
+
474
+ `vite dev` and `vite build` both work with Vite's default configuration: the
475
+ SDK's worker and WebAssembly files are served from `node_modules` in
476
+ development and emitted as assets in a build.
389
477
 
390
478
  The app manifest must declare the installation origin under
391
479
  `installation_origins.staging` for staging pairing, just as for other environments.
@@ -0,0 +1,128 @@
1
+ import { type Argon2idRunner } from "../account-key.js";
2
+ import { type AccountKeyControlPort, type AccountKeyMode, type PrivateAccountRequestOptions } from "../private-account.js";
3
+ import { type AppAccountKeyState } from "./wasm-runtime.js";
4
+ /** What the person typed. Strings cannot be wiped in JavaScript: callers drop
5
+ * their references at once and never log, store or publish them. */
6
+ export type AppAccountKeySecret = Readonly<{
7
+ password: string;
8
+ }> | Readonly<{
9
+ recoveryKey: string;
10
+ }>;
11
+ /** Why an unlock ended without a keyed device. The collection stays locked and
12
+ * usable for another attempt; each code has plain copy in `describeError`. */
13
+ export type AppAccountKeyUnlockProblem =
14
+ /** Wrong encryption password, or a recovery key of another account key. */
15
+ "wrong_secret"
16
+ /** The typed recovery key is not well formed. */
17
+ | "invalid_recovery_key"
18
+ /** The account has no account key: nothing to unlock with. */
19
+ | "no_account_key"
20
+ /** The account uses strict device approval: no password unlock exists. */
21
+ | "unlock_strict" | "rate_limited"
22
+ /** The account key service or the log could not be reached, or answered badly. */
23
+ | "unavailable"
24
+ /** This app runtime predates the account-key fetch proof signer. */
25
+ | "unlock_unavailable"
26
+ /** The secret is right, but this collection refused the grant (`detail`). */
27
+ | "unlock_failed";
28
+ export declare class AppAccountKeyUnlockError extends Error {
29
+ readonly reason: AppAccountKeyUnlockProblem;
30
+ readonly detail?: string | undefined;
31
+ readonly retryAfterMs?: number | undefined;
32
+ constructor(reason: AppAccountKeyUnlockProblem, detail?: string | undefined, retryAfterMs?: number | undefined);
33
+ }
34
+ /** The authenticated installation session of the adopted Private device. */
35
+ export interface AppAccountKeySession {
36
+ readonly accountId: string;
37
+ readonly connectorId: string;
38
+ readonly deviceId: string;
39
+ readonly collection: string;
40
+ readonly cpOrigin: string;
41
+ isCurrent(): boolean;
42
+ connectorBearer(options: {
43
+ signal: AbortSignal;
44
+ }): Promise<string>;
45
+ }
46
+ /** The native device, narrowed to what an unlock may do. */
47
+ export interface AppAccountKeyNative {
48
+ /** False when the pinned runtime has no account-key fetch signer. */
49
+ supportsFetchProof(): boolean;
50
+ /** The fixed `mdbase/v1/account-key-fetch` proof over a server challenge. */
51
+ signFetch(challenge: Uint8Array): Uint8Array;
52
+ /** Consumes and wipes the 32-byte loan on every path. */
53
+ unlockConsuming(secret: Uint8Array): AppAccountKeyState;
54
+ status(): AppAccountKeyState;
55
+ /** One foreground log turn. */
56
+ drive(options: {
57
+ signal: AbortSignal;
58
+ }): Promise<void>;
59
+ /** The log head the replica knows; 0 until a log turn has succeeded. */
60
+ headKnown(): number;
61
+ }
62
+ export interface AppAccountKeyHostOptions {
63
+ readonly fetch?: typeof fetch;
64
+ readonly now?: () => number;
65
+ readonly allowLoopbackHttp?: boolean;
66
+ /** Off-thread Argon2id; the default runs in this Worker. */
67
+ readonly argon2id?: Argon2idRunner;
68
+ /** How long a started self-grant may stay pending before the call gives up
69
+ * (the grant itself is not withdrawn). */
70
+ readonly settleMs?: number;
71
+ }
72
+ /** The sealed account-key bundle that ONE locked Private collection keeps
73
+ * between unlock attempts, so a mistyped password or a recovery key of another
74
+ * account key does not spend the account-wide fetch budget (10 per hour, shared
75
+ * with the person's other devices) again. It is the ciphertext Connect already
76
+ * gave this device, held in RAM only: never persisted, published or returned
77
+ * to the app. A held answer is used only while the budget-free status route,
78
+ * which applies the same account and installation scope checks on every
79
+ * attempt, still reports the same account-key version and key id for the same
80
+ * account, connector and device. Anything else drops it and fetches afresh.
81
+ * `unlockPrivateDevice` drops it on every outcome but a wrong secret; the
82
+ * owner clears it when the collection closes (which is also sign-out). */
83
+ export declare class AppAccountKeySealedBundle {
84
+ private held;
85
+ /** True while a sealed bundle is held. */
86
+ get holding(): boolean;
87
+ clear(): void;
88
+ }
89
+ /** `AccountKeyControlPort` over the installation bearer: status, challenge and
90
+ * fetch only. One fixed request each, no retry, bounded decoded bodies. */
91
+ export declare class AppCpAccountKeyControl implements AccountKeyControlPort {
92
+ private readonly session;
93
+ private readonly sign;
94
+ readonly accountId: string;
95
+ private readonly lifetime;
96
+ private readonly origin;
97
+ private readonly deviceId;
98
+ private readonly fetchImpl;
99
+ private readonly now;
100
+ constructor(session: AppAccountKeySession, sign: (challenge: Uint8Array) => Uint8Array, options?: AppAccountKeyHostOptions);
101
+ private check;
102
+ private request;
103
+ status(options: PrivateAccountRequestOptions): Promise<unknown>;
104
+ challenge(options: PrivateAccountRequestOptions): Promise<unknown>;
105
+ /** A fresh single-use challenge, the native fetch proof over it, then the
106
+ * bundle. The challenge and signature are wiped here; the bundle is returned
107
+ * to PrivateAccount only. */
108
+ fetch(options: PrivateAccountRequestOptions): Promise<unknown>;
109
+ put(): Promise<unknown>;
110
+ strict(): Promise<unknown>;
111
+ enrolRecoveryDevice(): Promise<unknown>;
112
+ close(): void;
113
+ }
114
+ /** The account-key mode from the status route (no bundle, no proof, not on the
115
+ * fetch budget). A failed read rejects: the mode is never guessed. */
116
+ export declare function readAccountKeyMode(session: AppAccountKeySession, options: AppAccountKeyHostOptions & {
117
+ signal: AbortSignal;
118
+ }): Promise<AccountKeyMode>;
119
+ /** Unlock this device's ONE adopted Private collection with the account key.
120
+ * Resolves only once the native applied policy has this device keyed and
121
+ * trusted. A typed `AppAccountKeyUnlockError` leaves the collection locked and
122
+ * open for another attempt; anything else is a lost owner or cancelled call. */
123
+ export declare function unlockPrivateDevice(session: AppAccountKeySession, native: AppAccountKeyNative, secret: AppAccountKeySecret, options: AppAccountKeyHostOptions & {
124
+ signal: AbortSignal;
125
+ onMode?(mode: AccountKeyMode): void;
126
+ /** This locked collection's holder for the sealed bundle between attempts. Without one, every attempt fetches. */
127
+ sealed?: AppAccountKeySealedBundle;
128
+ }): Promise<void>;