@interop/was-react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/LICENSE.md +20 -0
  2. package/README.md +410 -0
  3. package/dist/auth/chapi.d.ts +43 -0
  4. package/dist/auth/chapi.d.ts.map +1 -0
  5. package/dist/auth/chapi.js +107 -0
  6. package/dist/auth/chapi.js.map +1 -0
  7. package/dist/auth/loginFlow.d.ts +93 -0
  8. package/dist/auth/loginFlow.d.ts.map +1 -0
  9. package/dist/auth/loginFlow.js +149 -0
  10. package/dist/auth/loginFlow.js.map +1 -0
  11. package/dist/auth/loginRequest.d.ts +66 -0
  12. package/dist/auth/loginRequest.d.ts.map +1 -0
  13. package/dist/auth/loginRequest.js +83 -0
  14. package/dist/auth/loginRequest.js.map +1 -0
  15. package/dist/auth/verifyResponse.d.ts +52 -0
  16. package/dist/auth/verifyResponse.d.ts.map +1 -0
  17. package/dist/auth/verifyResponse.js +138 -0
  18. package/dist/auth/verifyResponse.js.map +1 -0
  19. package/dist/auth/verifyResponse.test.d.ts +2 -0
  20. package/dist/auth/verifyResponse.test.d.ts.map +1 -0
  21. package/dist/auth/verifyResponse.test.js +282 -0
  22. package/dist/auth/verifyResponse.test.js.map +1 -0
  23. package/dist/auth/walletRequestTypes.d.ts +143 -0
  24. package/dist/auth/walletRequestTypes.d.ts.map +1 -0
  25. package/dist/auth/walletRequestTypes.js +2 -0
  26. package/dist/auth/walletRequestTypes.js.map +1 -0
  27. package/dist/config.d.ts +150 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +27 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/dev/cli.d.ts +16 -0
  32. package/dist/dev/cli.d.ts.map +1 -0
  33. package/dist/dev/cli.js +122 -0
  34. package/dist/dev/cli.js.map +1 -0
  35. package/dist/dev/cli.test.d.ts +2 -0
  36. package/dist/dev/cli.test.d.ts.map +1 -0
  37. package/dist/dev/cli.test.js +39 -0
  38. package/dist/dev/cli.test.js.map +1 -0
  39. package/dist/dev/index.d.ts +10 -0
  40. package/dist/dev/index.d.ts.map +1 -0
  41. package/dist/dev/index.js +10 -0
  42. package/dist/dev/index.js.map +1 -0
  43. package/dist/dev/provisionDevGrants.d.ts +73 -0
  44. package/dist/dev/provisionDevGrants.d.ts.map +1 -0
  45. package/dist/dev/provisionDevGrants.js +176 -0
  46. package/dist/dev/provisionDevGrants.js.map +1 -0
  47. package/dist/grants.d.ts +59 -0
  48. package/dist/grants.d.ts.map +1 -0
  49. package/dist/grants.js +82 -0
  50. package/dist/grants.js.map +1 -0
  51. package/dist/grants.test.d.ts +2 -0
  52. package/dist/grants.test.d.ts.map +1 -0
  53. package/dist/grants.test.js +79 -0
  54. package/dist/grants.test.js.map +1 -0
  55. package/dist/identity/agents.d.ts +99 -0
  56. package/dist/identity/agents.d.ts.map +1 -0
  57. package/dist/identity/agents.js +132 -0
  58. package/dist/identity/agents.js.map +1 -0
  59. package/dist/identity/appSession.d.ts +78 -0
  60. package/dist/identity/appSession.d.ts.map +1 -0
  61. package/dist/identity/appSession.js +93 -0
  62. package/dist/identity/appSession.js.map +1 -0
  63. package/dist/identity/documentLoader.d.ts +20 -0
  64. package/dist/identity/documentLoader.d.ts.map +1 -0
  65. package/dist/identity/documentLoader.js +99 -0
  66. package/dist/identity/documentLoader.js.map +1 -0
  67. package/dist/identity/initAppSession.d.ts +27 -0
  68. package/dist/identity/initAppSession.d.ts.map +1 -0
  69. package/dist/identity/initAppSession.js +30 -0
  70. package/dist/identity/initAppSession.js.map +1 -0
  71. package/dist/identity/seedCredential.d.ts +74 -0
  72. package/dist/identity/seedCredential.d.ts.map +1 -0
  73. package/dist/identity/seedCredential.js +165 -0
  74. package/dist/identity/seedCredential.js.map +1 -0
  75. package/dist/identity/seedCredential.test.d.ts +2 -0
  76. package/dist/identity/seedCredential.test.d.ts.map +1 -0
  77. package/dist/identity/seedCredential.test.js +205 -0
  78. package/dist/identity/seedCredential.test.js.map +1 -0
  79. package/dist/identity/seedStore.d.ts +27 -0
  80. package/dist/identity/seedStore.d.ts.map +1 -0
  81. package/dist/identity/seedStore.js +74 -0
  82. package/dist/identity/seedStore.js.map +1 -0
  83. package/dist/index.d.ts +37 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +46 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/mui/ProtectedRoute.d.ts +10 -0
  88. package/dist/mui/ProtectedRoute.d.ts.map +1 -0
  89. package/dist/mui/ProtectedRoute.js +54 -0
  90. package/dist/mui/ProtectedRoute.js.map +1 -0
  91. package/dist/mui/ReconnectBanner.d.ts +2 -0
  92. package/dist/mui/ReconnectBanner.d.ts.map +1 -0
  93. package/dist/mui/ReconnectBanner.js +20 -0
  94. package/dist/mui/ReconnectBanner.js.map +1 -0
  95. package/dist/mui/SyncStatusChip.d.ts +2 -0
  96. package/dist/mui/SyncStatusChip.d.ts.map +1 -0
  97. package/dist/mui/SyncStatusChip.js +27 -0
  98. package/dist/mui/SyncStatusChip.js.map +1 -0
  99. package/dist/mui/index.d.ts +12 -0
  100. package/dist/mui/index.d.ts.map +1 -0
  101. package/dist/mui/index.js +12 -0
  102. package/dist/mui/index.js.map +1 -0
  103. package/dist/react/WasSessionProvider.d.ts +36 -0
  104. package/dist/react/WasSessionProvider.d.ts.map +1 -0
  105. package/dist/react/WasSessionProvider.js +35 -0
  106. package/dist/react/WasSessionProvider.js.map +1 -0
  107. package/dist/react/hooks.d.ts +66 -0
  108. package/dist/react/hooks.d.ts.map +1 -0
  109. package/dist/react/hooks.js +121 -0
  110. package/dist/react/hooks.js.map +1 -0
  111. package/dist/react/index.d.ts +11 -0
  112. package/dist/react/index.d.ts.map +1 -0
  113. package/dist/react/index.js +11 -0
  114. package/dist/react/index.js.map +1 -0
  115. package/dist/session/appReadyStore.d.ts +10 -0
  116. package/dist/session/appReadyStore.d.ts.map +1 -0
  117. package/dist/session/appReadyStore.js +22 -0
  118. package/dist/session/appReadyStore.js.map +1 -0
  119. package/dist/session/authStore.d.ts +71 -0
  120. package/dist/session/authStore.d.ts.map +1 -0
  121. package/dist/session/authStore.js +347 -0
  122. package/dist/session/authStore.js.map +1 -0
  123. package/dist/session/index.d.ts +10 -0
  124. package/dist/session/index.d.ts.map +1 -0
  125. package/dist/session/index.js +10 -0
  126. package/dist/session/index.js.map +1 -0
  127. package/dist/storage/entityStore.d.ts +51 -0
  128. package/dist/storage/entityStore.d.ts.map +1 -0
  129. package/dist/storage/entityStore.js +78 -0
  130. package/dist/storage/entityStore.js.map +1 -0
  131. package/dist/storage/localStore.d.ts +144 -0
  132. package/dist/storage/localStore.d.ts.map +1 -0
  133. package/dist/storage/localStore.js +289 -0
  134. package/dist/storage/localStore.js.map +1 -0
  135. package/dist/storage/rehydrate.d.ts +56 -0
  136. package/dist/storage/rehydrate.d.ts.map +1 -0
  137. package/dist/storage/rehydrate.js +87 -0
  138. package/dist/storage/rehydrate.js.map +1 -0
  139. package/dist/storage/rehydrate.test.d.ts +2 -0
  140. package/dist/storage/rehydrate.test.d.ts.map +1 -0
  141. package/dist/storage/rehydrate.test.js +172 -0
  142. package/dist/storage/rehydrate.test.js.map +1 -0
  143. package/dist/storage/storageManager.d.ts +39 -0
  144. package/dist/storage/storageManager.d.ts.map +1 -0
  145. package/dist/storage/storageManager.js +68 -0
  146. package/dist/storage/storageManager.js.map +1 -0
  147. package/dist/storage/syncController.d.ts +79 -0
  148. package/dist/storage/syncController.d.ts.map +1 -0
  149. package/dist/storage/syncController.js +189 -0
  150. package/dist/storage/syncController.js.map +1 -0
  151. package/dist/storage/syncStatusStore.d.ts +17 -0
  152. package/dist/storage/syncStatusStore.d.ts.map +1 -0
  153. package/dist/storage/syncStatusStore.js +19 -0
  154. package/dist/storage/syncStatusStore.js.map +1 -0
  155. package/dist/storage/wasRemoteStore.d.ts +73 -0
  156. package/dist/storage/wasRemoteStore.d.ts.map +1 -0
  157. package/dist/storage/wasRemoteStore.js +76 -0
  158. package/dist/storage/wasRemoteStore.js.map +1 -0
  159. package/dist/storage/wasSync.d.ts +42 -0
  160. package/dist/storage/wasSync.d.ts.map +1 -0
  161. package/dist/storage/wasSync.js +34 -0
  162. package/dist/storage/wasSync.js.map +1 -0
  163. package/dist/sync/changesQuery.d.ts +44 -0
  164. package/dist/sync/changesQuery.d.ts.map +1 -0
  165. package/dist/sync/changesQuery.js +61 -0
  166. package/dist/sync/changesQuery.js.map +1 -0
  167. package/dist/sync/changesQuery.test.d.ts +2 -0
  168. package/dist/sync/changesQuery.test.d.ts.map +1 -0
  169. package/dist/sync/changesQuery.test.js +145 -0
  170. package/dist/sync/changesQuery.test.js.map +1 -0
  171. package/dist/sync/docCipher.d.ts +68 -0
  172. package/dist/sync/docCipher.d.ts.map +1 -0
  173. package/dist/sync/docCipher.js +89 -0
  174. package/dist/sync/docCipher.js.map +1 -0
  175. package/dist/sync/feedMasterPort.d.ts +30 -0
  176. package/dist/sync/feedMasterPort.d.ts.map +1 -0
  177. package/dist/sync/feedMasterPort.js +58 -0
  178. package/dist/sync/feedMasterPort.js.map +1 -0
  179. package/dist/sync/index.d.ts +21 -0
  180. package/dist/sync/index.d.ts.map +1 -0
  181. package/dist/sync/index.js +21 -0
  182. package/dist/sync/index.js.map +1 -0
  183. package/dist/sync/lww.d.ts +29 -0
  184. package/dist/sync/lww.d.ts.map +1 -0
  185. package/dist/sync/lww.js +28 -0
  186. package/dist/sync/lww.js.map +1 -0
  187. package/dist/sync/lww.test.d.ts +2 -0
  188. package/dist/sync/lww.test.d.ts.map +1 -0
  189. package/dist/sync/lww.test.js +28 -0
  190. package/dist/sync/lww.test.js.map +1 -0
  191. package/dist/sync/lwwConflictHandler.d.ts +49 -0
  192. package/dist/sync/lwwConflictHandler.d.ts.map +1 -0
  193. package/dist/sync/lwwConflictHandler.js +65 -0
  194. package/dist/sync/lwwConflictHandler.js.map +1 -0
  195. package/dist/sync/lwwConflictHandler.test.d.ts +2 -0
  196. package/dist/sync/lwwConflictHandler.test.d.ts.map +1 -0
  197. package/dist/sync/lwwConflictHandler.test.js +78 -0
  198. package/dist/sync/lwwConflictHandler.test.js.map +1 -0
  199. package/dist/sync/pushWrites.d.ts +57 -0
  200. package/dist/sync/pushWrites.d.ts.map +1 -0
  201. package/dist/sync/pushWrites.js +159 -0
  202. package/dist/sync/pushWrites.js.map +1 -0
  203. package/dist/sync/pushWrites.test.d.ts +2 -0
  204. package/dist/sync/pushWrites.test.d.ts.map +1 -0
  205. package/dist/sync/pushWrites.test.js +287 -0
  206. package/dist/sync/pushWrites.test.js.map +1 -0
  207. package/dist/sync/syncedDocSchema.d.ts +22 -0
  208. package/dist/sync/syncedDocSchema.d.ts.map +1 -0
  209. package/dist/sync/syncedDocSchema.js +26 -0
  210. package/dist/sync/syncedDocSchema.js.map +1 -0
  211. package/dist/sync/types.d.ts +194 -0
  212. package/dist/sync/types.d.ts.map +1 -0
  213. package/dist/sync/types.js +36 -0
  214. package/dist/sync/types.js.map +1 -0
  215. package/dist/sync/wasReplication.d.ts +47 -0
  216. package/dist/sync/wasReplication.d.ts.map +1 -0
  217. package/dist/sync/wasReplication.js +55 -0
  218. package/dist/sync/wasReplication.js.map +1 -0
  219. package/dist/sync/wasSyncPort.d.ts +45 -0
  220. package/dist/sync/wasSyncPort.d.ts.map +1 -0
  221. package/dist/sync/wasSyncPort.js +170 -0
  222. package/dist/sync/wasSyncPort.js.map +1 -0
  223. package/package.json +142 -0
@@ -0,0 +1,79 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { parseGrants, parseInvocationTarget } from './grants.js';
6
+ /** Minimal delegated-zcap stub carrying just the fields `parseGrants` reads. */
7
+ function grant(invocationTarget) {
8
+ return {
9
+ '@context': ['https://w3id.org/zcap/v1'],
10
+ id: `urn:uuid:${invocationTarget}`,
11
+ parentCapability: 'urn:zcap:root:x',
12
+ controller: 'did:key:zApp',
13
+ invocationTarget,
14
+ allowedAction: ['GET', 'PUT'],
15
+ expires: '2099-01-01T00:00:00Z',
16
+ proof: {}
17
+ };
18
+ }
19
+ describe('parseInvocationTarget', () => {
20
+ it('splits a collection target into origin, space, collection', () => {
21
+ const parsed = parseInvocationTarget('http://localhost:3002/space/abc123/action-items');
22
+ expect(parsed).toEqual({
23
+ serverUrl: 'http://localhost:3002',
24
+ spaceId: 'abc123',
25
+ collectionId: 'action-items'
26
+ });
27
+ });
28
+ it('tolerates a trailing slash and omits collection on a space target', () => {
29
+ const parsed = parseInvocationTarget('https://was.example/space/s1/');
30
+ expect(parsed.serverUrl).toBe('https://was.example');
31
+ expect(parsed.spaceId).toBe('s1');
32
+ expect(parsed.collectionId).toBeUndefined();
33
+ });
34
+ it('rejects a non-WAS URL', () => {
35
+ expect(() => parseInvocationTarget('http://x/not-a-space/y')).toThrow();
36
+ expect(() => parseInvocationTarget('not-a-url')).toThrow();
37
+ });
38
+ });
39
+ describe('parseGrants', () => {
40
+ it('routes per-collection grants and derives one server + space', () => {
41
+ const parsed = parseGrants([
42
+ grant('http://localhost:3002/space/abc/action-items'),
43
+ grant('http://localhost:3002/space/abc/projects'),
44
+ grant('http://localhost:3002/space/abc/current-focus')
45
+ ]);
46
+ expect(parsed.serverUrl).toBe('http://localhost:3002');
47
+ expect(parsed.spaceId).toBe('abc');
48
+ expect(Object.keys(parsed.byCollectionId).sort()).toEqual([
49
+ 'action-items',
50
+ 'current-focus',
51
+ 'projects'
52
+ ]);
53
+ expect(parsed.byCollectionId['action-items']?.invocationTarget).toBe('http://localhost:3002/space/abc/action-items');
54
+ });
55
+ it('includes a space-scoped grant in topology but not routing', () => {
56
+ const parsed = parseGrants([
57
+ grant('http://localhost:3002/space/abc/'),
58
+ grant('http://localhost:3002/space/abc/goals')
59
+ ]);
60
+ expect(parsed.spaceId).toBe('abc');
61
+ expect(Object.keys(parsed.byCollectionId)).toEqual(['goals']);
62
+ });
63
+ it('rejects grants spanning two servers', () => {
64
+ expect(() => parseGrants([
65
+ grant('http://localhost:3002/space/abc/goals'),
66
+ grant('http://localhost:4000/space/abc/projects')
67
+ ])).toThrow(/two servers/);
68
+ });
69
+ it('rejects grants spanning two spaces', () => {
70
+ expect(() => parseGrants([
71
+ grant('http://localhost:3002/space/abc/goals'),
72
+ grant('http://localhost:3002/space/def/projects')
73
+ ])).toThrow(/two spaces/);
74
+ });
75
+ it('rejects an empty grant set', () => {
76
+ expect(() => parseGrants([])).toThrow();
77
+ });
78
+ });
79
+ //# sourceMappingURL=grants.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grants.test.js","sourceRoot":"","sources":["../src/grants.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * WAS identity + per-collection vault-key derivation from a 32-byte master seed.
6
+ *
7
+ * SEED-DERIVATION CONVENTION (pinned, part of the shared-key contract): the
8
+ * pinned `@interop/webkms-client` exposes `CapabilityAgent.fromSeed({ seed })`,
9
+ * which takes the raw 32 bytes AS-IS (no hashing). We use it for BOTH the master
10
+ * identity and the per-collection key-agreement keys, feeding raw bytes -- never
11
+ * `fromSecret`, which salt-hashes a STRING and would derive a different key for
12
+ * a byte array vs its text form.
13
+ *
14
+ * WHAT SELECTS THE KEY (verified against `CapabilityAgent.fromSeed`): the key
15
+ * material is derived from the `seed` bytes (the HMAC key) and the `keyName`
16
+ * string (the HMAC message) alone. The `handle` is stored on the returned agent
17
+ * as a cosmetic identifier and does NOT enter key derivation -- nor the derived
18
+ * did:key id, which is the fingerprint of the seed+keyName key pair. So the
19
+ * PINNED derivation inputs are the seed bytes and the `keyName` values
20
+ * (`IDENTITY_KEY_NAME` / `KAK_KEY_NAME` below); changing THOSE after first use
21
+ * is a data-migration event. The `identityHandle` / `kakHandle` parameters are
22
+ * safe to change and exist only so an app can supply a label for cosmetic
23
+ * continuity; they do not affect the identity, keys, or any stored data.
24
+ *
25
+ * Per-collection KAKs derive via `HKDF-SHA256(master, info = 'kak:v1:<id>')` to
26
+ * a 32-byte per-collection seed, then the standard Ed25519-to-X25519 path. HKDF
27
+ * one-wayness means a shared per-collection key exposes nothing about the master
28
+ * or sibling collections -- the future multi-app sharing unit.
29
+ *
30
+ * Not test-node-safe on React Native, but fine under Node/Vitest: the crypto
31
+ * stack (`webkms-client`, `x25519-key-agreement-key`) runs on the standard Web
32
+ * Crypto that Node 24 provides.
33
+ */
34
+ import { CapabilityAgent } from '@interop/webkms-client';
35
+ import { ZcapClient } from '@interop/ezcap';
36
+ import type { IKeyAgreementKey, IKeyResolver } from '@interop/data-integrity-core';
37
+ /**
38
+ * Default cosmetic label for the master identity agent. Local naming only (does
39
+ * not affect key material or the derived did:key); safe to override.
40
+ */
41
+ export declare const DEFAULT_IDENTITY_HANDLE = "was-react";
42
+ /**
43
+ * Default cosmetic label for a per-collection key-agreement agent. Local naming
44
+ * only (does not affect key material); safe to override.
45
+ */
46
+ export declare const DEFAULT_KAK_HANDLE = "was-react-kak";
47
+ /**
48
+ * The agents derived from the master seed: the app's stable did:key controller,
49
+ * its signing agent, and a ZcapClient for signing storage requests later.
50
+ */
51
+ export interface IdentityAgents {
52
+ controllerDid: string;
53
+ keyAgent: CapabilityAgent;
54
+ zcapClient: ZcapClient;
55
+ }
56
+ /**
57
+ * Derives one collection's X25519 key agreement key (KAK) plus its resolver, the
58
+ * material the doc-cipher needs. The concrete KAK type carries `type` /
59
+ * `publicKeyMultibase`; it is widened to `IKeyAgreementKey` only at the
60
+ * boundary.
61
+ */
62
+ export interface CollectionKeys {
63
+ keyAgreementKey: IKeyAgreementKey;
64
+ keyResolver: IKeyResolver;
65
+ }
66
+ /**
67
+ * Derives the master identity agents from the master seed. The did:key
68
+ * controller is stable across devices for the same seed.
69
+ *
70
+ * @param options {object}
71
+ * @param options.seed {Uint8Array} the 32-byte master seed
72
+ * @param [options.identityHandle] {string} cosmetic agent label; does not
73
+ * affect keys or the derived DID (defaults to `DEFAULT_IDENTITY_HANDLE`)
74
+ * @returns {Promise<IdentityAgents>}
75
+ */
76
+ export declare function deriveIdentity({ seed, identityHandle }: {
77
+ seed: Uint8Array;
78
+ identityHandle?: string;
79
+ }): Promise<IdentityAgents>;
80
+ /**
81
+ * Derives one collection's vault key material from the master seed:
82
+ * `HKDF(master, 'kak:v1:<collectionId>')` to a per-collection seed, then the
83
+ * Ed25519-to-X25519 (Montgomery-form) key-agreement key. Deterministic, so the
84
+ * same seed decrypts the same envelopes on any device. Encryption uses these
85
+ * per-collection KAKs from day one -- never share one KAK across collections.
86
+ *
87
+ * @param options {object}
88
+ * @param options.seed {Uint8Array} the 32-byte master seed
89
+ * @param options.collectionId {string} the WAS collection id (the HKDF label)
90
+ * @param [options.kakHandle] {string} cosmetic agent label; does not affect
91
+ * keys (defaults to `DEFAULT_KAK_HANDLE`)
92
+ * @returns {Promise<CollectionKeys>}
93
+ */
94
+ export declare function deriveCollectionKeys({ seed, collectionId, kakHandle }: {
95
+ seed: Uint8Array;
96
+ collectionId: string;
97
+ kakHandle?: string;
98
+ }): Promise<CollectionKeys>;
99
+ //# sourceMappingURL=agents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/identity/agents.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAE3C,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACb,MAAM,8BAA8B,CAAA;AAErC;;;GAGG;AACH,eAAO,MAAM,uBAAuB,cAAc,CAAA;AAElD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,kBAAkB,CAAA;AAOjD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,eAAe,CAAA;IACzB,UAAU,EAAE,UAAU,CAAA;CACvB;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,eAAe,EAAE,gBAAgB,CAAA;IACjC,WAAW,EAAE,YAAY,CAAA;CAC1B;AAiCD;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,EACnC,IAAI,EACJ,cAAwC,EACzC,EAAE;IACD,IAAI,EAAE,UAAU,CAAA;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC,cAAc,CAAC,CAa1B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,oBAAoB,CAAC,EACzC,IAAI,EACJ,YAAY,EACZ,SAA8B,EAC/B,EAAE;IACD,IAAI,EAAE,UAAU,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GAAG,OAAO,CAAC,cAAc,CAAC,CAyB1B"}
@@ -0,0 +1,132 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * WAS identity + per-collection vault-key derivation from a 32-byte master seed.
6
+ *
7
+ * SEED-DERIVATION CONVENTION (pinned, part of the shared-key contract): the
8
+ * pinned `@interop/webkms-client` exposes `CapabilityAgent.fromSeed({ seed })`,
9
+ * which takes the raw 32 bytes AS-IS (no hashing). We use it for BOTH the master
10
+ * identity and the per-collection key-agreement keys, feeding raw bytes -- never
11
+ * `fromSecret`, which salt-hashes a STRING and would derive a different key for
12
+ * a byte array vs its text form.
13
+ *
14
+ * WHAT SELECTS THE KEY (verified against `CapabilityAgent.fromSeed`): the key
15
+ * material is derived from the `seed` bytes (the HMAC key) and the `keyName`
16
+ * string (the HMAC message) alone. The `handle` is stored on the returned agent
17
+ * as a cosmetic identifier and does NOT enter key derivation -- nor the derived
18
+ * did:key id, which is the fingerprint of the seed+keyName key pair. So the
19
+ * PINNED derivation inputs are the seed bytes and the `keyName` values
20
+ * (`IDENTITY_KEY_NAME` / `KAK_KEY_NAME` below); changing THOSE after first use
21
+ * is a data-migration event. The `identityHandle` / `kakHandle` parameters are
22
+ * safe to change and exist only so an app can supply a label for cosmetic
23
+ * continuity; they do not affect the identity, keys, or any stored data.
24
+ *
25
+ * Per-collection KAKs derive via `HKDF-SHA256(master, info = 'kak:v1:<id>')` to
26
+ * a 32-byte per-collection seed, then the standard Ed25519-to-X25519 path. HKDF
27
+ * one-wayness means a shared per-collection key exposes nothing about the master
28
+ * or sibling collections -- the future multi-app sharing unit.
29
+ *
30
+ * Not test-node-safe on React Native, but fine under Node/Vitest: the crypto
31
+ * stack (`webkms-client`, `x25519-key-agreement-key`) runs on the standard Web
32
+ * Crypto that Node 24 provides.
33
+ */
34
+ import { CapabilityAgent } from '@interop/webkms-client';
35
+ import { Ed25519Signature2020 } from '@interop/ed25519-signature';
36
+ import { ZcapClient } from '@interop/ezcap';
37
+ import { X25519KeyAgreementKey2020 } from '@interop/x25519-key-agreement-key';
38
+ /**
39
+ * Default cosmetic label for the master identity agent. Local naming only (does
40
+ * not affect key material or the derived did:key); safe to override.
41
+ */
42
+ export const DEFAULT_IDENTITY_HANDLE = 'was-react';
43
+ /**
44
+ * Default cosmetic label for a per-collection key-agreement agent. Local naming
45
+ * only (does not affect key material); safe to override.
46
+ */
47
+ export const DEFAULT_KAK_HANDLE = 'was-react-kak';
48
+ // PINNED key-derivation inputs (the HMAC message that, with the seed, selects
49
+ // the key). Changing either after first use is a data-migration event.
50
+ const IDENTITY_KEY_NAME = 'app-key';
51
+ const KAK_KEY_NAME = 'kak';
52
+ /**
53
+ * HKDF-SHA256 expansion of the master seed into a 32-byte per-context seed.
54
+ *
55
+ * @param master {Uint8Array}
56
+ * @param info {string} the domain-separation label (e.g. `kak:v1:projects`)
57
+ * @returns {Promise<Uint8Array>}
58
+ */
59
+ async function hkdfExpand(master, info) {
60
+ const key = await globalThis.crypto.subtle.importKey('raw', master, 'HKDF', false, ['deriveBits']);
61
+ const bits = await globalThis.crypto.subtle.deriveBits({
62
+ name: 'HKDF',
63
+ hash: 'SHA-256',
64
+ salt: new Uint8Array(0),
65
+ info: new TextEncoder().encode(info)
66
+ }, key, 256);
67
+ return new Uint8Array(bits);
68
+ }
69
+ /**
70
+ * Derives the master identity agents from the master seed. The did:key
71
+ * controller is stable across devices for the same seed.
72
+ *
73
+ * @param options {object}
74
+ * @param options.seed {Uint8Array} the 32-byte master seed
75
+ * @param [options.identityHandle] {string} cosmetic agent label; does not
76
+ * affect keys or the derived DID (defaults to `DEFAULT_IDENTITY_HANDLE`)
77
+ * @returns {Promise<IdentityAgents>}
78
+ */
79
+ export async function deriveIdentity({ seed, identityHandle = DEFAULT_IDENTITY_HANDLE }) {
80
+ const keyAgent = await CapabilityAgent.fromSeed({
81
+ seed,
82
+ handle: identityHandle,
83
+ keyName: IDENTITY_KEY_NAME
84
+ });
85
+ const signer = keyAgent.getSigner();
86
+ const zcapClient = new ZcapClient({
87
+ SuiteClass: Ed25519Signature2020,
88
+ invocationSigner: signer,
89
+ delegationSigner: signer
90
+ });
91
+ return { controllerDid: keyAgent.id, keyAgent, zcapClient };
92
+ }
93
+ /**
94
+ * Derives one collection's vault key material from the master seed:
95
+ * `HKDF(master, 'kak:v1:<collectionId>')` to a per-collection seed, then the
96
+ * Ed25519-to-X25519 (Montgomery-form) key-agreement key. Deterministic, so the
97
+ * same seed decrypts the same envelopes on any device. Encryption uses these
98
+ * per-collection KAKs from day one -- never share one KAK across collections.
99
+ *
100
+ * @param options {object}
101
+ * @param options.seed {Uint8Array} the 32-byte master seed
102
+ * @param options.collectionId {string} the WAS collection id (the HKDF label)
103
+ * @param [options.kakHandle] {string} cosmetic agent label; does not affect
104
+ * keys (defaults to `DEFAULT_KAK_HANDLE`)
105
+ * @returns {Promise<CollectionKeys>}
106
+ */
107
+ export async function deriveCollectionKeys({ seed, collectionId, kakHandle = DEFAULT_KAK_HANDLE }) {
108
+ const collectionSeed = await hkdfExpand(seed, `kak:v1:${collectionId}`);
109
+ const keyAgent = await CapabilityAgent.fromSeed({
110
+ seed: collectionSeed,
111
+ handle: kakHandle,
112
+ keyName: KAK_KEY_NAME
113
+ });
114
+ const keyAgreementKey = X25519KeyAgreementKey2020.fromEd25519VerificationKey2020({
115
+ keyPair: keyAgent.getVerificationKeyPair()
116
+ });
117
+ const keyResolver = async ({ id }) => {
118
+ if (id !== keyAgreementKey.id) {
119
+ throw new Error(`Unknown key id "${id}".`);
120
+ }
121
+ return {
122
+ id: keyAgreementKey.id,
123
+ type: keyAgreementKey.type,
124
+ publicKeyMultibase: keyAgreementKey.publicKeyMultibase
125
+ };
126
+ };
127
+ return {
128
+ keyAgreementKey: keyAgreementKey,
129
+ keyResolver
130
+ };
131
+ }
132
+ //# sourceMappingURL=agents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.js","sourceRoot":"","sources":["../../src/identity/agents.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The persisted app session: what a reload needs to restore the authenticated
6
+ * state with ZERO wallet popups, adapted to the RP model where the seed itself
7
+ * is persisted -- the wallet remains the recovery source of truth, this is only
8
+ * the hot cache.
9
+ *
10
+ * Record: `{ seed, controllerDid, serverUrl, spaceId, grants, expires }`.
11
+ * `expires` is the earliest `expires` across the granted zcaps; a record past
12
+ * it is cleared on load and the caller falls through to the returning-login
13
+ * flow (same seed comes back from the wallet, same DID, same vault keys).
14
+ *
15
+ * Persistence rides on a `SeedStore` (see `seedStore.ts`) the caller supplies.
16
+ */
17
+ import type { IZcap } from '@interop/data-integrity-core';
18
+ import type { SeedStore } from './seedStore.js';
19
+ export interface AppSessionRecord {
20
+ controllerDid: string;
21
+ serverUrl: string;
22
+ spaceId: string;
23
+ grants: IZcap[];
24
+ /** ISO timestamp: the earliest expiry across the granted zcaps. */
25
+ expires: string;
26
+ }
27
+ /** A restored session: the record plus the separately persisted seed. */
28
+ export interface RestoredAppSession extends AppSessionRecord {
29
+ seed: Uint8Array;
30
+ }
31
+ /** Whether an ISO `expires` timestamp is in the past (or malformed). */
32
+ export declare function isExpired(expires: string, now?: Date): boolean;
33
+ /**
34
+ * Whether an ISO `expires` timestamp is within `thresholdMs` of now (or already
35
+ * past, or malformed) -- the signal to surface the reconnect banner proactively,
36
+ * before a live request fails with 401/403.
37
+ */
38
+ export declare function isNearExpiry(expires: string, thresholdMs: number, now?: Date): boolean;
39
+ /**
40
+ * The earliest `expires` across a grant set. Grants without a parseable
41
+ * `expires` are ignored; returns `null` when none carries one (callers treat
42
+ * that as not restorable -- wallet grants always carry an expiry).
43
+ */
44
+ export declare function earliestExpiry(grants: IZcap[]): string | null;
45
+ /**
46
+ * Persists the session (seed + record) for hot restore.
47
+ *
48
+ * @param options {object}
49
+ * @param options.session {RestoredAppSession}
50
+ * @param options.store {SeedStore}
51
+ * @returns {Promise<void>}
52
+ */
53
+ export declare function persistAppSession({ session, store }: {
54
+ session: RestoredAppSession;
55
+ store: SeedStore;
56
+ }): Promise<void>;
57
+ /**
58
+ * Restores the persisted session, or returns `null` (clearing any stale state)
59
+ * when it is missing, malformed, or expired.
60
+ *
61
+ * @param options {object}
62
+ * @param options.store {SeedStore}
63
+ * @returns {Promise<RestoredAppSession | null>}
64
+ */
65
+ export declare function restoreAppSession({ store }: {
66
+ store: SeedStore;
67
+ }): Promise<RestoredAppSession | null>;
68
+ /**
69
+ * Wipes the persisted session (seed + record).
70
+ *
71
+ * @param options {object}
72
+ * @param options.store {SeedStore}
73
+ * @returns {Promise<void>}
74
+ */
75
+ export declare function clearAppSession({ store }: {
76
+ store: SeedStore;
77
+ }): Promise<void>;
78
+ //# sourceMappingURL=appSession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appSession.d.ts","sourceRoot":"","sources":["../../src/identity/appSession.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAA;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,KAAK,EAAE,CAAA;IACf,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,yEAAyE;AACzE,MAAM,WAAW,kBAAmB,SAAQ,gBAAgB;IAC1D,IAAI,EAAE,UAAU,CAAA;CACjB;AAED,wEAAwE;AACxE,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAG1E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,GAAG,GAAE,IAAiB,GACrB,OAAO,CAMT;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,GAAG,IAAI,CAgB7D;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,EACtC,OAAO,EACP,KAAK,EACN,EAAE;IACD,OAAO,EAAE,kBAAkB,CAAA;IAC3B,KAAK,EAAE,SAAS,CAAA;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,EACtC,KAAK,EACN,EAAE;IACD,KAAK,EAAE,SAAS,CAAA;CACjB,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAuBrC;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,EACpC,KAAK,EACN,EAAE;IACD,KAAK,EAAE,SAAS,CAAA;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhB"}
@@ -0,0 +1,93 @@
1
+ /** Whether an ISO `expires` timestamp is in the past (or malformed). */
2
+ export function isExpired(expires, now = new Date()) {
3
+ const at = new Date(expires).getTime();
4
+ return Number.isNaN(at) || at <= now.getTime();
5
+ }
6
+ /**
7
+ * Whether an ISO `expires` timestamp is within `thresholdMs` of now (or already
8
+ * past, or malformed) -- the signal to surface the reconnect banner proactively,
9
+ * before a live request fails with 401/403.
10
+ */
11
+ export function isNearExpiry(expires, thresholdMs, now = new Date()) {
12
+ const at = new Date(expires).getTime();
13
+ if (Number.isNaN(at)) {
14
+ return true;
15
+ }
16
+ return at - now.getTime() <= thresholdMs;
17
+ }
18
+ /**
19
+ * The earliest `expires` across a grant set. Grants without a parseable
20
+ * `expires` are ignored; returns `null` when none carries one (callers treat
21
+ * that as not restorable -- wallet grants always carry an expiry).
22
+ */
23
+ export function earliestExpiry(grants) {
24
+ let earliest = null;
25
+ for (const grant of grants) {
26
+ const expires = grant.expires;
27
+ if (typeof expires !== 'string') {
28
+ continue;
29
+ }
30
+ const at = new Date(expires).getTime();
31
+ if (Number.isNaN(at)) {
32
+ continue;
33
+ }
34
+ if (earliest === null || at < new Date(earliest).getTime()) {
35
+ earliest = expires;
36
+ }
37
+ }
38
+ return earliest;
39
+ }
40
+ /**
41
+ * Persists the session (seed + record) for hot restore.
42
+ *
43
+ * @param options {object}
44
+ * @param options.session {RestoredAppSession}
45
+ * @param options.store {SeedStore}
46
+ * @returns {Promise<void>}
47
+ */
48
+ export async function persistAppSession({ session, store }) {
49
+ const { seed, ...record } = session;
50
+ await store.saveSeed(seed);
51
+ await store.saveRecord(record);
52
+ }
53
+ /**
54
+ * Restores the persisted session, or returns `null` (clearing any stale state)
55
+ * when it is missing, malformed, or expired.
56
+ *
57
+ * @param options {object}
58
+ * @param options.store {SeedStore}
59
+ * @returns {Promise<RestoredAppSession | null>}
60
+ */
61
+ export async function restoreAppSession({ store }) {
62
+ const [seed, stored] = await Promise.all([
63
+ store.loadSeed(),
64
+ store.loadRecord()
65
+ ]);
66
+ const record = stored;
67
+ if (!seed ||
68
+ !record ||
69
+ typeof record.controllerDid !== 'string' ||
70
+ typeof record.serverUrl !== 'string' ||
71
+ typeof record.spaceId !== 'string' ||
72
+ !Array.isArray(record.grants) ||
73
+ record.grants.length === 0 ||
74
+ typeof record.expires !== 'string') {
75
+ return null;
76
+ }
77
+ if (isExpired(record.expires)) {
78
+ await clearAppSession({ store });
79
+ return null;
80
+ }
81
+ return { seed, ...record };
82
+ }
83
+ /**
84
+ * Wipes the persisted session (seed + record).
85
+ *
86
+ * @param options {object}
87
+ * @param options.store {SeedStore}
88
+ * @returns {Promise<void>}
89
+ */
90
+ export async function clearAppSession({ store }) {
91
+ await store.clearSeedStore();
92
+ }
93
+ //# sourceMappingURL=appSession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appSession.js","sourceRoot":"","sources":["../../src/identity/appSession.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /** The envelope shape returned for every resolved URL. */
2
+ export type DocumentLoader = (url: string) => Promise<{
3
+ contextUrl: string | null;
4
+ document: unknown;
5
+ documentUrl: string;
6
+ }>;
7
+ /**
8
+ * Builds the JSON-LD document loader handed to `@interop/vc` issuance and
9
+ * verifier-core verification. Same envelope shape as the base loader; the http
10
+ * did:web dev shim activates only when `wasServerUrl` is an http URL.
11
+ *
12
+ * @param [options] {object}
13
+ * @param [options.wasServerUrl] {string} the configured WAS server URL; when
14
+ * it is an http URL, did:web DIDs on that exact host are resolved over http
15
+ * @returns {DocumentLoader}
16
+ */
17
+ export declare function createDocumentLoader({ wasServerUrl }?: {
18
+ wasServerUrl?: string;
19
+ }): DocumentLoader;
20
+ //# sourceMappingURL=documentLoader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documentLoader.d.ts","sourceRoot":"","sources":["../../src/identity/documentLoader.ts"],"names":[],"mappings":"AAeA,0DAA0D;AAC1D,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACpD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,QAAQ,EAAE,OAAO,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAC,CAAA;AA6CF;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,YAAY,EACb,GAAE;IACD,YAAY,CAAC,EAAE,MAAM,CAAA;CACjB,GAAG,cAAc,CA+CtB"}
@@ -0,0 +1,99 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The app's one JSON-LD document loader: static security contexts plus did:key
6
+ * / did:web resolution (`@interop/security-document-loader`).
7
+ *
8
+ * Local-dev shim: the did:web method mandates https, but a local WAS server
9
+ * (which hosts the wallet's did:web documents) may run plain http. When the
10
+ * configured WAS server URL is http, DIDs on exactly that host are resolved over
11
+ * it; everything else falls through to the standard loader. In production
12
+ * (https server, or no server URL) the shim never engages.
13
+ */
14
+ import { securityLoader } from '@interop/security-document-loader';
15
+ const baseLoader = securityLoader({ fetchRemoteContexts: true }).build();
16
+ /** Per-suite context for a dereferenced verification-method node. */
17
+ const CONTEXT_BY_KEY_TYPE = {
18
+ Ed25519VerificationKey2020: 'https://w3id.org/security/suites/ed25519-2020/v1',
19
+ X25519KeyAgreementKey2020: 'https://w3id.org/security/suites/x25519-2020/v1',
20
+ Multikey: 'https://w3id.org/security/multikey/v1'
21
+ };
22
+ /** Dereferences a `#fragment` subnode the way did-web-resolver's getNode does. */
23
+ function nodeOf(didDocument, id) {
24
+ const methods = (didDocument.verificationMethod ?? []);
25
+ let match = methods.find(vm => vm?.id === id);
26
+ if (!match) {
27
+ for (const [key, value] of Object.entries(didDocument)) {
28
+ if (key === '@context' || key === 'verificationMethod') {
29
+ continue;
30
+ }
31
+ if (Array.isArray(value)) {
32
+ match = value.find(entry => entry?.id === id);
33
+ }
34
+ else if (value?.id === id) {
35
+ match = value;
36
+ }
37
+ if (match) {
38
+ break;
39
+ }
40
+ }
41
+ }
42
+ if (!match) {
43
+ throw new Error(`DID document entity with id "${id}" not found.`);
44
+ }
45
+ const context = (match.type && CONTEXT_BY_KEY_TYPE[match.type]) ?? didDocument['@context'];
46
+ return { '@context': context, ...match };
47
+ }
48
+ /**
49
+ * Builds the JSON-LD document loader handed to `@interop/vc` issuance and
50
+ * verifier-core verification. Same envelope shape as the base loader; the http
51
+ * did:web dev shim activates only when `wasServerUrl` is an http URL.
52
+ *
53
+ * @param [options] {object}
54
+ * @param [options.wasServerUrl] {string} the configured WAS server URL; when
55
+ * it is an http URL, did:web DIDs on that exact host are resolved over http
56
+ * @returns {DocumentLoader}
57
+ */
58
+ export function createDocumentLoader({ wasServerUrl } = {}) {
59
+ /**
60
+ * Maps a did:web DID onto the local http WAS server's URL, or `null` when the
61
+ * DID does not live on that host (or the shim is not applicable).
62
+ */
63
+ function insecureDidWebUrl(didAuthority) {
64
+ if (!wasServerUrl || !wasServerUrl.startsWith('http://')) {
65
+ return null;
66
+ }
67
+ const server = new URL(wasServerUrl);
68
+ const segments = didAuthority.split(':');
69
+ // ['did', 'web', '<encoded host>', ...path]
70
+ const host = segments[2] ? decodeURIComponent(segments[2]) : '';
71
+ if (host !== server.host) {
72
+ return null;
73
+ }
74
+ const path = segments.slice(3).map(decodeURIComponent).join('/');
75
+ return `http://${host}/${path ? `${path}/did.json` : '.well-known/did.json'}`;
76
+ }
77
+ return async function documentLoader(url) {
78
+ if (url.startsWith('did:web:')) {
79
+ const [didAuthority = ''] = url.split(/[#?]/);
80
+ const fetchUrl = insecureDidWebUrl(didAuthority);
81
+ if (fetchUrl) {
82
+ const response = await fetch(fetchUrl);
83
+ if (!response.ok) {
84
+ throw new Error(`Could not fetch the DID document at "${fetchUrl}" (status ${response.status}).`);
85
+ }
86
+ const didDocument = (await response.json());
87
+ if (didDocument.id !== didAuthority) {
88
+ throw new Error(`DID document for "${didAuthority}" not found.`);
89
+ }
90
+ const document = url.includes('#')
91
+ ? nodeOf(didDocument, url)
92
+ : didDocument;
93
+ return { contextUrl: null, document, documentUrl: url };
94
+ }
95
+ }
96
+ return (await baseLoader(url));
97
+ };
98
+ }
99
+ //# sourceMappingURL=documentLoader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documentLoader.js","sourceRoot":"","sources":["../../src/identity/documentLoader.ts"],"names":[],"mappings":"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"}