@basictech/react 0.11.0-beta.1 → 0.11.0-beta.3
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 +99 -8
- package/dist/index.d.mts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +38 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +33 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -26,6 +26,80 @@ npm install @basictech/react @basictech/schema
|
|
|
26
26
|
|
|
27
27
|
React is a peer dependency and must already be installed by your application.
|
|
28
28
|
|
|
29
|
+
## Beta auth update guide
|
|
30
|
+
|
|
31
|
+
After this auth update is published to the `beta` dist-tag, upgrade the packages you use together:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @basictech/react@beta @basictech/schema@beta
|
|
35
|
+
# If you import core directly, update that dependency too:
|
|
36
|
+
npm install @basictech/core@beta
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Keep the public packages on matching beta versions. No browser storage reset or persisted-profile
|
|
40
|
+
migration is required. Check your lockfile for the resolved version; this guide does not imply
|
|
41
|
+
that an older installed beta contains the update.
|
|
42
|
+
|
|
43
|
+
### Update auth checks and editing controls
|
|
44
|
+
|
|
45
|
+
- Replace `useAuth().status === 'reauth_required'` with `status === 'expired'`.
|
|
46
|
+
Core client snapshots likewise use `authStatus: 'expired'`; only low-level `AuthSession`
|
|
47
|
+
retains the legacy compatibility state.
|
|
48
|
+
- Use `auth.canWrite` to disable mutation controls and `auth.readOnlyReason` for explanation.
|
|
49
|
+
It is account eligibility, not a guarantee of connectivity or permission to a particular source.
|
|
50
|
+
Catch imperative mutation errors too: auth can expire after the UI renders. Newly attempted
|
|
51
|
+
expired mutations reject with `BasicError.code === 'AUTH_EXPIRED'`.
|
|
52
|
+
- Keep cached data visible when expired. Do not treat `!auth.isSignedIn` as instructions to
|
|
53
|
+
sign out, remove the account, or clear local storage. Expiry retains identity, cached records,
|
|
54
|
+
and already queued edits, but blocks new writes and pauses sync sends.
|
|
55
|
+
- Offer `auth.signIn()` to reauthorize the same DID/issuer. For a different identity, add or switch
|
|
56
|
+
accounts first. Pending data must not be replayed into another account.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// Uses the schema-bound basic instance from the quick start below.
|
|
60
|
+
function AuthNotice() {
|
|
61
|
+
const auth = basic.useAuth()
|
|
62
|
+
if (auth.status !== 'expired') return null
|
|
63
|
+
return (
|
|
64
|
+
<aside role="status">
|
|
65
|
+
Session expired. Cached data is read-only; your pending edits are retained.
|
|
66
|
+
<button onClick={() => void auth.signIn()}>Sign in again</button>
|
|
67
|
+
</aside>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
// In your editor: <button disabled={!auth.canWrite} ...>Save</button>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Render per-account status
|
|
74
|
+
|
|
75
|
+
`useAccounts().accounts` now returns `BasicAccount[]`, including anonymous and expired accounts.
|
|
76
|
+
Each account adds `auth: { status, reason, checkedAt }` to the existing profile fields:
|
|
77
|
+
|
|
78
|
+
| Account status | Meaning |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `anon` | Local anonymous workspace; editable without credentials |
|
|
81
|
+
| `checking` | Authorization has not been verified for this remembered account |
|
|
82
|
+
| `authenticated` | Current runtime authentication succeeded |
|
|
83
|
+
| `recovering` | Transient failure; ordinary offline queues remain writable |
|
|
84
|
+
| `expired` | Interactive reauthorization required; retain data read-only |
|
|
85
|
+
| `signed_out` | No usable active authorization |
|
|
86
|
+
|
|
87
|
+
`checkedAt` is the local evaluation time in milliseconds since epoch, or null—not a guarantee
|
|
88
|
+
from the server. Inactive accounts are not all refreshed: retained identity without credentials
|
|
89
|
+
is expired; otherwise an unverified remembered account is checking. The active anonymous account
|
|
90
|
+
has `auth.status: 'anon'`, while top-level `useAuth()` reports `status: 'signed_out'` and
|
|
91
|
+
`isAnonymous: true`. REST exposes one remembered account, not a multi-account registry.
|
|
92
|
+
|
|
93
|
+
Ordinary network loss is not expiry, and anonymous editing still works. `goOnline()` cannot
|
|
94
|
+
bypass expired read-only enforcement. Reauthorization resumes retained queued work; already
|
|
95
|
+
accepted server requests cannot be undone by client cancellation. REST has no offline cache.
|
|
96
|
+
|
|
97
|
+
Verified session revocation and automatic account/data cleanup are deferred to a later update.
|
|
98
|
+
Generic `invalid_grant` is not evidence that it is safe to erase local data. Browser storage
|
|
99
|
+
remains unchanged: localStorage holds profiles/refresh credentials, sessionStorage holds tab
|
|
100
|
+
selection/PKCE, access tokens live in memory, and Dexie wraps IndexedDB for replica data.
|
|
101
|
+
The default browser adapter writes no auth cookies. Read-only does not encrypt cached data.
|
|
102
|
+
|
|
29
103
|
## Quick start
|
|
30
104
|
|
|
31
105
|
Define the schema and create `basic` once at module scope. The returned client, Provider, and hooks
|
|
@@ -151,8 +225,8 @@ client.
|
|
|
151
225
|
| --- | --- | --- | --- |
|
|
152
226
|
| `clientId` | `string` | Required | The application's client DID. Its Basic app metadata must register the redirect URI. |
|
|
153
227
|
| `schema` | `S extends BasicSchema` | Required by `createBasic` | Pass the `defineSchema()` result used by this application. It is optional only in the lower-level `createBasicClient`. |
|
|
154
|
-
| `identityOrigin` | `string` | `'https://basic.id'` | Use another
|
|
155
|
-
| `defaultPds` | `string` | `'https://pds.basic.id'` | Change the PDS
|
|
228
|
+
| `identityOrigin` | `string` | `'https://basic.id'` | Use another Basic ID web origin for the shares management URL. |
|
|
229
|
+
| `defaultPds` | `string` | `'https://pds.basic.id'` | Change the PDS used for handle resolution and no-argument `signIn()`. `signIn(handleOrDid)` discovers that identity's PDS after resolving handles here. |
|
|
156
230
|
| `redirectUri` | `string` | `location.origin + location.pathname` | Set an explicit OAuth callback route. Outside a browser location, it is required. The URI must appear in the client metadata for `clientId`. |
|
|
157
231
|
| `mode` | `'sync' \| 'rest'` | `'sync'` | Choose direct REST when offline replicas, anonymous mode, multi-account state, and `watch()` are not needed. |
|
|
158
232
|
| `anonymous` | `boolean` | `true` in sync mode | Set `false` to require sign-in. REST mode always disables anonymous workspaces. |
|
|
@@ -170,6 +244,7 @@ client.
|
|
|
170
244
|
| `currentUrl` | `() => string` | `location.href`, or `''` without `location` | Tell auth bootstrap where to read OAuth `code` and `state`. |
|
|
171
245
|
| `replaceUrl` | `(url: string) => void` | `history.replaceState({}, '', url)` | Integrate removal of consumed OAuth query parameters with custom navigation. |
|
|
172
246
|
| `createMessageChannel` | `(name: string) => AuthMessageChannel` | `BroadcastChannel` when available | Replace or disable cross-context auth notifications. The default token adapter also transfers access tokens transiently between tabs. |
|
|
247
|
+
| `reconcileTrigger` | `(listener: () => void) => () => void` | `focus`, `online`, and `visibilitychange` listeners | Subscribe the client to host lifecycle signals so a suspended or backgrounded application reconciles credentials with its owner and mounted Sync sessions on resume. Return a cleanup function; the client calls it on `stop()`. |
|
|
173
248
|
| `ownerCredential` | `OwnerCredentialAdapter` | None | First-party Basic ID/dev tooling can inject owner credentials for `client.drive()`, account-wide `client.storageInfo()`, and `client.shares.leave()`. Normal app credentials should use existing repos and `shares.manageUrl()`. |
|
|
174
249
|
| `uploadTransport` | `UploadTransportAdapter` | Browser XHR, then the core fetch fallback | Supply a multipart transport. XHR is used by default because fetch has no upload-progress events. |
|
|
175
250
|
| `warn` | `(message: string) => void` | `console.warn` for schema checks | Route schema-drift warnings into application logging. |
|
|
@@ -192,6 +267,10 @@ The defaults are selected from browser capabilities at client creation:
|
|
|
192
267
|
- **Auth coordination:** token rotation and sign-out notifications use `BroadcastChannel` when
|
|
193
268
|
available. Access tokens can cross that channel transiently but are not written by the default
|
|
194
269
|
token store.
|
|
270
|
+
- **Lifecycle recovery:** when a tab regains focus, comes back online, or becomes visible again,
|
|
271
|
+
the client reconciles fresh credentials with the owner and mounted Sync sessions. A browser that
|
|
272
|
+
suspended the page recovers without a reload, and transient refresh failures retry rather than
|
|
273
|
+
signing the profile out. Override `reconcileTrigger` to supply that signal in another host.
|
|
195
274
|
- **Uploads:** browser multipart uploads use XHR for real `onProgress(loaded, total)` events. The
|
|
196
275
|
fetch fallback reports only start and completion.
|
|
197
276
|
|
|
@@ -271,7 +350,9 @@ unknown table names and infer fields in reads, writes, and queries.
|
|
|
271
350
|
| `isReady` | `boolean` | Auth bootstrap has left `bootstrapping` |
|
|
272
351
|
| `isSignedIn` | `boolean` | Status is `authenticated` or `recovering` |
|
|
273
352
|
| `isAnonymous` | `boolean` | The active sync profile is anonymous and not signed in |
|
|
274
|
-
| `status` | `'bootstrapping' \| 'signed_out' \| 'authenticated' \| 'recovering' \| '
|
|
353
|
+
| `status` | `'bootstrapping' \| 'signed_out' \| 'authenticated' \| 'recovering' \| 'expired'` | Current public auth lifecycle |
|
|
354
|
+
| `canWrite` | `boolean` | Active-account write eligibility; source permissions still apply |
|
|
355
|
+
| `readOnlyReason` | `string \| null` | For example `AUTH_EXPIRED` when reauthorization is needed |
|
|
275
356
|
| `error` | `BasicError \| null` | Stable auth bootstrap/refresh error code when available |
|
|
276
357
|
| `user` | `AuthUser \| null` | OIDC user info (`sub`, `pds_url`, and optional `email`, `name`, `picture`, `handle`) |
|
|
277
358
|
| `did` | `string \| null` | Signed-in account DID |
|
|
@@ -288,6 +369,9 @@ export function AuthPanel() {
|
|
|
288
369
|
const auth = basic.useAuth()
|
|
289
370
|
|
|
290
371
|
if (!auth.isReady) return <p>Starting Basic…</p>
|
|
372
|
+
if (auth.status === 'expired') {
|
|
373
|
+
return <p>Session expired. Cached data is read-only. <button onClick={() => void auth.signIn()}>Sign in again</button></p>
|
|
374
|
+
}
|
|
291
375
|
if (auth.isSignedIn) {
|
|
292
376
|
return (
|
|
293
377
|
<p>
|
|
@@ -317,8 +401,8 @@ replicas and returns to another local profile or a fresh anonymous one when enab
|
|
|
317
401
|
|
|
318
402
|
`useAccounts()` returns `{ accounts, active, switchAccount, addAccount, removeAccount }`:
|
|
319
403
|
|
|
320
|
-
- `accounts:
|
|
321
|
-
- `active:
|
|
404
|
+
- `accounts: BasicAccount[]` lists local profiles with per-account auth summaries.
|
|
405
|
+
- `active: BasicAccount | null` is this tab's selected profile and auth summary.
|
|
322
406
|
- `switchAccount(id): Promise<void>` changes the active profile in this tab.
|
|
323
407
|
- `addAccount(): Promise<BasicProfile>` creates and activates a new anonymous profile from which to
|
|
324
408
|
start another sign-in.
|
|
@@ -745,6 +829,7 @@ Shares v2 is capability-gated and requires sign-in. `useOutgoingShares()` return
|
|
|
745
829
|
get(id: string): Promise<OutgoingShareInfo>
|
|
746
830
|
cancel(id: string): Promise<OutgoingShareInfo>
|
|
747
831
|
revoke(id: string): Promise<OutgoingShareInfo>
|
|
832
|
+
getContactHandle(did: string): Promise<string | null>
|
|
748
833
|
manageUrl(): string
|
|
749
834
|
}
|
|
750
835
|
```
|
|
@@ -754,7 +839,7 @@ Shares v2 is capability-gated and requires sign-in. `useOutgoingShares()` return
|
|
|
754
839
|
| Field | Type | Meaning |
|
|
755
840
|
| --- | --- | --- |
|
|
756
841
|
| `repo` | `'default' \| { repoId: string }` | Origin repo |
|
|
757
|
-
| recipient | Exactly one of `recipientHandle: string` or `recipientDid: string` | A handle is trimmed, lowercased, and resolved through `
|
|
842
|
+
| recipient | Exactly one of `recipientHandle: string` or `recipientDid: string` | A handle is trimmed, lowercased, and resolved through `defaultPds`; a canonical DID skips handle resolution. |
|
|
758
843
|
| `role` | `'viewer' \| 'editor'` | Collaborator role |
|
|
759
844
|
| `scope` | `Array<{ table: string } \| { table: string; recordIds: string[] }>` | Whole-table clauses or static record sets |
|
|
760
845
|
| `acceptBy` | `string` | Optional acceptance deadline passed to Shares v2 |
|
|
@@ -807,6 +892,10 @@ export function OutgoingShares() {
|
|
|
807
892
|
`deliveryState`, and created, accepted, and ended timestamps/reasons. Revocation prevents future
|
|
808
893
|
access but cannot retract data a recipient already downloaded.
|
|
809
894
|
|
|
895
|
+
After a handle-based share is created successfully, the SDK stores a profile-local contact entry
|
|
896
|
+
containing `{ did, handle }` in browser local storage. Use `getContactHandle(recipientDid)` to show
|
|
897
|
+
the remembered handle; it returns `null` for DIDs that were shared directly or are not cached.
|
|
898
|
+
|
|
810
899
|
`manageUrl()` returns the Basic ID `/shares` handoff. Invite acceptance and ordinary mount
|
|
811
900
|
management belong there. `client.shares.leave()` is intentionally owner-only and throws
|
|
812
901
|
`OWNER_CREDENTIAL_REQUIRED` without an explicit owner adapter.
|
|
@@ -1037,6 +1126,8 @@ Common application-facing codes include:
|
|
|
1037
1126
|
| Code | Typical cause |
|
|
1038
1127
|
| --- | --- |
|
|
1039
1128
|
| `AUTHORIZATION_REQUIRED` | A signed-in token/session is required |
|
|
1129
|
+
| `AUTH_EXPIRED` | Session needs reauthorization; cached data and pending edits are retained |
|
|
1130
|
+
| `ACCOUNT_CHANGED` | The account changed while an operation was in flight; do not blindly retry into the new account |
|
|
1040
1131
|
| `UNKNOWN_SOURCE` | The repo is not in the catalog or the mount has not been listed/opened |
|
|
1041
1132
|
| `RECORD_NOT_FOUND`, `RECORD_EXISTS`, `RECORD_NOT_DELETED` | Record lifecycle precondition failed |
|
|
1042
1133
|
| `INVALID_PATCH`, `INVALID_RECORD_ID`, `INVALID_IDEMPOTENCY_KEY` | Invalid local mutation input |
|
|
@@ -1093,8 +1184,8 @@ for the lower-level model.
|
|
|
1093
1184
|
Client Components should import `@basictech/react` directly.
|
|
1094
1185
|
- Migrating from 0.10 requires deliberate call-site changes; there is no compatibility layer. Read
|
|
1095
1186
|
[Migrating to Basic SDK 0.11](https://github.com/basicdb/basic-server/blob/dev/docs/MIGRATING_TO_0.11.md).
|
|
1096
|
-
-
|
|
1097
|
-
|
|
1187
|
+
- 0.11 is a beta API, published to npm under the `beta` dist-tag. Check the installed version
|
|
1188
|
+
rather than assuming it matches the repository workspace.
|
|
1098
1189
|
|
|
1099
1190
|
## License
|
|
1100
1191
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ReactElement } from 'react';
|
|
3
|
-
import { BasicSchema, BasicConfig, BasicClient, BasicProfile, AuthStatus, BasicError, AuthUser, BasicSyncStatus, BasicRejection, BasicConflict, SourceRef, SchemaStatus, BasicDb, Repo, FileRecord, MountFileInfo, StorageInfo, FileListQuery, MountInfo, MountHandle, OutgoingShareInfo, CreateOutgoingShareInput, MountsQuery, JsonObject, BasicRecord, Query, MountViewerFiles, OwnerFiles, TableNames, Collection, InferValue, KeyValueStorage, TokenStore, AuthMessageChannel, ReplicaStoreFactory, StoragePartition, ReplicaStore, UploadTransportAdapter } from '@basictech/core';
|
|
3
|
+
import { BasicSchema, BasicConfig, BasicClient, BasicAccount, BasicProfile, AuthStatus, BasicError, AuthUser, BasicSyncStatus, BasicRejection, BasicConflict, SourceRef, SchemaStatus, BasicDb, Repo, FileRecord, MountFileInfo, StorageInfo, FileListQuery, MountInfo, MountHandle, OutgoingShareInfo, CreateOutgoingShareInput, MountsQuery, JsonObject, BasicRecord, Query, MountViewerFiles, OwnerFiles, TableNames, Collection, InferValue, KeyValueStorage, TokenStore, AuthMessageChannel, ReplicaStoreFactory, StoragePartition, ReplicaStore, UploadTransportAdapter } from '@basictech/core';
|
|
4
4
|
|
|
5
5
|
type BrowserBasicConfig<S extends BasicSchema = BasicSchema> = BasicConfig<S>;
|
|
6
6
|
/** Create a core client with the complete browser adapter set installed. */
|
|
7
7
|
declare function createBasicClient<S extends BasicSchema>(config: BrowserBasicConfig<S>): BasicClient<S>;
|
|
8
8
|
|
|
9
9
|
interface UseAccountsResult {
|
|
10
|
-
accounts:
|
|
11
|
-
active:
|
|
10
|
+
accounts: BasicAccount[];
|
|
11
|
+
active: BasicAccount | null;
|
|
12
12
|
switchAccount(id: string): Promise<void>;
|
|
13
13
|
addAccount(): Promise<BasicProfile>;
|
|
14
14
|
removeAccount(id: string): Promise<void>;
|
|
@@ -19,6 +19,8 @@ interface UseAuthResult {
|
|
|
19
19
|
isReady: boolean;
|
|
20
20
|
isSignedIn: boolean;
|
|
21
21
|
isAnonymous: boolean;
|
|
22
|
+
canWrite: boolean;
|
|
23
|
+
readOnlyReason: string | null;
|
|
22
24
|
status: AuthStatus;
|
|
23
25
|
error: BasicError | null;
|
|
24
26
|
user: AuthUser | null;
|
|
@@ -87,6 +89,7 @@ interface UseOutgoingSharesResult {
|
|
|
87
89
|
get(id: string): Promise<OutgoingShareInfo>;
|
|
88
90
|
cancel(id: string): Promise<OutgoingShareInfo>;
|
|
89
91
|
revoke(id: string): Promise<OutgoingShareInfo>;
|
|
92
|
+
getContactHandle(did: string): Promise<string | null>;
|
|
90
93
|
manageUrl(): string;
|
|
91
94
|
}
|
|
92
95
|
declare function useOutgoingShares(): UseOutgoingSharesResult;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ReactElement } from 'react';
|
|
3
|
-
import { BasicSchema, BasicConfig, BasicClient, BasicProfile, AuthStatus, BasicError, AuthUser, BasicSyncStatus, BasicRejection, BasicConflict, SourceRef, SchemaStatus, BasicDb, Repo, FileRecord, MountFileInfo, StorageInfo, FileListQuery, MountInfo, MountHandle, OutgoingShareInfo, CreateOutgoingShareInput, MountsQuery, JsonObject, BasicRecord, Query, MountViewerFiles, OwnerFiles, TableNames, Collection, InferValue, KeyValueStorage, TokenStore, AuthMessageChannel, ReplicaStoreFactory, StoragePartition, ReplicaStore, UploadTransportAdapter } from '@basictech/core';
|
|
3
|
+
import { BasicSchema, BasicConfig, BasicClient, BasicAccount, BasicProfile, AuthStatus, BasicError, AuthUser, BasicSyncStatus, BasicRejection, BasicConflict, SourceRef, SchemaStatus, BasicDb, Repo, FileRecord, MountFileInfo, StorageInfo, FileListQuery, MountInfo, MountHandle, OutgoingShareInfo, CreateOutgoingShareInput, MountsQuery, JsonObject, BasicRecord, Query, MountViewerFiles, OwnerFiles, TableNames, Collection, InferValue, KeyValueStorage, TokenStore, AuthMessageChannel, ReplicaStoreFactory, StoragePartition, ReplicaStore, UploadTransportAdapter } from '@basictech/core';
|
|
4
4
|
|
|
5
5
|
type BrowserBasicConfig<S extends BasicSchema = BasicSchema> = BasicConfig<S>;
|
|
6
6
|
/** Create a core client with the complete browser adapter set installed. */
|
|
7
7
|
declare function createBasicClient<S extends BasicSchema>(config: BrowserBasicConfig<S>): BasicClient<S>;
|
|
8
8
|
|
|
9
9
|
interface UseAccountsResult {
|
|
10
|
-
accounts:
|
|
11
|
-
active:
|
|
10
|
+
accounts: BasicAccount[];
|
|
11
|
+
active: BasicAccount | null;
|
|
12
12
|
switchAccount(id: string): Promise<void>;
|
|
13
13
|
addAccount(): Promise<BasicProfile>;
|
|
14
14
|
removeAccount(id: string): Promise<void>;
|
|
@@ -19,6 +19,8 @@ interface UseAuthResult {
|
|
|
19
19
|
isReady: boolean;
|
|
20
20
|
isSignedIn: boolean;
|
|
21
21
|
isAnonymous: boolean;
|
|
22
|
+
canWrite: boolean;
|
|
23
|
+
readOnlyReason: string | null;
|
|
22
24
|
status: AuthStatus;
|
|
23
25
|
error: BasicError | null;
|
|
24
26
|
user: AuthUser | null;
|
|
@@ -87,6 +89,7 @@ interface UseOutgoingSharesResult {
|
|
|
87
89
|
get(id: string): Promise<OutgoingShareInfo>;
|
|
88
90
|
cancel(id: string): Promise<OutgoingShareInfo>;
|
|
89
91
|
revoke(id: string): Promise<OutgoingShareInfo>;
|
|
92
|
+
getContactHandle(did: string): Promise<string | null>;
|
|
90
93
|
manageUrl(): string;
|
|
91
94
|
}
|
|
92
95
|
declare function useOutgoingShares(): UseOutgoingSharesResult;
|
package/dist/index.js
CHANGED
|
@@ -60,7 +60,7 @@ __export(index_exports, {
|
|
|
60
60
|
module.exports = __toCommonJS(index_exports);
|
|
61
61
|
|
|
62
62
|
// src/create-client.ts
|
|
63
|
-
var
|
|
63
|
+
var import_core4 = require("@basictech/core");
|
|
64
64
|
|
|
65
65
|
// src/adapters/broadcast-channel.ts
|
|
66
66
|
function createBrowserMessageChannel(name) {
|
|
@@ -444,7 +444,6 @@ function browserStorage(name) {
|
|
|
444
444
|
}
|
|
445
445
|
|
|
446
446
|
// src/adapters/token-store.ts
|
|
447
|
-
var import_core3 = require("@basictech/core");
|
|
448
447
|
var ACCESS_TOKEN_ADOPTION_TIMEOUT_MS = 100;
|
|
449
448
|
function parseTokens(value) {
|
|
450
449
|
if (!value) return null;
|
|
@@ -521,7 +520,7 @@ var BrowserTokenStore = class {
|
|
|
521
520
|
else this.accessWaiters.delete(key);
|
|
522
521
|
}
|
|
523
522
|
waitForAccessToken(key, refreshToken) {
|
|
524
|
-
return new Promise((resolve
|
|
523
|
+
return new Promise((resolve) => {
|
|
525
524
|
let timer;
|
|
526
525
|
const waiter = {
|
|
527
526
|
refreshToken,
|
|
@@ -537,17 +536,14 @@ var BrowserTokenStore = class {
|
|
|
537
536
|
const remaining = (this.accessWaiters.get(key) ?? []).filter((candidate) => candidate !== waiter);
|
|
538
537
|
if (remaining.length) this.accessWaiters.set(key, remaining);
|
|
539
538
|
else this.accessWaiters.delete(key);
|
|
540
|
-
|
|
541
|
-
"ACCESS_TOKEN_ADOPTION_TIMEOUT",
|
|
542
|
-
"timed out waiting for the access token matching persisted refresh material"
|
|
543
|
-
));
|
|
539
|
+
resolve(null);
|
|
544
540
|
}, ACCESS_TOKEN_ADOPTION_TIMEOUT_MS);
|
|
545
541
|
});
|
|
546
542
|
}
|
|
547
543
|
};
|
|
548
544
|
|
|
549
545
|
// src/adapters/upload.ts
|
|
550
|
-
var
|
|
546
|
+
var import_core3 = require("@basictech/core");
|
|
551
547
|
function responseHeaders(xhr) {
|
|
552
548
|
const headers = new Headers();
|
|
553
549
|
for (const line of xhr.getAllResponseHeaders().trim().split(/[\r\n]+/)) {
|
|
@@ -560,7 +556,7 @@ function responseHeaders(xhr) {
|
|
|
560
556
|
var browserUploadTransport = {
|
|
561
557
|
upload(request) {
|
|
562
558
|
if (typeof XMLHttpRequest === "undefined") {
|
|
563
|
-
return Promise.reject(new
|
|
559
|
+
return Promise.reject(new import_core3.BasicError("XHR_UNAVAILABLE"));
|
|
564
560
|
}
|
|
565
561
|
return new Promise((resolve, reject) => {
|
|
566
562
|
const xhr = new XMLHttpRequest();
|
|
@@ -592,9 +588,9 @@ var browserUploadTransport = {
|
|
|
592
588
|
statusText: xhr.statusText,
|
|
593
589
|
headers: responseHeaders(xhr)
|
|
594
590
|
})));
|
|
595
|
-
xhr.onerror = () => finish(() => reject(new
|
|
591
|
+
xhr.onerror = () => finish(() => reject(new import_core3.BasicError("NETWORK_ERROR", "the upload could not reach the server")));
|
|
596
592
|
xhr.onabort = () => finish(() => reject(request.signal?.reason ?? new DOMException("Aborted", "AbortError")));
|
|
597
|
-
xhr.ontimeout = () => finish(() => reject(new
|
|
593
|
+
xhr.ontimeout = () => finish(() => reject(new import_core3.BasicError("NETWORK_ERROR", "the upload timed out")));
|
|
598
594
|
const form = new FormData();
|
|
599
595
|
form.append("path", request.path);
|
|
600
596
|
if (request.name !== void 0) form.append("name", request.name);
|
|
@@ -606,21 +602,37 @@ var browserUploadTransport = {
|
|
|
606
602
|
};
|
|
607
603
|
|
|
608
604
|
// src/create-client.ts
|
|
605
|
+
function browserReconcileTrigger(listener) {
|
|
606
|
+
if (typeof window === "undefined" || typeof document === "undefined") return () => {
|
|
607
|
+
};
|
|
608
|
+
const onVisible = () => {
|
|
609
|
+
if (document.visibilityState === "visible") listener();
|
|
610
|
+
};
|
|
611
|
+
window.addEventListener("focus", listener);
|
|
612
|
+
window.addEventListener("online", listener);
|
|
613
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
614
|
+
return () => {
|
|
615
|
+
window.removeEventListener("focus", listener);
|
|
616
|
+
window.removeEventListener("online", listener);
|
|
617
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
618
|
+
};
|
|
619
|
+
}
|
|
609
620
|
function createBasicClient(config) {
|
|
610
|
-
const local = browserStorage("localStorage") ?? new
|
|
611
|
-
const session = browserStorage("sessionStorage") ?? new
|
|
621
|
+
const local = browserStorage("localStorage") ?? new import_core4.MemoryKeyValueStorage();
|
|
622
|
+
const session = browserStorage("sessionStorage") ?? new import_core4.MemoryKeyValueStorage();
|
|
612
623
|
const defaultTokenStore = config.tokenStore ? null : new BrowserTokenStore(local);
|
|
613
624
|
const canBroadcast = typeof BroadcastChannel !== "undefined";
|
|
614
625
|
const canPersistReplica = typeof indexedDB !== "undefined";
|
|
615
|
-
return (0,
|
|
626
|
+
return (0, import_core4.createBasicClient)({
|
|
616
627
|
...config,
|
|
617
628
|
kv: config.kv ?? local,
|
|
618
629
|
tokenStore: config.tokenStore ?? defaultTokenStore,
|
|
619
630
|
sessionStorage: config.sessionStorage ?? session,
|
|
620
|
-
replicaStore: config.replicaStore ?? (canPersistReplica ? new PersistenceStore(config.clientId) : new
|
|
631
|
+
replicaStore: config.replicaStore ?? (canPersistReplica ? new PersistenceStore(config.clientId) : new import_core4.MemoryReplicaStoreFactory()),
|
|
621
632
|
navigate: config.navigate ?? browserNavigate,
|
|
622
633
|
currentUrl: config.currentUrl ?? browserCurrentUrl,
|
|
623
634
|
replaceUrl: config.replaceUrl ?? browserReplaceUrl,
|
|
635
|
+
reconcileTrigger: config.reconcileTrigger ?? browserReconcileTrigger,
|
|
624
636
|
createMessageChannel: config.createMessageChannel ?? (canBroadcast && defaultTokenStore ? createBrowserAuthChannelFactory(config.clientId, defaultTokenStore) : void 0),
|
|
625
637
|
uploadTransport: config.uploadTransport ?? (typeof XMLHttpRequest === "undefined" ? void 0 : browserUploadTransport)
|
|
626
638
|
});
|
|
@@ -706,7 +718,7 @@ var import_react4 = require("react");
|
|
|
706
718
|
|
|
707
719
|
// src/hooks/client.ts
|
|
708
720
|
var import_react3 = require("react");
|
|
709
|
-
var
|
|
721
|
+
var import_core5 = require("@basictech/core");
|
|
710
722
|
function useRequiredClient() {
|
|
711
723
|
const client = (0, import_react3.useContext)(BasicClientContext);
|
|
712
724
|
if (!client) throw new Error("Basic hooks must be used within a <BasicProvider>");
|
|
@@ -716,8 +728,8 @@ function useClientSnapshot(client) {
|
|
|
716
728
|
return (0, import_react3.useSyncExternalStore)(client.subscribe, client.getSnapshot, client.getSnapshot);
|
|
717
729
|
}
|
|
718
730
|
function toBasicError(error, fallback = "UNKNOWN_ERROR") {
|
|
719
|
-
if (error instanceof
|
|
720
|
-
return new
|
|
731
|
+
if (error instanceof import_core5.BasicError) return error;
|
|
732
|
+
return new import_core5.BasicError(fallback, error instanceof Error ? error.message : String(error));
|
|
721
733
|
}
|
|
722
734
|
function useAsyncResult(load, initialData, enabled, settled, dependencies) {
|
|
723
735
|
const loadRef = (0, import_react3.useRef)(load);
|
|
@@ -766,12 +778,12 @@ function useAccountsFor(client) {
|
|
|
766
778
|
const addAccount = (0, import_react4.useCallback)(() => client.addAccount(), [client]);
|
|
767
779
|
const removeAccount = (0, import_react4.useCallback)((id) => client.removeAccount(id), [client]);
|
|
768
780
|
return (0, import_react4.useMemo)(() => ({
|
|
769
|
-
accounts: snapshot.
|
|
781
|
+
accounts: snapshot.accounts,
|
|
770
782
|
active: snapshot.activeProfile,
|
|
771
783
|
switchAccount,
|
|
772
784
|
addAccount,
|
|
773
785
|
removeAccount
|
|
774
|
-
}), [snapshot.
|
|
786
|
+
}), [snapshot.accounts, snapshot.activeProfile, switchAccount, addAccount, removeAccount]);
|
|
775
787
|
}
|
|
776
788
|
function useAccounts() {
|
|
777
789
|
return useAccountsFor(useRequiredClient());
|
|
@@ -779,7 +791,7 @@ function useAccounts() {
|
|
|
779
791
|
|
|
780
792
|
// src/hooks/auth.ts
|
|
781
793
|
var import_react5 = require("react");
|
|
782
|
-
var
|
|
794
|
+
var import_core6 = require("@basictech/core");
|
|
783
795
|
function useAuthFor(client) {
|
|
784
796
|
const snapshot = useClientSnapshot(client);
|
|
785
797
|
const signIn = (0, import_react5.useCallback)((input) => client.signIn(input), [client]);
|
|
@@ -789,8 +801,10 @@ function useAuthFor(client) {
|
|
|
789
801
|
isReady: snapshot.isReady,
|
|
790
802
|
isSignedIn: snapshot.isSignedIn,
|
|
791
803
|
isAnonymous: snapshot.isAnonymous,
|
|
804
|
+
canWrite: snapshot.canWrite,
|
|
805
|
+
readOnlyReason: snapshot.readOnlyReason,
|
|
792
806
|
status: snapshot.authStatus,
|
|
793
|
-
error: snapshot.authErrorCode ? new
|
|
807
|
+
error: snapshot.authErrorCode ? new import_core6.BasicError(snapshot.authErrorCode) : null,
|
|
794
808
|
user: snapshot.user,
|
|
795
809
|
did: snapshot.did,
|
|
796
810
|
handle: snapshot.handle,
|
|
@@ -979,6 +993,7 @@ function useOutgoingSharesFor(client) {
|
|
|
979
993
|
const get = (0, import_react9.useCallback)((id) => client.shares.getOutgoing(id), [client]);
|
|
980
994
|
const cancel = (0, import_react9.useCallback)((id) => client.shares.cancel(id), [client]);
|
|
981
995
|
const revoke = (0, import_react9.useCallback)((id) => client.shares.revoke(id), [client]);
|
|
996
|
+
const getContactHandle = (0, import_react9.useCallback)((did) => client.shares.getContactHandle(did), [client]);
|
|
982
997
|
const manageUrl = (0, import_react9.useCallback)(() => client.shares.manageUrl(), [client]);
|
|
983
998
|
const { data: outgoingShares, ...state } = result;
|
|
984
999
|
return {
|
|
@@ -989,6 +1004,7 @@ function useOutgoingSharesFor(client) {
|
|
|
989
1004
|
get,
|
|
990
1005
|
cancel,
|
|
991
1006
|
revoke,
|
|
1007
|
+
getContactHandle,
|
|
992
1008
|
manageUrl
|
|
993
1009
|
};
|
|
994
1010
|
}
|