@enbox/api 0.2.3 → 0.2.4

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 (73) hide show
  1. package/README.md +235 -35
  2. package/dist/browser.mjs +13 -13
  3. package/dist/browser.mjs.map +4 -4
  4. package/dist/esm/dwn-api.js +24 -10
  5. package/dist/esm/dwn-api.js.map +1 -1
  6. package/dist/esm/index.js +6 -0
  7. package/dist/esm/index.js.map +1 -1
  8. package/dist/esm/live-query.js +34 -5
  9. package/dist/esm/live-query.js.map +1 -1
  10. package/dist/esm/permission-grant.js +3 -6
  11. package/dist/esm/permission-grant.js.map +1 -1
  12. package/dist/esm/permission-request.js +4 -7
  13. package/dist/esm/permission-request.js.map +1 -1
  14. package/dist/esm/record-data.js +131 -0
  15. package/dist/esm/record-data.js.map +1 -0
  16. package/dist/esm/record-types.js +9 -0
  17. package/dist/esm/record-types.js.map +1 -0
  18. package/dist/esm/record.js +58 -184
  19. package/dist/esm/record.js.map +1 -1
  20. package/dist/esm/repository-types.js +13 -0
  21. package/dist/esm/repository-types.js.map +1 -0
  22. package/dist/esm/repository.js +347 -0
  23. package/dist/esm/repository.js.map +1 -0
  24. package/dist/esm/typed-live-query.js +101 -0
  25. package/dist/esm/typed-live-query.js.map +1 -0
  26. package/dist/esm/typed-record.js +227 -0
  27. package/dist/esm/typed-record.js.map +1 -0
  28. package/dist/esm/typed-web5.js +134 -23
  29. package/dist/esm/typed-web5.js.map +1 -1
  30. package/dist/esm/web5.js +78 -20
  31. package/dist/esm/web5.js.map +1 -1
  32. package/dist/types/dwn-api.d.ts.map +1 -1
  33. package/dist/types/index.d.ts +6 -0
  34. package/dist/types/index.d.ts.map +1 -1
  35. package/dist/types/live-query.d.ts +43 -4
  36. package/dist/types/live-query.d.ts.map +1 -1
  37. package/dist/types/permission-grant.d.ts +1 -1
  38. package/dist/types/permission-grant.d.ts.map +1 -1
  39. package/dist/types/permission-request.d.ts +1 -1
  40. package/dist/types/permission-request.d.ts.map +1 -1
  41. package/dist/types/record-data.d.ts +49 -0
  42. package/dist/types/record-data.d.ts.map +1 -0
  43. package/dist/types/record-types.d.ts +145 -0
  44. package/dist/types/record-types.d.ts.map +1 -0
  45. package/dist/types/record.d.ts +13 -144
  46. package/dist/types/record.d.ts.map +1 -1
  47. package/dist/types/repository-types.d.ts +137 -0
  48. package/dist/types/repository-types.d.ts.map +1 -0
  49. package/dist/types/repository.d.ts +59 -0
  50. package/dist/types/repository.d.ts.map +1 -0
  51. package/dist/types/typed-live-query.d.ts +86 -0
  52. package/dist/types/typed-live-query.d.ts.map +1 -0
  53. package/dist/types/typed-record.d.ts +179 -0
  54. package/dist/types/typed-record.d.ts.map +1 -0
  55. package/dist/types/typed-web5.d.ts +55 -24
  56. package/dist/types/typed-web5.d.ts.map +1 -1
  57. package/dist/types/web5.d.ts +47 -2
  58. package/dist/types/web5.d.ts.map +1 -1
  59. package/package.json +8 -7
  60. package/src/dwn-api.ts +30 -13
  61. package/src/index.ts +6 -0
  62. package/src/live-query.ts +71 -7
  63. package/src/permission-grant.ts +2 -3
  64. package/src/permission-request.ts +3 -4
  65. package/src/record-data.ts +155 -0
  66. package/src/record-types.ts +188 -0
  67. package/src/record.ts +86 -389
  68. package/src/repository-types.ts +249 -0
  69. package/src/repository.ts +391 -0
  70. package/src/typed-live-query.ts +156 -0
  71. package/src/typed-record.ts +309 -0
  72. package/src/typed-web5.ts +202 -49
  73. package/src/web5.ts +150 -23
package/src/web5.ts CHANGED
@@ -96,6 +96,38 @@ export type Web5AnonymousApi = {
96
96
  dwn: DwnReaderApi;
97
97
  };
98
98
 
99
+ /** Parameters passed to the onProviderAuthRequired callback. */
100
+ export type ProviderAuthParams = {
101
+ /** Full authorize URL to open in a browser (query params already appended). */
102
+ authorizeUrl: string;
103
+ /** The DWN endpoint URL this auth is for (informational). */
104
+ dwnEndpoint: string;
105
+ /** CSRF nonce — the provider will return this unchanged in the redirect. */
106
+ state: string;
107
+ };
108
+
109
+ /** Result returned by the app after the user completes provider auth. */
110
+ export type ProviderAuthResult = {
111
+ /** Authorization code from the provider's redirect. */
112
+ code: string;
113
+ /** Must match the state from ProviderAuthParams (CSRF validation). */
114
+ state: string;
115
+ };
116
+
117
+ /** Persisted registration token data for a DWN endpoint. */
118
+ export type RegistrationTokenData = {
119
+ /** Opaque registration token for POST /registration. */
120
+ registrationToken: string;
121
+ /** Refresh token for obtaining new registration tokens. */
122
+ refreshToken?: string;
123
+ /** Unix timestamp (ms) when the token expires. Undefined = never expires. */
124
+ expiresAt?: number;
125
+ /** Provider's token exchange URL (needed for code exchange). */
126
+ tokenUrl: string;
127
+ /** Provider's refresh URL (needed for token refresh). */
128
+ refreshUrl?: string;
129
+ };
130
+
99
131
  /** Optional overrides that can be provided when calling {@link Web5.connect}. */
100
132
  export type Web5ConnectOptions = {
101
133
  /**
@@ -190,10 +222,29 @@ export type Web5ConnectOptions = {
190
222
  * If registration is successful, the `onSuccess` callback will be called.
191
223
  */
192
224
  registration? : {
193
- /** Called when all of the DWN registrations are successful */
194
- onSuccess: () => void;
195
- /** Called when any of the DWN registrations fail */
196
- onFailure: (error: any) => void;
225
+ /** Called when all of the DWN registrations are successful. */
226
+ onSuccess : () => void;
227
+ /** Called when any of the DWN registrations fail. */
228
+ onFailure : (error: any) => void;
229
+
230
+ /**
231
+ * Called when a DWN endpoint requires provider auth (`'provider-auth-v0'`).
232
+ * The app is responsible for opening the authorizeUrl in a browser,
233
+ * capturing the redirect back, and returning the auth code.
234
+ * If not provided, provider-auth endpoints fall back to PoW registration.
235
+ */
236
+ onProviderAuthRequired? : (params: ProviderAuthParams) => Promise<ProviderAuthResult>;
237
+
238
+ /**
239
+ * Pre-existing registration tokens from a previous session, keyed by DWN endpoint URL.
240
+ * If a valid (non-expired) token exists for an endpoint, it is used directly.
241
+ */
242
+ registrationTokens? : Record<string, RegistrationTokenData>;
243
+
244
+ /**
245
+ * Called when new registration tokens are obtained so the app can persist them.
246
+ */
247
+ onRegistrationTokens? : (tokens: Record<string, RegistrationTokenData>) => void;
197
248
  }
198
249
  };
199
250
 
@@ -449,17 +500,14 @@ export class Web5 {
449
500
  throw new Error(`Failed to connect to wallet: ${error.message}`);
450
501
  }
451
502
  } else {
452
- // No connected identity found and no connectOptions provided, use local Identities
453
- // Query the Agent's DWN tenant for identity records.
503
+ // No connected (WalletConnect) identity and no walletConnectOptions provided.
504
+ // Look for an existing local identity, or create one on first use.
454
505
  const identities = await userAgent.identity.list();
455
506
 
456
- // If an existing identity is not found found, create a new one.
457
- const existingIdentityCount = identities.length;
458
- if (existingIdentityCount === 0) {
459
- // since we are creating a new identity, we will want to register sync for the created Did
507
+ if (identities.length === 0) {
460
508
  registerSync = true;
461
509
 
462
- // Generate a new Identity for the end-user.
510
+ // First use — generate a new Identity for the end-user.
463
511
  identity = await userAgent.identity.create({
464
512
  didMethod : 'dht',
465
513
  metadata : { name: 'Default' },
@@ -489,8 +537,9 @@ export class Web5 {
489
537
  });
490
538
 
491
539
  } else {
492
- // If multiple identities are found, use the first one.
493
- // TODO: Implement selecting a connectedDid from multiple identities
540
+ // Reconnecting use the first local identity. When the agent manages
541
+ // multiple identities (e.g. created via agent.identity.create()), the
542
+ // first one returned by the store is used as the default for connect().
494
543
  identity = identities[0];
495
544
  }
496
545
  }
@@ -500,27 +549,105 @@ export class Web5 {
500
549
  // If the stored identity has a connected DID, use the identity DID as the delegated DID, otherwise it is undefined.
501
550
  delegateDid = identity.metadata.connectedDid ? identity.did.uri : undefined;
502
551
  if (registration !== undefined) {
503
- // If a registration object is passed, we attempt to register the AgentDID and the ConnectedDID with the DWN endpoints provided
552
+ const updatedTokens: Record<string, RegistrationTokenData> = {
553
+ ...(registration.registrationTokens ?? {}),
554
+ };
555
+
504
556
  try {
505
557
  for (const dwnEndpoint of serviceEndpointNodes) {
506
- // check if endpoint needs registration
507
558
  const serverInfo = await userAgent.rpc.getServerInfo(dwnEndpoint);
559
+
508
560
  if (serverInfo.registrationRequirements.length === 0) {
509
- // no registration required
510
561
  continue;
511
562
  }
512
563
 
513
- // register the agent DID
514
- await DwnRegistrar.registerTenant(dwnEndpoint, agent.agentDid.uri);
564
+ // Deduplicate DIDs to register.
565
+ const didsToRegister = [agent.agentDid.uri, connectedDid]
566
+ .filter((did, i, arr): did is string => arr.indexOf(did) === i);
567
+
568
+ const hasProviderAuth = serverInfo.registrationRequirements.includes('provider-auth-v0')
569
+ && serverInfo.providerAuth !== undefined;
570
+
571
+ if (hasProviderAuth && registration.onProviderAuthRequired) {
572
+ // --- Provider Auth Path ---
573
+ let tokenData = updatedTokens[dwnEndpoint];
574
+
575
+ // Refresh expired tokens.
576
+ if (tokenData?.expiresAt !== undefined && tokenData.expiresAt < Date.now()) {
577
+ if (tokenData.refreshUrl && tokenData.refreshToken) {
578
+ const refreshed = await DwnRegistrar.refreshRegistrationToken(
579
+ tokenData.refreshUrl, tokenData.refreshToken,
580
+ );
581
+ tokenData = {
582
+ registrationToken : refreshed.registrationToken,
583
+ refreshToken : refreshed.refreshToken,
584
+ expiresAt : refreshed.expiresIn !== undefined
585
+ ? Date.now() + (refreshed.expiresIn * 1000) : undefined,
586
+ tokenUrl : tokenData.tokenUrl,
587
+ refreshUrl : tokenData.refreshUrl,
588
+ };
589
+ updatedTokens[dwnEndpoint] = tokenData;
590
+ } else {
591
+ tokenData = undefined;
592
+ }
593
+ }
594
+
595
+ // Run the auth flow if no valid token exists.
596
+ if (tokenData === undefined) {
597
+ const state = crypto.randomUUID();
598
+ const providerAuth = serverInfo.providerAuth!;
599
+ const separator = providerAuth.authorizeUrl.includes('?') ? '&' : '?';
600
+ const authorizeUrl = `${providerAuth.authorizeUrl}${separator}`
601
+ + `redirect_uri=${encodeURIComponent(dwnEndpoint)}`
602
+ + `&state=${encodeURIComponent(state)}`;
603
+
604
+ const authResult = await registration.onProviderAuthRequired({
605
+ authorizeUrl,
606
+ dwnEndpoint,
607
+ state,
608
+ });
609
+
610
+ if (authResult.state !== state) {
611
+ throw new Error('Provider auth state mismatch — possible CSRF attack.');
612
+ }
613
+
614
+ const tokenResponse = await DwnRegistrar.exchangeAuthCode(
615
+ providerAuth.tokenUrl, authResult.code, dwnEndpoint,
616
+ );
617
+
618
+ tokenData = {
619
+ registrationToken : tokenResponse.registrationToken,
620
+ refreshToken : tokenResponse.refreshToken,
621
+ expiresAt : tokenResponse.expiresIn !== undefined
622
+ ? Date.now() + (tokenResponse.expiresIn * 1000) : undefined,
623
+ tokenUrl : providerAuth.tokenUrl,
624
+ refreshUrl : providerAuth.refreshUrl,
625
+ };
626
+ updatedTokens[dwnEndpoint] = tokenData;
627
+ }
628
+
629
+ // Register each DID using the provider auth token.
630
+ for (const did of didsToRegister) {
631
+ await DwnRegistrar.registerTenantWithToken(
632
+ dwnEndpoint, did, tokenData.registrationToken,
633
+ );
634
+ }
635
+
636
+ } else {
637
+ // --- Default Path (PoW / general registration) ---
638
+ for (const did of didsToRegister) {
639
+ await DwnRegistrar.registerTenant(dwnEndpoint, did);
640
+ }
641
+ }
642
+ }
515
643
 
516
- // register the connected Identity DID
517
- await DwnRegistrar.registerTenant(dwnEndpoint, connectedDid);
644
+ // Notify app of updated tokens for persistence.
645
+ if (registration.onRegistrationTokens) {
646
+ registration.onRegistrationTokens(updatedTokens);
518
647
  }
519
648
 
520
- // If no failures occurred, call the onSuccess callback
521
649
  registration.onSuccess();
522
650
  } catch (error) {
523
- // for any failure, call the onFailure callback with the error
524
651
  registration.onFailure(error);
525
652
  }
526
653
  }
@@ -599,7 +726,7 @@ export class Web5 {
599
726
  const connectedProtocols = new Set<string>();
600
727
  for (const grantMessage of grants) {
601
728
  // use the delegateDid as the connectedDid of the grant as they do not yet support impersonation/delegation
602
- const grant = await PermissionGrant.parse({ connectedDid: delegateDid, agent, message: grantMessage });
729
+ const grant = PermissionGrant.parse({ connectedDid: delegateDid, agent, message: grantMessage });
603
730
  // store the grant as the owner of the DWN, this will allow the delegateDid to use the grant when impersonating the connectedDid
604
731
  const { status } = await grant.store(true);
605
732
  if (status.code !== 202) {