@enbox/api 0.2.3 → 0.3.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.
- package/README.md +235 -35
- package/dist/browser.mjs +13 -13
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/dwn-api.js +24 -10
- package/dist/esm/dwn-api.js.map +1 -1
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/live-query.js +34 -5
- package/dist/esm/live-query.js.map +1 -1
- package/dist/esm/permission-grant.js +3 -6
- package/dist/esm/permission-grant.js.map +1 -1
- package/dist/esm/permission-request.js +4 -7
- package/dist/esm/permission-request.js.map +1 -1
- package/dist/esm/record-data.js +131 -0
- package/dist/esm/record-data.js.map +1 -0
- package/dist/esm/record-types.js +9 -0
- package/dist/esm/record-types.js.map +1 -0
- package/dist/esm/record.js +58 -184
- package/dist/esm/record.js.map +1 -1
- package/dist/esm/repository-types.js +13 -0
- package/dist/esm/repository-types.js.map +1 -0
- package/dist/esm/repository.js +347 -0
- package/dist/esm/repository.js.map +1 -0
- package/dist/esm/typed-live-query.js +129 -0
- package/dist/esm/typed-live-query.js.map +1 -0
- package/dist/esm/typed-record.js +227 -0
- package/dist/esm/typed-record.js.map +1 -0
- package/dist/esm/typed-web5.js +134 -23
- package/dist/esm/typed-web5.js.map +1 -1
- package/dist/esm/web5.js +83 -22
- package/dist/esm/web5.js.map +1 -1
- package/dist/types/dwn-api.d.ts.map +1 -1
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/live-query.d.ts +43 -4
- package/dist/types/live-query.d.ts.map +1 -1
- package/dist/types/permission-grant.d.ts +1 -1
- package/dist/types/permission-grant.d.ts.map +1 -1
- package/dist/types/permission-request.d.ts +1 -1
- package/dist/types/permission-request.d.ts.map +1 -1
- package/dist/types/record-data.d.ts +49 -0
- package/dist/types/record-data.d.ts.map +1 -0
- package/dist/types/record-types.d.ts +145 -0
- package/dist/types/record-types.d.ts.map +1 -0
- package/dist/types/record.d.ts +13 -144
- package/dist/types/record.d.ts.map +1 -1
- package/dist/types/repository-types.d.ts +137 -0
- package/dist/types/repository-types.d.ts.map +1 -0
- package/dist/types/repository.d.ts +59 -0
- package/dist/types/repository.d.ts.map +1 -0
- package/dist/types/typed-live-query.d.ts +111 -0
- package/dist/types/typed-live-query.d.ts.map +1 -0
- package/dist/types/typed-record.d.ts +179 -0
- package/dist/types/typed-record.d.ts.map +1 -0
- package/dist/types/typed-web5.d.ts +55 -24
- package/dist/types/typed-web5.d.ts.map +1 -1
- package/dist/types/web5.d.ts +54 -4
- package/dist/types/web5.d.ts.map +1 -1
- package/package.json +8 -7
- package/src/dwn-api.ts +30 -13
- package/src/index.ts +6 -0
- package/src/live-query.ts +71 -7
- package/src/permission-grant.ts +2 -3
- package/src/permission-request.ts +3 -4
- package/src/record-data.ts +155 -0
- package/src/record-types.ts +188 -0
- package/src/record.ts +86 -389
- package/src/repository-types.ts +249 -0
- package/src/repository.ts +391 -0
- package/src/typed-live-query.ts +200 -0
- package/src/typed-record.ts +309 -0
- package/src/typed-web5.ts +202 -49
- package/src/web5.ts +162 -27
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
|
/**
|
|
@@ -165,8 +197,13 @@ export type Web5ConnectOptions = {
|
|
|
165
197
|
|
|
166
198
|
/**
|
|
167
199
|
* Enable synchronization of DWN records between local and remote DWNs.
|
|
168
|
-
*
|
|
169
|
-
*
|
|
200
|
+
*
|
|
201
|
+
* - **Omitted / `undefined`**: Live sync mode (default). Opens real-time
|
|
202
|
+
* `MessagesSubscribe` WebSocket subscriptions for instant pull and
|
|
203
|
+
* push-on-write, with a background SMT integrity check every 5 minutes.
|
|
204
|
+
* - **Interval string** (e.g. `'2m'`, `'30s'`): Poll mode. Performs a full
|
|
205
|
+
* SMT set-reconciliation sync at the specified interval.
|
|
206
|
+
* - **`'off'`**: Sync is disabled entirely.
|
|
170
207
|
*/
|
|
171
208
|
sync?: string;
|
|
172
209
|
|
|
@@ -190,10 +227,29 @@ export type Web5ConnectOptions = {
|
|
|
190
227
|
* If registration is successful, the `onSuccess` callback will be called.
|
|
191
228
|
*/
|
|
192
229
|
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;
|
|
230
|
+
/** Called when all of the DWN registrations are successful. */
|
|
231
|
+
onSuccess : () => void;
|
|
232
|
+
/** Called when any of the DWN registrations fail. */
|
|
233
|
+
onFailure : (error: any) => void;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Called when a DWN endpoint requires provider auth (`'provider-auth-v0'`).
|
|
237
|
+
* The app is responsible for opening the authorizeUrl in a browser,
|
|
238
|
+
* capturing the redirect back, and returning the auth code.
|
|
239
|
+
* If not provided, provider-auth endpoints fall back to PoW registration.
|
|
240
|
+
*/
|
|
241
|
+
onProviderAuthRequired? : (params: ProviderAuthParams) => Promise<ProviderAuthResult>;
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Pre-existing registration tokens from a previous session, keyed by DWN endpoint URL.
|
|
245
|
+
* If a valid (non-expired) token exists for an endpoint, it is used directly.
|
|
246
|
+
*/
|
|
247
|
+
registrationTokens? : Record<string, RegistrationTokenData>;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Called when new registration tokens are obtained so the app can persist them.
|
|
251
|
+
*/
|
|
252
|
+
onRegistrationTokens? : (tokens: Record<string, RegistrationTokenData>) => void;
|
|
197
253
|
}
|
|
198
254
|
};
|
|
199
255
|
|
|
@@ -449,17 +505,14 @@ export class Web5 {
|
|
|
449
505
|
throw new Error(`Failed to connect to wallet: ${error.message}`);
|
|
450
506
|
}
|
|
451
507
|
} else {
|
|
452
|
-
// No connected identity
|
|
453
|
-
//
|
|
508
|
+
// No connected (WalletConnect) identity and no walletConnectOptions provided.
|
|
509
|
+
// Look for an existing local identity, or create one on first use.
|
|
454
510
|
const identities = await userAgent.identity.list();
|
|
455
511
|
|
|
456
|
-
|
|
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
|
|
512
|
+
if (identities.length === 0) {
|
|
460
513
|
registerSync = true;
|
|
461
514
|
|
|
462
|
-
//
|
|
515
|
+
// First use — generate a new Identity for the end-user.
|
|
463
516
|
identity = await userAgent.identity.create({
|
|
464
517
|
didMethod : 'dht',
|
|
465
518
|
metadata : { name: 'Default' },
|
|
@@ -489,8 +542,9 @@ export class Web5 {
|
|
|
489
542
|
});
|
|
490
543
|
|
|
491
544
|
} else {
|
|
492
|
-
//
|
|
493
|
-
//
|
|
545
|
+
// Reconnecting — use the first local identity. When the agent manages
|
|
546
|
+
// multiple identities (e.g. created via agent.identity.create()), the
|
|
547
|
+
// first one returned by the store is used as the default for connect().
|
|
494
548
|
identity = identities[0];
|
|
495
549
|
}
|
|
496
550
|
}
|
|
@@ -500,27 +554,105 @@ export class Web5 {
|
|
|
500
554
|
// If the stored identity has a connected DID, use the identity DID as the delegated DID, otherwise it is undefined.
|
|
501
555
|
delegateDid = identity.metadata.connectedDid ? identity.did.uri : undefined;
|
|
502
556
|
if (registration !== undefined) {
|
|
503
|
-
|
|
557
|
+
const updatedTokens: Record<string, RegistrationTokenData> = {
|
|
558
|
+
...(registration.registrationTokens ?? {}),
|
|
559
|
+
};
|
|
560
|
+
|
|
504
561
|
try {
|
|
505
562
|
for (const dwnEndpoint of serviceEndpointNodes) {
|
|
506
|
-
// check if endpoint needs registration
|
|
507
563
|
const serverInfo = await userAgent.rpc.getServerInfo(dwnEndpoint);
|
|
564
|
+
|
|
508
565
|
if (serverInfo.registrationRequirements.length === 0) {
|
|
509
|
-
// no registration required
|
|
510
566
|
continue;
|
|
511
567
|
}
|
|
512
568
|
|
|
513
|
-
//
|
|
514
|
-
|
|
569
|
+
// Deduplicate DIDs to register.
|
|
570
|
+
const didsToRegister = [agent.agentDid.uri, connectedDid]
|
|
571
|
+
.filter((did, i, arr): did is string => arr.indexOf(did) === i);
|
|
572
|
+
|
|
573
|
+
const hasProviderAuth = serverInfo.registrationRequirements.includes('provider-auth-v0')
|
|
574
|
+
&& serverInfo.providerAuth !== undefined;
|
|
575
|
+
|
|
576
|
+
if (hasProviderAuth && registration.onProviderAuthRequired) {
|
|
577
|
+
// --- Provider Auth Path ---
|
|
578
|
+
let tokenData = updatedTokens[dwnEndpoint];
|
|
579
|
+
|
|
580
|
+
// Refresh expired tokens.
|
|
581
|
+
if (tokenData?.expiresAt !== undefined && tokenData.expiresAt < Date.now()) {
|
|
582
|
+
if (tokenData.refreshUrl && tokenData.refreshToken) {
|
|
583
|
+
const refreshed = await DwnRegistrar.refreshRegistrationToken(
|
|
584
|
+
tokenData.refreshUrl, tokenData.refreshToken,
|
|
585
|
+
);
|
|
586
|
+
tokenData = {
|
|
587
|
+
registrationToken : refreshed.registrationToken,
|
|
588
|
+
refreshToken : refreshed.refreshToken,
|
|
589
|
+
expiresAt : refreshed.expiresIn !== undefined
|
|
590
|
+
? Date.now() + (refreshed.expiresIn * 1000) : undefined,
|
|
591
|
+
tokenUrl : tokenData.tokenUrl,
|
|
592
|
+
refreshUrl : tokenData.refreshUrl,
|
|
593
|
+
};
|
|
594
|
+
updatedTokens[dwnEndpoint] = tokenData;
|
|
595
|
+
} else {
|
|
596
|
+
tokenData = undefined;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Run the auth flow if no valid token exists.
|
|
601
|
+
if (tokenData === undefined) {
|
|
602
|
+
const state = crypto.randomUUID();
|
|
603
|
+
const providerAuth = serverInfo.providerAuth!;
|
|
604
|
+
const separator = providerAuth.authorizeUrl.includes('?') ? '&' : '?';
|
|
605
|
+
const authorizeUrl = `${providerAuth.authorizeUrl}${separator}`
|
|
606
|
+
+ `redirect_uri=${encodeURIComponent(dwnEndpoint)}`
|
|
607
|
+
+ `&state=${encodeURIComponent(state)}`;
|
|
608
|
+
|
|
609
|
+
const authResult = await registration.onProviderAuthRequired({
|
|
610
|
+
authorizeUrl,
|
|
611
|
+
dwnEndpoint,
|
|
612
|
+
state,
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
if (authResult.state !== state) {
|
|
616
|
+
throw new Error('Provider auth state mismatch — possible CSRF attack.');
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const tokenResponse = await DwnRegistrar.exchangeAuthCode(
|
|
620
|
+
providerAuth.tokenUrl, authResult.code, dwnEndpoint,
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
tokenData = {
|
|
624
|
+
registrationToken : tokenResponse.registrationToken,
|
|
625
|
+
refreshToken : tokenResponse.refreshToken,
|
|
626
|
+
expiresAt : tokenResponse.expiresIn !== undefined
|
|
627
|
+
? Date.now() + (tokenResponse.expiresIn * 1000) : undefined,
|
|
628
|
+
tokenUrl : providerAuth.tokenUrl,
|
|
629
|
+
refreshUrl : providerAuth.refreshUrl,
|
|
630
|
+
};
|
|
631
|
+
updatedTokens[dwnEndpoint] = tokenData;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Register each DID using the provider auth token.
|
|
635
|
+
for (const did of didsToRegister) {
|
|
636
|
+
await DwnRegistrar.registerTenantWithToken(
|
|
637
|
+
dwnEndpoint, did, tokenData.registrationToken,
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
} else {
|
|
642
|
+
// --- Default Path (PoW / general registration) ---
|
|
643
|
+
for (const did of didsToRegister) {
|
|
644
|
+
await DwnRegistrar.registerTenant(dwnEndpoint, did);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
515
648
|
|
|
516
|
-
|
|
517
|
-
|
|
649
|
+
// Notify app of updated tokens for persistence.
|
|
650
|
+
if (registration.onRegistrationTokens) {
|
|
651
|
+
registration.onRegistrationTokens(updatedTokens);
|
|
518
652
|
}
|
|
519
653
|
|
|
520
|
-
// If no failures occurred, call the onSuccess callback
|
|
521
654
|
registration.onSuccess();
|
|
522
655
|
} catch (error) {
|
|
523
|
-
// for any failure, call the onFailure callback with the error
|
|
524
656
|
registration.onFailure(error);
|
|
525
657
|
}
|
|
526
658
|
}
|
|
@@ -546,8 +678,11 @@ export class Web5 {
|
|
|
546
678
|
}
|
|
547
679
|
|
|
548
680
|
// Enable sync using the specified interval or default.
|
|
549
|
-
sync
|
|
550
|
-
|
|
681
|
+
// When sync is unset (undefined), default to live mode.
|
|
682
|
+
// When sync is an interval string (e.g. '2m', '30s'), use poll mode with that interval.
|
|
683
|
+
const syncMode = sync === undefined ? 'live' : 'poll';
|
|
684
|
+
const syncInterval = sync ?? (syncMode === 'live' ? '5m' : '2m');
|
|
685
|
+
userAgent.sync.startSync({ mode: syncMode, interval: syncInterval })
|
|
551
686
|
.catch((error: any) => {
|
|
552
687
|
console.error(`Sync failed: ${error}`);
|
|
553
688
|
});
|
|
@@ -599,7 +734,7 @@ export class Web5 {
|
|
|
599
734
|
const connectedProtocols = new Set<string>();
|
|
600
735
|
for (const grantMessage of grants) {
|
|
601
736
|
// use the delegateDid as the connectedDid of the grant as they do not yet support impersonation/delegation
|
|
602
|
-
const grant =
|
|
737
|
+
const grant = PermissionGrant.parse({ connectedDid: delegateDid, agent, message: grantMessage });
|
|
603
738
|
// store the grant as the owner of the DWN, this will allow the delegateDid to use the grant when impersonating the connectedDid
|
|
604
739
|
const { status } = await grant.store(true);
|
|
605
740
|
if (status.code !== 202) {
|