@basictech/react 0.11.0-beta.1 → 0.11.0-beta.2
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 +15 -5
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +34 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +29 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -151,8 +151,8 @@ client.
|
|
|
151
151
|
| --- | --- | --- | --- |
|
|
152
152
|
| `clientId` | `string` | Required | The application's client DID. Its Basic app metadata must register the redirect URI. |
|
|
153
153
|
| `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
|
|
154
|
+
| `identityOrigin` | `string` | `'https://basic.id'` | Use another Basic ID web origin for the shares management URL. |
|
|
155
|
+
| `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
156
|
| `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
157
|
| `mode` | `'sync' \| 'rest'` | `'sync'` | Choose direct REST when offline replicas, anonymous mode, multi-account state, and `watch()` are not needed. |
|
|
158
158
|
| `anonymous` | `boolean` | `true` in sync mode | Set `false` to require sign-in. REST mode always disables anonymous workspaces. |
|
|
@@ -170,6 +170,7 @@ client.
|
|
|
170
170
|
| `currentUrl` | `() => string` | `location.href`, or `''` without `location` | Tell auth bootstrap where to read OAuth `code` and `state`. |
|
|
171
171
|
| `replaceUrl` | `(url: string) => void` | `history.replaceState({}, '', url)` | Integrate removal of consumed OAuth query parameters with custom navigation. |
|
|
172
172
|
| `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. |
|
|
173
|
+
| `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
174
|
| `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
175
|
| `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
176
|
| `warn` | `(message: string) => void` | `console.warn` for schema checks | Route schema-drift warnings into application logging. |
|
|
@@ -192,6 +193,10 @@ The defaults are selected from browser capabilities at client creation:
|
|
|
192
193
|
- **Auth coordination:** token rotation and sign-out notifications use `BroadcastChannel` when
|
|
193
194
|
available. Access tokens can cross that channel transiently but are not written by the default
|
|
194
195
|
token store.
|
|
196
|
+
- **Lifecycle recovery:** when a tab regains focus, comes back online, or becomes visible again,
|
|
197
|
+
the client reconciles fresh credentials with the owner and mounted Sync sessions. A browser that
|
|
198
|
+
suspended the page recovers without a reload, and transient refresh failures retry rather than
|
|
199
|
+
signing the profile out. Override `reconcileTrigger` to supply that signal in another host.
|
|
195
200
|
- **Uploads:** browser multipart uploads use XHR for real `onProgress(loaded, total)` events. The
|
|
196
201
|
fetch fallback reports only start and completion.
|
|
197
202
|
|
|
@@ -745,6 +750,7 @@ Shares v2 is capability-gated and requires sign-in. `useOutgoingShares()` return
|
|
|
745
750
|
get(id: string): Promise<OutgoingShareInfo>
|
|
746
751
|
cancel(id: string): Promise<OutgoingShareInfo>
|
|
747
752
|
revoke(id: string): Promise<OutgoingShareInfo>
|
|
753
|
+
getContactHandle(did: string): Promise<string | null>
|
|
748
754
|
manageUrl(): string
|
|
749
755
|
}
|
|
750
756
|
```
|
|
@@ -754,7 +760,7 @@ Shares v2 is capability-gated and requires sign-in. `useOutgoingShares()` return
|
|
|
754
760
|
| Field | Type | Meaning |
|
|
755
761
|
| --- | --- | --- |
|
|
756
762
|
| `repo` | `'default' \| { repoId: string }` | Origin repo |
|
|
757
|
-
| recipient | Exactly one of `recipientHandle: string` or `recipientDid: string` | A handle is trimmed, lowercased, and resolved through `
|
|
763
|
+
| 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
764
|
| `role` | `'viewer' \| 'editor'` | Collaborator role |
|
|
759
765
|
| `scope` | `Array<{ table: string } \| { table: string; recordIds: string[] }>` | Whole-table clauses or static record sets |
|
|
760
766
|
| `acceptBy` | `string` | Optional acceptance deadline passed to Shares v2 |
|
|
@@ -807,6 +813,10 @@ export function OutgoingShares() {
|
|
|
807
813
|
`deliveryState`, and created, accepted, and ended timestamps/reasons. Revocation prevents future
|
|
808
814
|
access but cannot retract data a recipient already downloaded.
|
|
809
815
|
|
|
816
|
+
After a handle-based share is created successfully, the SDK stores a profile-local contact entry
|
|
817
|
+
containing `{ did, handle }` in browser local storage. Use `getContactHandle(recipientDid)` to show
|
|
818
|
+
the remembered handle; it returns `null` for DIDs that were shared directly or are not cached.
|
|
819
|
+
|
|
810
820
|
`manageUrl()` returns the Basic ID `/shares` handoff. Invite acceptance and ordinary mount
|
|
811
821
|
management belong there. `client.shares.leave()` is intentionally owner-only and throws
|
|
812
822
|
`OWNER_CREDENTIAL_REQUIRED` without an explicit owner adapter.
|
|
@@ -1093,8 +1103,8 @@ for the lower-level model.
|
|
|
1093
1103
|
Client Components should import `@basictech/react` directly.
|
|
1094
1104
|
- Migrating from 0.10 requires deliberate call-site changes; there is no compatibility layer. Read
|
|
1095
1105
|
[Migrating to Basic SDK 0.11](https://github.com/basicdb/basic-server/blob/dev/docs/MIGRATING_TO_0.11.md).
|
|
1096
|
-
-
|
|
1097
|
-
|
|
1106
|
+
- 0.11 is a beta API, published to npm under the `beta` dist-tag. Check the installed version
|
|
1107
|
+
rather than assuming it matches the repository workspace.
|
|
1098
1108
|
|
|
1099
1109
|
## License
|
|
1100
1110
|
|
package/dist/index.d.mts
CHANGED
|
@@ -87,6 +87,7 @@ interface UseOutgoingSharesResult {
|
|
|
87
87
|
get(id: string): Promise<OutgoingShareInfo>;
|
|
88
88
|
cancel(id: string): Promise<OutgoingShareInfo>;
|
|
89
89
|
revoke(id: string): Promise<OutgoingShareInfo>;
|
|
90
|
+
getContactHandle(did: string): Promise<string | null>;
|
|
90
91
|
manageUrl(): string;
|
|
91
92
|
}
|
|
92
93
|
declare function useOutgoingShares(): UseOutgoingSharesResult;
|
package/dist/index.d.ts
CHANGED
|
@@ -87,6 +87,7 @@ interface UseOutgoingSharesResult {
|
|
|
87
87
|
get(id: string): Promise<OutgoingShareInfo>;
|
|
88
88
|
cancel(id: string): Promise<OutgoingShareInfo>;
|
|
89
89
|
revoke(id: string): Promise<OutgoingShareInfo>;
|
|
90
|
+
getContactHandle(did: string): Promise<string | null>;
|
|
90
91
|
manageUrl(): string;
|
|
91
92
|
}
|
|
92
93
|
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);
|
|
@@ -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]);
|
|
@@ -790,7 +802,7 @@ function useAuthFor(client) {
|
|
|
790
802
|
isSignedIn: snapshot.isSignedIn,
|
|
791
803
|
isAnonymous: snapshot.isAnonymous,
|
|
792
804
|
status: snapshot.authStatus,
|
|
793
|
-
error: snapshot.authErrorCode ? new
|
|
805
|
+
error: snapshot.authErrorCode ? new import_core6.BasicError(snapshot.authErrorCode) : null,
|
|
794
806
|
user: snapshot.user,
|
|
795
807
|
did: snapshot.did,
|
|
796
808
|
handle: snapshot.handle,
|
|
@@ -979,6 +991,7 @@ function useOutgoingSharesFor(client) {
|
|
|
979
991
|
const get = (0, import_react9.useCallback)((id) => client.shares.getOutgoing(id), [client]);
|
|
980
992
|
const cancel = (0, import_react9.useCallback)((id) => client.shares.cancel(id), [client]);
|
|
981
993
|
const revoke = (0, import_react9.useCallback)((id) => client.shares.revoke(id), [client]);
|
|
994
|
+
const getContactHandle = (0, import_react9.useCallback)((did) => client.shares.getContactHandle(did), [client]);
|
|
982
995
|
const manageUrl = (0, import_react9.useCallback)(() => client.shares.manageUrl(), [client]);
|
|
983
996
|
const { data: outgoingShares, ...state } = result;
|
|
984
997
|
return {
|
|
@@ -989,6 +1002,7 @@ function useOutgoingSharesFor(client) {
|
|
|
989
1002
|
get,
|
|
990
1003
|
cancel,
|
|
991
1004
|
revoke,
|
|
1005
|
+
getContactHandle,
|
|
992
1006
|
manageUrl
|
|
993
1007
|
};
|
|
994
1008
|
}
|