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
package/dist/neural.d.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a suppressor graph on top of a captured microphone track.
|
|
3
|
+
*
|
|
4
|
+
* The model runs on the audio thread through an `AudioWorkletNode` — a
|
|
5
|
+
* recurrent network trained on speech that separates a voice from everything
|
|
6
|
+
* that is not one: keyboards, fans, traffic, a room full of people. The
|
|
7
|
+
* browser's `noiseSuppression` constraint answers the same question with a
|
|
8
|
+
* stationary-noise gate that either lets a transient through or eats the voice
|
|
9
|
+
* with it.
|
|
10
|
+
*
|
|
11
|
+
* Prefer {@link prepareNeuralSuppressor} before the capture opens: the fallback
|
|
12
|
+
* to the browser's own suppression is a `getUserMedia` constraint, and a
|
|
13
|
+
* constraint cannot be added to a capture that already exists. A caller that
|
|
14
|
+
* did not preload still works — the load happens here, idempotently.
|
|
15
|
+
*
|
|
16
|
+
* The model assumes 48 kHz. A context opened at any other rate answers `null`
|
|
17
|
+
* before anything is fetched, because feeding speech samples of the wrong rate
|
|
18
|
+
* into a network trained on 48 kHz produces mush, not a voice.
|
|
19
|
+
*
|
|
20
|
+
* Failure is not fatal and is reported as `null` — whether the wasm would not
|
|
21
|
+
* load or the worklet node could not be built: by the time this runs the caller
|
|
22
|
+
* has already committed to the neural path, so the honest outcome is a call
|
|
23
|
+
* without suppression rather than a microphone that will not open. {@link
|
|
24
|
+
* prepareNeuralSuppressor}, which exists to be called *before* the capture,
|
|
25
|
+
* still throws so the caller can pick a different fallback while one is
|
|
26
|
+
* reachable.
|
|
27
|
+
*
|
|
28
|
+
* @param context - The shared context, prepared at 48 kHz.
|
|
29
|
+
* @param source - The captured microphone track.
|
|
30
|
+
* @param assets - The resolved asset addresses from {@link NeuralSuppressorAssets}.
|
|
31
|
+
* @param options - `maxChannels` is the channel count of the capture; the model
|
|
32
|
+
* is mono, and a stereo capture asks for two so a second channel is not lost.
|
|
33
|
+
* @returns The denoised track and its teardown, or `null` when this context or
|
|
34
|
+
* this graph cannot host the suppressor.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createNeuralSuppressor(context: AudioContext, source: MediaStreamTrack, assets: NeuralSuppressorAssets, options: {
|
|
37
|
+
maxChannels: number;
|
|
38
|
+
}): Promise<NeuralSuppressor | null>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Whether this browser can run a worklet-based noise suppressor.
|
|
42
|
+
*
|
|
43
|
+
* Answered from the two things the path needs that nothing but the platform can
|
|
44
|
+
* supply: `AudioWorkletNode` runs custom DSP on the audio thread, and a secure
|
|
45
|
+
* context is what the browser requires before letting a module near the
|
|
46
|
+
* microphone graph. Everything else — the models, the wasm — ships with the
|
|
47
|
+
* caller, so it cannot be missing. The context must also run at 48 kHz, which
|
|
48
|
+
* is checked at build time rather than here because the caller owns how its
|
|
49
|
+
* context was opened.
|
|
50
|
+
*
|
|
51
|
+
* Deliberately conservative: anything it cannot establish answers `false`, and
|
|
52
|
+
* a `false` sends the caller back to the browser's own `noiseSuppression`
|
|
53
|
+
* constraint, which is weaker but always there.
|
|
54
|
+
*
|
|
55
|
+
* @returns True when a neural suppressor can be built on top of a capture.
|
|
56
|
+
*/
|
|
57
|
+
export declare function isNeuralSuppressionSupported(): boolean;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A denoised track plus the graph that has to come down with it.
|
|
61
|
+
*
|
|
62
|
+
* `track` is not the captured one — it is the output of a `MediaStream`
|
|
63
|
+
* destination the worklet feeds — so stopping the published track is not enough
|
|
64
|
+
* to free the microphone. `release` closes the graph; the captured track behind
|
|
65
|
+
* it belongs to the caller.
|
|
66
|
+
*/
|
|
67
|
+
export declare interface NeuralSuppressor {
|
|
68
|
+
/** The track to publish in place of the captured one. */
|
|
69
|
+
track: MediaStreamTrack;
|
|
70
|
+
/** Tears the graph down. Does not stop the source track it was built from. */
|
|
71
|
+
release(): void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Where the suppressor's three pieces live, as URLs a bundler hands over.
|
|
76
|
+
*
|
|
77
|
+
* The `@sapphi-red/web-noise-suppressor` package ships the runnable assets next
|
|
78
|
+
* to its code, but it cannot say where they are from inside a library: the
|
|
79
|
+
* `?url` suffix that resolves a module to its served address is a bundler
|
|
80
|
+
* feature, and every bundler answers it differently. So this subpath takes the
|
|
81
|
+
* three resolved addresses as arguments and does the honest part — the graph,
|
|
82
|
+
* the load-once semantics, the per-context registration — here.
|
|
83
|
+
*/
|
|
84
|
+
export declare interface NeuralSuppressorAssets {
|
|
85
|
+
/**
|
|
86
|
+
* The `AudioWorklet` processor module the model runs its DSP in
|
|
87
|
+
* (`rnnoiseWorklet.js`).
|
|
88
|
+
*/
|
|
89
|
+
workletUrl: string;
|
|
90
|
+
/** The SIMD build of the model's wasm (`rnnoise_simd.wasm`). */
|
|
91
|
+
simdWasmUrl: string;
|
|
92
|
+
/** The baseline build, used where the engine lacks SIMD (`rnnoise.wasm`). */
|
|
93
|
+
wasmUrl: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The compiled model for this context, loading what is not ready yet.
|
|
98
|
+
*
|
|
99
|
+
* Idempotent across calls: the wasm fetch and the module registration both
|
|
100
|
+
* happen exactly once. The wasm is shared by every suppressor, while the module
|
|
101
|
+
* registration belongs to one context.
|
|
102
|
+
*
|
|
103
|
+
* @param context - The context the suppression graph will run in.
|
|
104
|
+
* @param assets - The resolved asset addresses from {@link NeuralSuppressorAssets}.
|
|
105
|
+
* @returns The compiled model binary, ready to hand to a worklet node.
|
|
106
|
+
* @throws Whatever the fetch or the compile threw, for the caller to fall back on.
|
|
107
|
+
*/
|
|
108
|
+
export declare function prepareNeuralSuppressor(context: AudioContext, assets: NeuralSuppressorAssets): Promise<ArrayBuffer>;
|
|
109
|
+
|
|
110
|
+
export { }
|
package/dist/neural.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { RnnoiseWorkletNode as e, loadRnnoise as t } from "@sapphi-red/web-noise-suppressor";
|
|
2
|
+
//#region src/neural/index.ts
|
|
3
|
+
function n() {
|
|
4
|
+
return typeof AudioWorkletNode < "u" && typeof window < "u" && window.isSecureContext;
|
|
5
|
+
}
|
|
6
|
+
var r = null, i = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
async function a(e, n) {
|
|
8
|
+
let a = i.get(e);
|
|
9
|
+
return a || (a = e.audioWorklet.addModule(n.workletUrl).then(() => void 0, (t) => {
|
|
10
|
+
throw i.delete(e), t;
|
|
11
|
+
}), i.set(e, a)), await a, r === null && (r = t({
|
|
12
|
+
url: n.wasmUrl,
|
|
13
|
+
simdUrl: n.simdWasmUrl
|
|
14
|
+
}).catch((e) => {
|
|
15
|
+
throw r = null, e;
|
|
16
|
+
})), r;
|
|
17
|
+
}
|
|
18
|
+
async function o(t, n, r, i) {
|
|
19
|
+
if (t.sampleRate !== 48e3) return null;
|
|
20
|
+
let o;
|
|
21
|
+
try {
|
|
22
|
+
o = await a(t, r);
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
let s;
|
|
27
|
+
try {
|
|
28
|
+
s = new e(t, {
|
|
29
|
+
maxChannels: i.maxChannels,
|
|
30
|
+
wasmBinary: o
|
|
31
|
+
});
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
let c = t.createMediaStreamSource(new MediaStream([n])), l = t.createMediaStreamDestination();
|
|
36
|
+
c.connect(s), s.connect(l);
|
|
37
|
+
let u = l.stream.getAudioTracks()[0];
|
|
38
|
+
return u ? (u.contentHint = n.contentHint, {
|
|
39
|
+
track: u,
|
|
40
|
+
release: () => {
|
|
41
|
+
c.disconnect(), s.disconnect(), l.disconnect(), s.destroy(), u.stop();
|
|
42
|
+
}
|
|
43
|
+
}) : (c.disconnect(), s.disconnect(), l.disconnect(), s.destroy(), null);
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { o as createNeuralSuppressor, n as isNeuralSuppressionSupported, a as prepareNeuralSuppressor };
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=neural.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"neural.js","names":[],"sources":["../src/neural/index.ts"],"sourcesContent":["import { RnnoiseWorkletNode, loadRnnoise } from \"@sapphi-red/web-noise-suppressor\";\n\n/**\n * Where the suppressor's three pieces live, as URLs a bundler hands over.\n *\n * The `@sapphi-red/web-noise-suppressor` package ships the runnable assets next\n * to its code, but it cannot say where they are from inside a library: the\n * `?url` suffix that resolves a module to its served address is a bundler\n * feature, and every bundler answers it differently. So this subpath takes the\n * three resolved addresses as arguments and does the honest part — the graph,\n * the load-once semantics, the per-context registration — here.\n */\nexport interface NeuralSuppressorAssets {\n /**\n * The `AudioWorklet` processor module the model runs its DSP in\n * (`rnnoiseWorklet.js`).\n */\n workletUrl: string;\n /** The SIMD build of the model's wasm (`rnnoise_simd.wasm`). */\n simdWasmUrl: string;\n /** The baseline build, used where the engine lacks SIMD (`rnnoise.wasm`). */\n wasmUrl: string;\n}\n\n/**\n * Whether this browser can run a worklet-based noise suppressor.\n *\n * Answered from the two things the path needs that nothing but the platform can\n * supply: `AudioWorkletNode` runs custom DSP on the audio thread, and a secure\n * context is what the browser requires before letting a module near the\n * microphone graph. Everything else — the models, the wasm — ships with the\n * caller, so it cannot be missing. The context must also run at 48 kHz, which\n * is checked at build time rather than here because the caller owns how its\n * context was opened.\n *\n * Deliberately conservative: anything it cannot establish answers `false`, and\n * a `false` sends the caller back to the browser's own `noiseSuppression`\n * constraint, which is weaker but always there.\n *\n * @returns True when a neural suppressor can be built on top of a capture.\n */\nexport function isNeuralSuppressionSupported(): boolean {\n return (\n typeof AudioWorkletNode !== \"undefined\" &&\n typeof window !== \"undefined\" &&\n window.isSecureContext\n );\n}\n\n/**\n * The one wasm binary every suppressor shares, fetched once.\n *\n * `loadRnnoise` compiles the whole model, and the answer is identical for every\n * graph that opens afterwards — a mesh can build a suppressor per published\n * track and a settings dialog may build a second for its monitor within a\n * single frame. Held as the promise rather than the result so the first two\n * callers race into one fetch instead of two. Reset on failure, so the next\n * call retries instead of replaying a dead promise.\n */\nlet loadingBinary: Promise<ArrayBuffer> | null = null;\n\n/**\n * The contexts the worklet module has already been registered on, mapped to the\n * registration's own promise.\n *\n * `addModule` is per-context and throws on a name it already holds, so the\n * module records the moment a context is ready and skips the call next time.\n * The promise is what makes a fast caller that cannot wait — a mesh can open a\n * microphone and a preview within a single frame — race into one `addModule`\n * instead of two: had this been a set, the second caller would see the context\n * absent while the first was still awaiting and register the module twice.\n * Mapped to a promise rather than the module itself the way the wasm is,\n * because the registration belongs to one context while the wasm is shared,\n * and nothing here should keep a dead context alive — the value is dropped\n * with the context. On failure the entry is removed, so the next attempt\n * actually retries instead of replaying the broken registration.\n */\nconst readyContexts = new WeakMap<AudioContext, Promise<void>>();\n\n/**\n * The compiled model for this context, loading what is not ready yet.\n *\n * Idempotent across calls: the wasm fetch and the module registration both\n * happen exactly once. The wasm is shared by every suppressor, while the module\n * registration belongs to one context.\n *\n * @param context - The context the suppression graph will run in.\n * @param assets - The resolved asset addresses from {@link NeuralSuppressorAssets}.\n * @returns The compiled model binary, ready to hand to a worklet node.\n * @throws Whatever the fetch or the compile threw, for the caller to fall back on.\n */\nexport async function prepareNeuralSuppressor(\n context: AudioContext,\n assets: NeuralSuppressorAssets,\n): Promise<ArrayBuffer> {\n let ready = readyContexts.get(context);\n if (!ready) {\n ready = context.audioWorklet.addModule(assets.workletUrl).then(\n () => undefined,\n (error: unknown) => {\n readyContexts.delete(context);\n throw error;\n },\n );\n readyContexts.set(context, ready);\n }\n await ready;\n if (loadingBinary === null) {\n loadingBinary = loadRnnoise({ url: assets.wasmUrl, simdUrl: assets.simdWasmUrl }).catch(\n (error: unknown) => {\n loadingBinary = null;\n throw error;\n },\n );\n }\n return loadingBinary;\n}\n\n/**\n * A denoised track plus the graph that has to come down with it.\n *\n * `track` is not the captured one — it is the output of a `MediaStream`\n * destination the worklet feeds — so stopping the published track is not enough\n * to free the microphone. `release` closes the graph; the captured track behind\n * it belongs to the caller.\n */\nexport interface NeuralSuppressor {\n /** The track to publish in place of the captured one. */\n track: MediaStreamTrack;\n /** Tears the graph down. Does not stop the source track it was built from. */\n release(): void;\n}\n\n/**\n * Build a suppressor graph on top of a captured microphone track.\n *\n * The model runs on the audio thread through an `AudioWorkletNode` — a\n * recurrent network trained on speech that separates a voice from everything\n * that is not one: keyboards, fans, traffic, a room full of people. The\n * browser's `noiseSuppression` constraint answers the same question with a\n * stationary-noise gate that either lets a transient through or eats the voice\n * with it.\n *\n * Prefer {@link prepareNeuralSuppressor} before the capture opens: the fallback\n * to the browser's own suppression is a `getUserMedia` constraint, and a\n * constraint cannot be added to a capture that already exists. A caller that\n * did not preload still works — the load happens here, idempotently.\n *\n * The model assumes 48 kHz. A context opened at any other rate answers `null`\n * before anything is fetched, because feeding speech samples of the wrong rate\n * into a network trained on 48 kHz produces mush, not a voice.\n *\n * Failure is not fatal and is reported as `null` — whether the wasm would not\n * load or the worklet node could not be built: by the time this runs the caller\n * has already committed to the neural path, so the honest outcome is a call\n * without suppression rather than a microphone that will not open. {@link\n * prepareNeuralSuppressor}, which exists to be called *before* the capture,\n * still throws so the caller can pick a different fallback while one is\n * reachable.\n *\n * @param context - The shared context, prepared at 48 kHz.\n * @param source - The captured microphone track.\n * @param assets - The resolved asset addresses from {@link NeuralSuppressorAssets}.\n * @param options - `maxChannels` is the channel count of the capture; the model\n * is mono, and a stereo capture asks for two so a second channel is not lost.\n * @returns The denoised track and its teardown, or `null` when this context or\n * this graph cannot host the suppressor.\n */\nexport async function createNeuralSuppressor(\n context: AudioContext,\n source: MediaStreamTrack,\n assets: NeuralSuppressorAssets,\n options: { maxChannels: number },\n): Promise<NeuralSuppressor | null> {\n if (context.sampleRate !== 48_000) return null;\n\n let binary: ArrayBuffer;\n try {\n binary = await prepareNeuralSuppressor(context, assets);\n } catch {\n return null;\n }\n\n let node: RnnoiseWorkletNode;\n try {\n node = new RnnoiseWorkletNode(context, {\n maxChannels: options.maxChannels,\n wasmBinary: binary,\n });\n } catch {\n return null;\n }\n\n const input = context.createMediaStreamSource(new MediaStream([source]));\n const output = context.createMediaStreamDestination();\n input.connect(node);\n node.connect(output);\n\n const track = output.stream.getAudioTracks()[0];\n if (!track) {\n input.disconnect();\n node.disconnect();\n output.disconnect();\n node.destroy();\n return null;\n }\n track.contentHint = source.contentHint;\n\n return {\n track,\n release: () => {\n input.disconnect();\n node.disconnect();\n output.disconnect();\n node.destroy();\n track.stop();\n },\n };\n}\n"],"mappings":";;AAyCA,SAAgB,IAAwC;CACpD,OACI,OAAO,mBAAqB,OAC5B,OAAO,SAAW,OAClB,OAAO;AAEf;AAYA,IAAI,IAA6C,MAkB3C,oBAAgB,IAAI,QAAqC;AAc/D,eAAsB,EAClB,GACA,GACoB;CACpB,IAAI,IAAQ,EAAc,IAAI,CAAO;CAoBrC,OAnBK,MACD,IAAQ,EAAQ,aAAa,UAAU,EAAO,UAAU,CAAC,CAAC,WAChD,KAAA,IACL,MAAmB;EAEhB,MADA,EAAc,OAAO,CAAO,GACtB;CACV,CACJ,GACA,EAAc,IAAI,GAAS,CAAK,IAEpC,MAAM,GACF,MAAkB,SAClB,IAAgB,EAAY;EAAE,KAAK,EAAO;EAAS,SAAS,EAAO;CAAY,CAAC,CAAC,CAAC,OAC7E,MAAmB;EAEhB,MADA,IAAgB,MACV;CACV,CACJ,IAEG;AACX;AAoDA,eAAsB,EAClB,GACA,GACA,GACA,GACgC;CAChC,IAAI,EAAQ,eAAe,MAAQ,OAAO;CAE1C,IAAI;CACJ,IAAI;EACA,IAAS,MAAM,EAAwB,GAAS,CAAM;CAC1D,QAAQ;EACJ,OAAO;CACX;CAEA,IAAI;CACJ,IAAI;EACA,IAAO,IAAI,EAAmB,GAAS;GACnC,aAAa,EAAQ;GACrB,YAAY;EAChB,CAAC;CACL,QAAQ;EACJ,OAAO;CACX;CAEA,IAAM,IAAQ,EAAQ,wBAAwB,IAAI,YAAY,CAAC,CAAM,CAAC,CAAC,GACjE,IAAS,EAAQ,6BAA6B;CAEpD,AADA,EAAM,QAAQ,CAAI,GAClB,EAAK,QAAQ,CAAM;CAEnB,IAAM,IAAQ,EAAO,OAAO,eAAe,CAAC,CAAC;CAU7C,OATK,KAOL,EAAM,cAAc,EAAO,aAEpB;EACH;EACA,eAAe;GAKX,AAJA,EAAM,WAAW,GACjB,EAAK,WAAW,GAChB,EAAO,WAAW,GAClB,EAAK,QAAQ,GACb,EAAM,KAAK;EACf;CACJ,MAjBI,EAAM,WAAW,GACjB,EAAK,WAAW,GAChB,EAAO,WAAW,GAClB,EAAK,QAAQ,GACN;AAcf"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=class extends Error{reason;providerError;constructor(e,t){super(e===`provider_error`?`OAuth provider returned an error: ${t??`unknown`}`:`OAuth callback is missing the "${e===`missing_state`?`state`:`code`}" param`),this.name=`OAuthCallbackError`,this.reason=e,this.providerError=t}},t=[`code`,`state`];function n(){let e=new URL(window.location.href);if(t.some(t=>e.searchParams.has(t))){for(let n of t)e.searchParams.delete(n);window.history.replaceState(window.history.state,``,`${e.pathname}${e.search}${e.hash}`)}}function r(t,r={}){let i=t===void 0,{scrubUrl:a=i}=r,o=t??globalThis.location?.search??``,s=typeof o==`string`?new URLSearchParams(o):o,c=s.get(`error`),l=s.get(`state`),u=s.get(`code`);if(a&&typeof window<`u`&&n(),c)throw new e(`provider_error`,c);if(!l)throw new e(`missing_state`);if(!u)throw new e(`missing_code`);return{code:u,state:l}}async function i(e,t,n={}){let{prefix:i=`/auth`,search:a,scrubUrl:o,signal:s}=n,{code:c,state:l}=r(a,{scrubUrl:o}),u=i.replace(/\/+$/,``);return e.get(`${u}/oauth/${encodeURIComponent(t)}/callback`,{params:{code:c,state:l},credentials:`include`,skipAuth:!0,retry:!1,signal:s})}exports.OAuthCallbackError=e,exports.exchangeOAuthCallback=i,exports.readOAuthCallbackParams=r;
|
|
2
|
+
//# sourceMappingURL=callback.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback.cjs","names":[],"sources":["../../src/oauth/callback.ts"],"sourcesContent":["import type { ApiClient } from \"@/http/types\";\n\n/** Why {@link readOAuthCallbackParams} refused a callback URL. */\nexport type OAuthCallbackErrorReason = \"provider_error\" | \"missing_state\" | \"missing_code\";\n\n/**\n * A provider redirect that cannot be exchanged for a session.\n *\n * Thrown before any request leaves the browser, so a forged or truncated\n * callback never reaches the backend's exchange route.\n */\nexport class OAuthCallbackError extends Error {\n /** Which part of the callback was wrong. */\n readonly reason: OAuthCallbackErrorReason;\n /** The provider's `error` param (e.g. `\"access_denied\"`), when `reason` is `\"provider_error\"`. */\n readonly providerError?: string;\n\n /**\n * @param reason - Which part of the callback was wrong.\n * @param providerError - The provider's `error` param, when it sent one.\n */\n constructor(reason: OAuthCallbackErrorReason, providerError?: string) {\n super(\n reason === \"provider_error\"\n ? `OAuth provider returned an error: ${providerError ?? \"unknown\"}`\n : `OAuth callback is missing the \"${reason === \"missing_state\" ? \"state\" : \"code\"}\" param`,\n );\n this.name = \"OAuthCallbackError\";\n this.reason = reason;\n this.providerError = providerError;\n }\n}\n\n/** The two values a provider appends to the redirect URI on success. */\nexport interface OAuthCallbackParams {\n /** Single-use authorization code. */\n code: string;\n /**\n * The CSRF `state` the backend minted when the flow started. Forwarded\n * verbatim: only the backend holds the value it must match.\n */\n state: string;\n}\n\n/** Options for {@link readOAuthCallbackParams}. */\nexport interface ReadOAuthCallbackOptions {\n /**\n * Remove `code` and `state` from the address bar after reading them, with\n * `history.replaceState` — other params, the hash and `history.state` are\n * kept. Default: `true` when reading `window.location`, `false` when an\n * explicit `search` is passed (there is no URL of yours to clean then).\n */\n scrubUrl?: boolean;\n}\n\nconst CALLBACK_SECRETS: readonly string[] = [\"code\", \"state\"];\n\n/**\n * Drop `code` and `state` from the current URL without a navigation.\n *\n * Left in place, the single-use code sits in the history entry, in a bookmark,\n * in a shared screenshot and in the `Referer` of the next request. Passing\n * `history.state` back keeps a router's own bookkeeping (React Router's `idx`)\n * intact. No-op when neither param is present.\n */\nfunction scrubCallbackUrl(): void {\n const url = new URL(window.location.href);\n if (!CALLBACK_SECRETS.some((key) => url.searchParams.has(key))) return;\n for (const key of CALLBACK_SECRETS) url.searchParams.delete(key);\n window.history.replaceState(\n window.history.state,\n \"\",\n `${url.pathname}${url.search}${url.hash}`,\n );\n}\n\n/**\n * Read `code` and `state` from a provider's redirect, refusing an incomplete one.\n *\n * The `state` is what ties the callback to the browser that started the flow.\n * Without it, an attacker sends the victim to `/callback?code=<attacker's code>`\n * and the victim ends up signed in to the attacker's account — a login CSRF.\n * The old recipe read only `code` and posted `{ code }`, so the `state` never\n * reached a backend that could compare it (#449). This reader makes dropping it\n * impossible: it returns both or throws.\n *\n * It does **not** compare the state. In the `tempest-fastapi-sdk` flow the\n * backend mints it in `GET {prefix}/oauth/{provider}/login`, keeps it in an\n * `HttpOnly` cookie bound to the provider, and checks it with\n * `hmac.compare_digest` in the callback (single use, `AUTH_OAUTH_STATE_TTL_SECONDS`\n * expiry). The browser cannot read that cookie, so any check here would be\n * theatre; forwarding the value is the browser's whole job.\n *\n * When it reads `window.location`, it also removes `code` and `state` from the\n * address bar (see {@link ReadOAuthCallbackOptions.scrubUrl}) — before\n * validating, so a refused callback is cleaned too.\n *\n * @param search - The query string or params to read. Default: `window.location.search`.\n * @param options - Whether to scrub the URL.\n * @returns The `code` and `state`.\n * @throws {OAuthCallbackError} `\"provider_error\"` when the provider sent `error`\n * (the user declined, for instance), `\"missing_state\"` without `state`,\n * `\"missing_code\"` without `code`.\n *\n * @example\n * const { code, state } = readOAuthCallbackParams(\"?code=4%2F0A&state=xyz\");\n */\nexport function readOAuthCallbackParams(\n search?: string | URLSearchParams,\n options: ReadOAuthCallbackOptions = {},\n): OAuthCallbackParams {\n const fromLocation = search === undefined;\n const { scrubUrl = fromLocation } = options;\n const source = search ?? globalThis.location?.search ?? \"\";\n const params = typeof source === \"string\" ? new URLSearchParams(source) : source;\n const error = params.get(\"error\");\n const state = params.get(\"state\");\n const code = params.get(\"code\");\n if (scrubUrl && typeof window !== \"undefined\") scrubCallbackUrl();\n if (error) throw new OAuthCallbackError(\"provider_error\", error);\n if (!state) throw new OAuthCallbackError(\"missing_state\");\n if (!code) throw new OAuthCallbackError(\"missing_code\");\n return { code, state };\n}\n\n/** Options for {@link exchangeOAuthCallback}. */\nexport interface ExchangeOAuthCallbackOptions {\n /**\n * Path the backend's auth router is mounted at — the `prefix` given to\n * `make_auth_router`. Default: `\"/auth\"`.\n */\n prefix?: string;\n /** Where to read the provider's redirect from. Default: `window.location.search`. */\n search?: string | URLSearchParams;\n /** Forwarded to {@link readOAuthCallbackParams}. Default: scrub when reading `window.location`. */\n scrubUrl?: boolean;\n /** Abort the exchange. */\n signal?: AbortSignal;\n}\n\n/**\n * Finish a `tempest-fastapi-sdk` social login from the SPA's callback route.\n *\n * Reads the redirect with {@link readOAuthCallbackParams} and forwards `code`\n * **and** `state` to `GET {prefix}/oauth/{provider}/callback`, where the backend\n * compares the state against its `HttpOnly` cookie, redeems the code and\n * answers with the same JWT pair `POST {prefix}/login` returns.\n *\n * The request goes with `credentials: \"include\"` because the state cookie is\n * the other half of the comparison; with `skipAuth`, since there is no session\n * yet and a `401` here means a forged or expired callback, not an expired token\n * to refresh; and with `retry: false`, because the backend clears the state\n * cookie on the first attempt and a code redeems once.\n *\n * Pass it as the `exchange` of {@link useOAuthCallback}, which runs it once even\n * under StrictMode.\n *\n * @param api - The client from `createApiClient` (or `createTempestAuth().api`).\n * @param provider - The provider key registered on the backend (e.g. `\"google\"`).\n * @param options - Router prefix, the query string to read, abort signal.\n * @returns The backend's login response.\n * @throws {OAuthCallbackError} Before any request, when the redirect carries\n * `error` or lacks `state` or `code`.\n *\n * @example\n * const { loading, error } = useOAuthCallback<LoginResponse>({\n * exchange: () => exchangeOAuthCallback<LoginResponse>(api, \"google\"),\n * onSuccess: () => navigate(\"/\", { replace: true }),\n * });\n */\nexport async function exchangeOAuthCallback<T>(\n api: ApiClient,\n provider: string,\n options: ExchangeOAuthCallbackOptions = {},\n): Promise<T> {\n const { prefix = \"/auth\", search, scrubUrl, signal } = options;\n const { code, state } = readOAuthCallbackParams(search, { scrubUrl });\n const base = prefix.replace(/\\/+$/, \"\");\n return api.get<T>(`${base}/oauth/${encodeURIComponent(provider)}/callback`, {\n params: { code, state },\n credentials: \"include\",\n skipAuth: true,\n retry: false,\n signal,\n });\n}\n"],"mappings":"AAWA,IAAa,EAAb,cAAwC,KAAM,CAE1C,OAEA,cAMA,YAAY,EAAkC,EAAwB,CAClE,MACI,IAAW,iBACL,qCAAqC,GAAiB,YACtD,kCAAkC,IAAW,gBAAkB,QAAU,OAAO,QAC1F,EACA,KAAK,KAAO,qBACZ,KAAK,OAAS,EACd,KAAK,cAAgB,CACzB,CACJ,EAwBM,EAAsC,CAAC,OAAQ,OAAO,EAU5D,SAAS,GAAyB,CAC9B,IAAM,EAAM,IAAI,IAAI,OAAO,SAAS,IAAI,EACnC,KAAiB,KAAM,GAAQ,EAAI,aAAa,IAAI,CAAG,CAAC,EAC7D,KAAK,IAAM,KAAO,EAAkB,EAAI,aAAa,OAAO,CAAG,EAC/D,OAAO,QAAQ,aACX,OAAO,QAAQ,MACf,GACA,GAAG,EAAI,WAAW,EAAI,SAAS,EAAI,MACvC,CAL+D,CAMnE,CAiCA,SAAgB,EACZ,EACA,EAAoC,CAAC,EAClB,CACnB,IAAM,EAAe,IAAW,IAAA,GAC1B,CAAE,WAAW,GAAiB,EAC9B,EAAS,GAAU,WAAW,UAAU,QAAU,GAClD,EAAS,OAAO,GAAW,SAAW,IAAI,gBAAgB,CAAM,EAAI,EACpE,EAAQ,EAAO,IAAI,OAAO,EAC1B,EAAQ,EAAO,IAAI,OAAO,EAC1B,EAAO,EAAO,IAAI,MAAM,EAE9B,GADI,GAAY,OAAO,OAAW,KAAa,EAAiB,EAC5D,EAAO,MAAM,IAAI,EAAmB,iBAAkB,CAAK,EAC/D,GAAI,CAAC,EAAO,MAAM,IAAI,EAAmB,eAAe,EACxD,GAAI,CAAC,EAAM,MAAM,IAAI,EAAmB,cAAc,EACtD,MAAO,CAAE,OAAM,OAAM,CACzB,CA+CA,eAAsB,EAClB,EACA,EACA,EAAwC,CAAC,EAC/B,CACV,GAAM,CAAE,SAAS,QAAS,SAAQ,WAAU,UAAW,EACjD,CAAE,OAAM,SAAU,EAAwB,EAAQ,CAAE,UAAS,CAAC,EAC9D,EAAO,EAAO,QAAQ,OAAQ,EAAE,EACtC,OAAO,EAAI,IAAO,GAAG,EAAK,SAAS,mBAAmB,CAAQ,EAAE,WAAY,CACxE,OAAQ,CAAE,OAAM,OAAM,EACtB,YAAa,UACb,SAAU,GACV,MAAO,GACP,QACJ,CAAC,CACL"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//#region src/oauth/callback.ts
|
|
2
|
+
var e = class extends Error {
|
|
3
|
+
reason;
|
|
4
|
+
providerError;
|
|
5
|
+
constructor(e, t) {
|
|
6
|
+
super(e === "provider_error" ? `OAuth provider returned an error: ${t ?? "unknown"}` : `OAuth callback is missing the "${e === "missing_state" ? "state" : "code"}" param`), this.name = "OAuthCallbackError", this.reason = e, this.providerError = t;
|
|
7
|
+
}
|
|
8
|
+
}, t = ["code", "state"];
|
|
9
|
+
function n() {
|
|
10
|
+
let e = new URL(window.location.href);
|
|
11
|
+
if (t.some((t) => e.searchParams.has(t))) {
|
|
12
|
+
for (let n of t) e.searchParams.delete(n);
|
|
13
|
+
window.history.replaceState(window.history.state, "", `${e.pathname}${e.search}${e.hash}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function r(t, r = {}) {
|
|
17
|
+
let i = t === void 0, { scrubUrl: a = i } = r, o = t ?? globalThis.location?.search ?? "", s = typeof o == "string" ? new URLSearchParams(o) : o, c = s.get("error"), l = s.get("state"), u = s.get("code");
|
|
18
|
+
if (a && typeof window < "u" && n(), c) throw new e("provider_error", c);
|
|
19
|
+
if (!l) throw new e("missing_state");
|
|
20
|
+
if (!u) throw new e("missing_code");
|
|
21
|
+
return {
|
|
22
|
+
code: u,
|
|
23
|
+
state: l
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async function i(e, t, n = {}) {
|
|
27
|
+
let { prefix: i = "/auth", search: a, scrubUrl: o, signal: s } = n, { code: c, state: l } = r(a, { scrubUrl: o }), u = i.replace(/\/+$/, "");
|
|
28
|
+
return e.get(`${u}/oauth/${encodeURIComponent(t)}/callback`, {
|
|
29
|
+
params: {
|
|
30
|
+
code: c,
|
|
31
|
+
state: l
|
|
32
|
+
},
|
|
33
|
+
credentials: "include",
|
|
34
|
+
skipAuth: !0,
|
|
35
|
+
retry: !1,
|
|
36
|
+
signal: s
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { e as OAuthCallbackError, i as exchangeOAuthCallback, r as readOAuthCallbackParams };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=callback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback.js","names":[],"sources":["../../src/oauth/callback.ts"],"sourcesContent":["import type { ApiClient } from \"@/http/types\";\n\n/** Why {@link readOAuthCallbackParams} refused a callback URL. */\nexport type OAuthCallbackErrorReason = \"provider_error\" | \"missing_state\" | \"missing_code\";\n\n/**\n * A provider redirect that cannot be exchanged for a session.\n *\n * Thrown before any request leaves the browser, so a forged or truncated\n * callback never reaches the backend's exchange route.\n */\nexport class OAuthCallbackError extends Error {\n /** Which part of the callback was wrong. */\n readonly reason: OAuthCallbackErrorReason;\n /** The provider's `error` param (e.g. `\"access_denied\"`), when `reason` is `\"provider_error\"`. */\n readonly providerError?: string;\n\n /**\n * @param reason - Which part of the callback was wrong.\n * @param providerError - The provider's `error` param, when it sent one.\n */\n constructor(reason: OAuthCallbackErrorReason, providerError?: string) {\n super(\n reason === \"provider_error\"\n ? `OAuth provider returned an error: ${providerError ?? \"unknown\"}`\n : `OAuth callback is missing the \"${reason === \"missing_state\" ? \"state\" : \"code\"}\" param`,\n );\n this.name = \"OAuthCallbackError\";\n this.reason = reason;\n this.providerError = providerError;\n }\n}\n\n/** The two values a provider appends to the redirect URI on success. */\nexport interface OAuthCallbackParams {\n /** Single-use authorization code. */\n code: string;\n /**\n * The CSRF `state` the backend minted when the flow started. Forwarded\n * verbatim: only the backend holds the value it must match.\n */\n state: string;\n}\n\n/** Options for {@link readOAuthCallbackParams}. */\nexport interface ReadOAuthCallbackOptions {\n /**\n * Remove `code` and `state` from the address bar after reading them, with\n * `history.replaceState` — other params, the hash and `history.state` are\n * kept. Default: `true` when reading `window.location`, `false` when an\n * explicit `search` is passed (there is no URL of yours to clean then).\n */\n scrubUrl?: boolean;\n}\n\nconst CALLBACK_SECRETS: readonly string[] = [\"code\", \"state\"];\n\n/**\n * Drop `code` and `state` from the current URL without a navigation.\n *\n * Left in place, the single-use code sits in the history entry, in a bookmark,\n * in a shared screenshot and in the `Referer` of the next request. Passing\n * `history.state` back keeps a router's own bookkeeping (React Router's `idx`)\n * intact. No-op when neither param is present.\n */\nfunction scrubCallbackUrl(): void {\n const url = new URL(window.location.href);\n if (!CALLBACK_SECRETS.some((key) => url.searchParams.has(key))) return;\n for (const key of CALLBACK_SECRETS) url.searchParams.delete(key);\n window.history.replaceState(\n window.history.state,\n \"\",\n `${url.pathname}${url.search}${url.hash}`,\n );\n}\n\n/**\n * Read `code` and `state` from a provider's redirect, refusing an incomplete one.\n *\n * The `state` is what ties the callback to the browser that started the flow.\n * Without it, an attacker sends the victim to `/callback?code=<attacker's code>`\n * and the victim ends up signed in to the attacker's account — a login CSRF.\n * The old recipe read only `code` and posted `{ code }`, so the `state` never\n * reached a backend that could compare it (#449). This reader makes dropping it\n * impossible: it returns both or throws.\n *\n * It does **not** compare the state. In the `tempest-fastapi-sdk` flow the\n * backend mints it in `GET {prefix}/oauth/{provider}/login`, keeps it in an\n * `HttpOnly` cookie bound to the provider, and checks it with\n * `hmac.compare_digest` in the callback (single use, `AUTH_OAUTH_STATE_TTL_SECONDS`\n * expiry). The browser cannot read that cookie, so any check here would be\n * theatre; forwarding the value is the browser's whole job.\n *\n * When it reads `window.location`, it also removes `code` and `state` from the\n * address bar (see {@link ReadOAuthCallbackOptions.scrubUrl}) — before\n * validating, so a refused callback is cleaned too.\n *\n * @param search - The query string or params to read. Default: `window.location.search`.\n * @param options - Whether to scrub the URL.\n * @returns The `code` and `state`.\n * @throws {OAuthCallbackError} `\"provider_error\"` when the provider sent `error`\n * (the user declined, for instance), `\"missing_state\"` without `state`,\n * `\"missing_code\"` without `code`.\n *\n * @example\n * const { code, state } = readOAuthCallbackParams(\"?code=4%2F0A&state=xyz\");\n */\nexport function readOAuthCallbackParams(\n search?: string | URLSearchParams,\n options: ReadOAuthCallbackOptions = {},\n): OAuthCallbackParams {\n const fromLocation = search === undefined;\n const { scrubUrl = fromLocation } = options;\n const source = search ?? globalThis.location?.search ?? \"\";\n const params = typeof source === \"string\" ? new URLSearchParams(source) : source;\n const error = params.get(\"error\");\n const state = params.get(\"state\");\n const code = params.get(\"code\");\n if (scrubUrl && typeof window !== \"undefined\") scrubCallbackUrl();\n if (error) throw new OAuthCallbackError(\"provider_error\", error);\n if (!state) throw new OAuthCallbackError(\"missing_state\");\n if (!code) throw new OAuthCallbackError(\"missing_code\");\n return { code, state };\n}\n\n/** Options for {@link exchangeOAuthCallback}. */\nexport interface ExchangeOAuthCallbackOptions {\n /**\n * Path the backend's auth router is mounted at — the `prefix` given to\n * `make_auth_router`. Default: `\"/auth\"`.\n */\n prefix?: string;\n /** Where to read the provider's redirect from. Default: `window.location.search`. */\n search?: string | URLSearchParams;\n /** Forwarded to {@link readOAuthCallbackParams}. Default: scrub when reading `window.location`. */\n scrubUrl?: boolean;\n /** Abort the exchange. */\n signal?: AbortSignal;\n}\n\n/**\n * Finish a `tempest-fastapi-sdk` social login from the SPA's callback route.\n *\n * Reads the redirect with {@link readOAuthCallbackParams} and forwards `code`\n * **and** `state` to `GET {prefix}/oauth/{provider}/callback`, where the backend\n * compares the state against its `HttpOnly` cookie, redeems the code and\n * answers with the same JWT pair `POST {prefix}/login` returns.\n *\n * The request goes with `credentials: \"include\"` because the state cookie is\n * the other half of the comparison; with `skipAuth`, since there is no session\n * yet and a `401` here means a forged or expired callback, not an expired token\n * to refresh; and with `retry: false`, because the backend clears the state\n * cookie on the first attempt and a code redeems once.\n *\n * Pass it as the `exchange` of {@link useOAuthCallback}, which runs it once even\n * under StrictMode.\n *\n * @param api - The client from `createApiClient` (or `createTempestAuth().api`).\n * @param provider - The provider key registered on the backend (e.g. `\"google\"`).\n * @param options - Router prefix, the query string to read, abort signal.\n * @returns The backend's login response.\n * @throws {OAuthCallbackError} Before any request, when the redirect carries\n * `error` or lacks `state` or `code`.\n *\n * @example\n * const { loading, error } = useOAuthCallback<LoginResponse>({\n * exchange: () => exchangeOAuthCallback<LoginResponse>(api, \"google\"),\n * onSuccess: () => navigate(\"/\", { replace: true }),\n * });\n */\nexport async function exchangeOAuthCallback<T>(\n api: ApiClient,\n provider: string,\n options: ExchangeOAuthCallbackOptions = {},\n): Promise<T> {\n const { prefix = \"/auth\", search, scrubUrl, signal } = options;\n const { code, state } = readOAuthCallbackParams(search, { scrubUrl });\n const base = prefix.replace(/\\/+$/, \"\");\n return api.get<T>(`${base}/oauth/${encodeURIComponent(provider)}/callback`, {\n params: { code, state },\n credentials: \"include\",\n skipAuth: true,\n retry: false,\n signal,\n });\n}\n"],"mappings":";AAWA,IAAa,IAAb,cAAwC,MAAM;CAE1C;CAEA;CAMA,YAAY,GAAkC,GAAwB;EAQlE,AAPA,MACI,MAAW,mBACL,qCAAqC,KAAiB,cACtD,kCAAkC,MAAW,kBAAkB,UAAU,OAAO,QAC1F,GACA,KAAK,OAAO,sBACZ,KAAK,SAAS,GACd,KAAK,gBAAgB;CACzB;AACJ,GAwBM,IAAsC,CAAC,QAAQ,OAAO;AAU5D,SAAS,IAAyB;CAC9B,IAAM,IAAM,IAAI,IAAI,OAAO,SAAS,IAAI;CACnC,MAAiB,MAAM,MAAQ,EAAI,aAAa,IAAI,CAAG,CAAC,GAC7D;OAAK,IAAM,KAAO,GAAkB,EAAI,aAAa,OAAO,CAAG;EAC/D,OAAO,QAAQ,aACX,OAAO,QAAQ,OACf,IACA,GAAG,EAAI,WAAW,EAAI,SAAS,EAAI,MACvC;CAL+D;AAMnE;AAiCA,SAAgB,EACZ,GACA,IAAoC,CAAC,GAClB;CACnB,IAAM,IAAe,MAAW,KAAA,GAC1B,EAAE,cAAW,MAAiB,GAC9B,IAAS,KAAU,WAAW,UAAU,UAAU,IAClD,IAAS,OAAO,KAAW,WAAW,IAAI,gBAAgB,CAAM,IAAI,GACpE,IAAQ,EAAO,IAAI,OAAO,GAC1B,IAAQ,EAAO,IAAI,OAAO,GAC1B,IAAO,EAAO,IAAI,MAAM;CAE9B,IADI,KAAY,OAAO,SAAW,OAAa,EAAiB,GAC5D,GAAO,MAAM,IAAI,EAAmB,kBAAkB,CAAK;CAC/D,IAAI,CAAC,GAAO,MAAM,IAAI,EAAmB,eAAe;CACxD,IAAI,CAAC,GAAM,MAAM,IAAI,EAAmB,cAAc;CACtD,OAAO;EAAE;EAAM;CAAM;AACzB;AA+CA,eAAsB,EAClB,GACA,GACA,IAAwC,CAAC,GAC/B;CACV,IAAM,EAAE,YAAS,SAAS,WAAQ,aAAU,cAAW,GACjD,EAAE,SAAM,aAAU,EAAwB,GAAQ,EAAE,YAAS,CAAC,GAC9D,IAAO,EAAO,QAAQ,QAAQ,EAAE;CACtC,OAAO,EAAI,IAAO,GAAG,EAAK,SAAS,mBAAmB,CAAQ,EAAE,YAAY;EACxE,QAAQ;GAAE;GAAM;EAAM;EACtB,aAAa;EACb,UAAU;EACV,OAAO;EACP;CACJ,CAAC;AACL"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
let e=require("react");function t(t){let
|
|
1
|
+
let e=require("react");function t(t){let[n,r]=(0,e.useState)(null),[i,a]=(0,e.useState)(null),[o,s]=(0,e.useState)(`pending`),c=(0,e.useRef)(t),l=(0,e.useRef)(!1),u=(0,e.useRef)(!1);return(0,e.useEffect)(()=>{c.current=t}),(0,e.useEffect)(()=>(u.current=!0,l.current||(l.current=!0,c.current.exchange().then(e=>{if(u.current)return r(e),s(`success`),c.current.onSuccess?.(e)}).catch(e=>{u.current&&(a(e),s(`error`),c.current.onError?.(e))})),()=>{u.current=!1}),[]),{loading:o===`pending`,data:n,error:i,status:o}}exports.useOAuthCallback=t;
|
|
2
2
|
//# sourceMappingURL=use-oauth-callback.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-oauth-callback.cjs","names":[],"sources":["../../src/oauth/use-oauth-callback.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\n\nexport interface UseOAuthCallbackOptions<T> {\n /** Function that exchanges the provider response for an app session. Called once on mount. */\n exchange: () => Promise<T>;\n /** Fired with the result on success. Receives the value resolved by `exchange`. */\n onSuccess?: (result: T) => void | Promise<void>;\n /** Fired when `exchange` throws or rejects. */\n onError?: (error: unknown) => void;\n}\n\nexport interface UseOAuthCallbackResult<T> {\n /** `true` while the exchange promise is pending. */\n loading: boolean;\n /** Last resolved value, when `status === \"success\"`. */\n data: T | null;\n /** Last rejection reason, when `status === \"error\"`. */\n error: unknown;\n /** Aggregated state — `\"pending\"`, `\"success\"`, or `\"error\"`. */\n status: \"pending\" | \"success\" | \"error\";\n}\n\n/**\n * Run an OAuth-callback \"exchange\" exactly once on mount. Designed for\n * `/callback` routes that receive provider redirects and need to swap a\n * code/token for an app session via the backend.\n *\n *
|
|
1
|
+
{"version":3,"file":"use-oauth-callback.cjs","names":[],"sources":["../../src/oauth/use-oauth-callback.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\n\nexport interface UseOAuthCallbackOptions<T> {\n /** Function that exchanges the provider response for an app session. Called once on mount. */\n exchange: () => Promise<T>;\n /** Fired with the result on success. Receives the value resolved by `exchange`. */\n onSuccess?: (result: T) => void | Promise<void>;\n /** Fired when `exchange` throws or rejects. */\n onError?: (error: unknown) => void;\n}\n\nexport interface UseOAuthCallbackResult<T> {\n /** `true` while the exchange promise is pending. */\n loading: boolean;\n /** Last resolved value, when `status === \"success\"`. */\n data: T | null;\n /** Last rejection reason, when `status === \"error\"`. */\n error: unknown;\n /** Aggregated state — `\"pending\"`, `\"success\"`, or `\"error\"`. */\n status: \"pending\" | \"success\" | \"error\";\n}\n\n/**\n * Run an OAuth-callback \"exchange\" exactly once on mount. Designed for\n * `/callback` routes that receive provider redirects and need to swap a\n * code/token for an app session via the backend.\n *\n * Runs `exchange` once per logical mount and keeps its result. StrictMode's\n * simulated unmount/remount in dev and a parent re-render with inline\n * callbacks used to cancel the only exchange and skip the second, leaving the\n * hook `\"pending\"` forever with the single-use code already spent. Now the\n * options live in a latest-value ref, the effect has no dependencies, and the\n * result is dropped only when the component is still unmounted when it lands.\n * The newest `onSuccess`/`onError` are the ones called.\n *\n * For a provider redirect, the exchange must carry the `state` along with the\n * `code`, or no backend can tell a forged callback from a real one (login\n * CSRF). `exchangeOAuthCallback` does that against a\n * `tempest-fastapi-sdk` backend; `readOAuthCallbackParams` returns both\n * values (or throws) for any other backend.\n *\n * @example\n * function OAuthCallback() {\n * const { loading, error } = useOAuthCallback<LoginResponse>({\n * exchange: () => exchangeOAuthCallback<LoginResponse>(api, \"google\"),\n * onSuccess: ({ user_id, access_token }) => {\n * useAuthStore.getState().setSession({ user: { id: user_id }, token: access_token });\n * navigate(\"/dashboard\", { replace: true });\n * },\n * onError: () => navigate(\"/login?error=oauth\", { replace: true }),\n * });\n *\n * if (loading) return <Spinner />;\n * if (error) return <ErrorState description=\"OAuth falhou\" />;\n * return null;\n * }\n */\nexport function useOAuthCallback<T>(\n options: UseOAuthCallbackOptions<T>,\n): UseOAuthCallbackResult<T> {\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<unknown>(null);\n const [status, setStatus] = useState<\"pending\" | \"success\" | \"error\">(\"pending\");\n const optionsRef = useRef<UseOAuthCallbackOptions<T>>(options);\n const ranRef = useRef<boolean>(false);\n const mountedRef = useRef<boolean>(false);\n\n useEffect(() => {\n optionsRef.current = options;\n });\n\n useEffect(() => {\n mountedRef.current = true;\n if (!ranRef.current) {\n ranRef.current = true;\n optionsRef.current\n .exchange()\n .then((result) => {\n if (!mountedRef.current) return;\n setData(result);\n setStatus(\"success\");\n return optionsRef.current.onSuccess?.(result);\n })\n .catch((err: unknown) => {\n if (!mountedRef.current) return;\n setError(err);\n setStatus(\"error\");\n optionsRef.current.onError?.(err);\n });\n }\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n return { loading: status === \"pending\", data, error, status };\n}\n"],"mappings":"uBAyDA,SAAgB,EACZ,EACyB,CACzB,GAAM,CAAC,EAAM,IAAA,EAAW,EAAA,SAAA,CAAmB,IAAI,EACzC,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAkB,IAAI,EAC1C,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAA0C,SAAS,EACzE,GAAA,EAAa,EAAA,OAAA,CAAmC,CAAO,EACvD,GAAA,EAAS,EAAA,OAAA,CAAgB,EAAK,EAC9B,GAAA,EAAa,EAAA,OAAA,CAAgB,EAAK,EA8BxC,OA5BA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAW,QAAU,CACzB,CAAC,GAED,EAAA,EAAA,UAAA,MACI,EAAW,QAAU,GAChB,EAAO,UACR,EAAO,QAAU,GACjB,EAAW,QACN,SAAS,CAAC,CACV,KAAM,GAAW,CACT,KAAW,QAGhB,OAFA,EAAQ,CAAM,EACd,EAAU,SAAS,EACZ,EAAW,QAAQ,YAAY,CAAM,CAChD,CAAC,CAAC,CACD,MAAO,GAAiB,CAChB,EAAW,UAChB,EAAS,CAAG,EACZ,EAAU,OAAO,EACjB,EAAW,QAAQ,UAAU,CAAG,EACpC,CAAC,OAEI,CACT,EAAW,QAAU,EACzB,GACD,CAAC,CAAC,EAEE,CAAE,QAAS,IAAW,UAAW,OAAM,QAAO,QAAO,CAChE"}
|
|
@@ -1,27 +1,20 @@
|
|
|
1
1
|
import { useEffect as e, useRef as t, useState as n } from "react";
|
|
2
2
|
//#region src/oauth/use-oauth-callback.ts
|
|
3
3
|
function r(r) {
|
|
4
|
-
let
|
|
4
|
+
let [i, a] = n(null), [o, s] = n(null), [c, l] = n("pending"), u = t(r), d = t(!1), f = t(!1);
|
|
5
5
|
return e(() => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
a,
|
|
19
|
-
o
|
|
20
|
-
]), {
|
|
21
|
-
loading: d === "pending",
|
|
22
|
-
data: s,
|
|
23
|
-
error: l,
|
|
24
|
-
status: d
|
|
6
|
+
u.current = r;
|
|
7
|
+
}), e(() => (f.current = !0, d.current || (d.current = !0, u.current.exchange().then((e) => {
|
|
8
|
+
if (f.current) return a(e), l("success"), u.current.onSuccess?.(e);
|
|
9
|
+
}).catch((e) => {
|
|
10
|
+
f.current && (s(e), l("error"), u.current.onError?.(e));
|
|
11
|
+
})), () => {
|
|
12
|
+
f.current = !1;
|
|
13
|
+
}), []), {
|
|
14
|
+
loading: c === "pending",
|
|
15
|
+
data: i,
|
|
16
|
+
error: o,
|
|
17
|
+
status: c
|
|
25
18
|
};
|
|
26
19
|
}
|
|
27
20
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-oauth-callback.js","names":[],"sources":["../../src/oauth/use-oauth-callback.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\n\nexport interface UseOAuthCallbackOptions<T> {\n /** Function that exchanges the provider response for an app session. Called once on mount. */\n exchange: () => Promise<T>;\n /** Fired with the result on success. Receives the value resolved by `exchange`. */\n onSuccess?: (result: T) => void | Promise<void>;\n /** Fired when `exchange` throws or rejects. */\n onError?: (error: unknown) => void;\n}\n\nexport interface UseOAuthCallbackResult<T> {\n /** `true` while the exchange promise is pending. */\n loading: boolean;\n /** Last resolved value, when `status === \"success\"`. */\n data: T | null;\n /** Last rejection reason, when `status === \"error\"`. */\n error: unknown;\n /** Aggregated state — `\"pending\"`, `\"success\"`, or `\"error\"`. */\n status: \"pending\" | \"success\" | \"error\";\n}\n\n/**\n * Run an OAuth-callback \"exchange\" exactly once on mount. Designed for\n * `/callback` routes that receive provider redirects and need to swap a\n * code/token for an app session via the backend.\n *\n *
|
|
1
|
+
{"version":3,"file":"use-oauth-callback.js","names":[],"sources":["../../src/oauth/use-oauth-callback.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\n\nexport interface UseOAuthCallbackOptions<T> {\n /** Function that exchanges the provider response for an app session. Called once on mount. */\n exchange: () => Promise<T>;\n /** Fired with the result on success. Receives the value resolved by `exchange`. */\n onSuccess?: (result: T) => void | Promise<void>;\n /** Fired when `exchange` throws or rejects. */\n onError?: (error: unknown) => void;\n}\n\nexport interface UseOAuthCallbackResult<T> {\n /** `true` while the exchange promise is pending. */\n loading: boolean;\n /** Last resolved value, when `status === \"success\"`. */\n data: T | null;\n /** Last rejection reason, when `status === \"error\"`. */\n error: unknown;\n /** Aggregated state — `\"pending\"`, `\"success\"`, or `\"error\"`. */\n status: \"pending\" | \"success\" | \"error\";\n}\n\n/**\n * Run an OAuth-callback \"exchange\" exactly once on mount. Designed for\n * `/callback` routes that receive provider redirects and need to swap a\n * code/token for an app session via the backend.\n *\n * Runs `exchange` once per logical mount and keeps its result. StrictMode's\n * simulated unmount/remount in dev and a parent re-render with inline\n * callbacks used to cancel the only exchange and skip the second, leaving the\n * hook `\"pending\"` forever with the single-use code already spent. Now the\n * options live in a latest-value ref, the effect has no dependencies, and the\n * result is dropped only when the component is still unmounted when it lands.\n * The newest `onSuccess`/`onError` are the ones called.\n *\n * For a provider redirect, the exchange must carry the `state` along with the\n * `code`, or no backend can tell a forged callback from a real one (login\n * CSRF). `exchangeOAuthCallback` does that against a\n * `tempest-fastapi-sdk` backend; `readOAuthCallbackParams` returns both\n * values (or throws) for any other backend.\n *\n * @example\n * function OAuthCallback() {\n * const { loading, error } = useOAuthCallback<LoginResponse>({\n * exchange: () => exchangeOAuthCallback<LoginResponse>(api, \"google\"),\n * onSuccess: ({ user_id, access_token }) => {\n * useAuthStore.getState().setSession({ user: { id: user_id }, token: access_token });\n * navigate(\"/dashboard\", { replace: true });\n * },\n * onError: () => navigate(\"/login?error=oauth\", { replace: true }),\n * });\n *\n * if (loading) return <Spinner />;\n * if (error) return <ErrorState description=\"OAuth falhou\" />;\n * return null;\n * }\n */\nexport function useOAuthCallback<T>(\n options: UseOAuthCallbackOptions<T>,\n): UseOAuthCallbackResult<T> {\n const [data, setData] = useState<T | null>(null);\n const [error, setError] = useState<unknown>(null);\n const [status, setStatus] = useState<\"pending\" | \"success\" | \"error\">(\"pending\");\n const optionsRef = useRef<UseOAuthCallbackOptions<T>>(options);\n const ranRef = useRef<boolean>(false);\n const mountedRef = useRef<boolean>(false);\n\n useEffect(() => {\n optionsRef.current = options;\n });\n\n useEffect(() => {\n mountedRef.current = true;\n if (!ranRef.current) {\n ranRef.current = true;\n optionsRef.current\n .exchange()\n .then((result) => {\n if (!mountedRef.current) return;\n setData(result);\n setStatus(\"success\");\n return optionsRef.current.onSuccess?.(result);\n })\n .catch((err: unknown) => {\n if (!mountedRef.current) return;\n setError(err);\n setStatus(\"error\");\n optionsRef.current.onError?.(err);\n });\n }\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n return { loading: status === \"pending\", data, error, status };\n}\n"],"mappings":";;AAyDA,SAAgB,EACZ,GACyB;CACzB,IAAM,CAAC,GAAM,KAAW,EAAmB,IAAI,GACzC,CAAC,GAAO,KAAY,EAAkB,IAAI,GAC1C,CAAC,GAAQ,KAAa,EAA0C,SAAS,GACzE,IAAa,EAAmC,CAAO,GACvD,IAAS,EAAgB,EAAK,GAC9B,IAAa,EAAgB,EAAK;CA8BxC,OA5BA,QAAgB;EACZ,EAAW,UAAU;CACzB,CAAC,GAED,SACI,EAAW,UAAU,IAChB,EAAO,YACR,EAAO,UAAU,IACjB,EAAW,QACN,SAAS,CAAC,CACV,MAAM,MAAW;EACT,MAAW,SAGhB,OAFA,EAAQ,CAAM,GACd,EAAU,SAAS,GACZ,EAAW,QAAQ,YAAY,CAAM;CAChD,CAAC,CAAC,CACD,OAAO,MAAiB;EAChB,EAAW,YAChB,EAAS,CAAG,GACZ,EAAU,OAAO,GACjB,EAAW,QAAQ,UAAU,CAAG;CACpC,CAAC,UAEI;EACT,EAAW,UAAU;CACzB,IACD,CAAC,CAAC,GAEE;EAAE,SAAS,MAAW;EAAW;EAAM;EAAO;CAAO;AAChE"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../hooks/use-latest-ref.cjs"),t=require("./utils.cjs"),n=require("./web-push-client.cjs");let r=require("react");function i(i){let a=e.useLatestRef(i),o=(0,r.useMemo)(()=>new n.WebPushClient({vapidPublicKey:i.vapidPublicKey,onSubscribe:e=>a.current.onSubscribe(e),onUnsubscribe:e=>a.current.onUnsubscribe?.(e),getRegistration:i.getRegistration}),[i.vapidPublicKey,i.getRegistration,a]),[s]=(0,r.useState)(()=>t.isPushSupported()),[c,l]=(0,r.useState)(()=>o.permission()),[u,d]=(0,r.useState)(!1),[f,p]=(0,r.useState)(!1),[m,h]=(0,r.useState)(null),g=(0,r.useCallback)(async()=>{if(s){l(o.permission());try{let e=await o.isSubscribed();d(e)}catch(e){h(e instanceof Error?e:Error(String(e)))}}},[o,s]);return(0,r.useEffect)(()=>{g()},[g]),{supported:s,permission:c,subscribed:u,loading:f,error:m,subscribe:(0,r.useCallback)(async()=>{if(s){p(!0),h(null);try{await o.subscribe(),d(!0),l(o.permission())}catch(e){throw h(e instanceof Error?e:Error(String(e))),e}finally{p(!1)}}},[o,s]),unsubscribe:(0,r.useCallback)(async()=>{if(s){p(!0),h(null);try{await o.unsubscribe(),d(!1)}catch(e){throw h(e instanceof Error?e:Error(String(e))),e}finally{p(!1)}}},[o,s]),refresh:g}}exports.usePushSubscription=i;
|
|
1
|
+
const e=require("../hooks/use-latest-ref.cjs"),t=require("./utils.cjs"),n=require("./web-push-client.cjs");let r=require("react");function i(i){let a=e.useLatestRef(i),o=(0,r.useMemo)(()=>new n.WebPushClient({vapidPublicKey:i.vapidPublicKey,onSubscribe:e=>a.current.onSubscribe(e),onUnsubscribe:e=>a.current.onUnsubscribe?.(e),getRegistration:i.getRegistration}),[i.vapidPublicKey,i.getRegistration,a]),[s]=(0,r.useState)(()=>t.isPushSupported()),[c,l]=(0,r.useState)(()=>o.permission()),[u,d]=(0,r.useState)(!1),[f,p]=(0,r.useState)(!1),[m,h]=(0,r.useState)(null),g=(0,r.useCallback)(async()=>{if(s){l(o.permission());try{let e=await o.isSubscribed();d(e)}catch(e){h(e instanceof Error?e:Error(String(e)))}}},[o,s]);return(0,r.useEffect)(()=>{g()},[g]),{supported:s,permission:c,subscribed:u,loading:f,error:m,subscribe:(0,r.useCallback)(async()=>{if(s){p(!0),h(null);try{await o.subscribe(),d(!0),l(o.permission())}catch(e){throw h(e instanceof Error?e:Error(String(e))),e}finally{p(!1)}}},[o,s]),unsubscribe:(0,r.useCallback)(async()=>{if(s){p(!0),h(null);try{await o.unsubscribe(),d(!1)}catch(e){throw e instanceof n.WebPushUnsubscribeError&&d(!e.browserUnsubscribed),h(e instanceof Error?e:Error(String(e))),e}finally{p(!1)}}},[o,s]),refresh:g}}exports.usePushSubscription=i;
|
|
2
2
|
//# sourceMappingURL=use-push-subscription.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-push-subscription.cjs","names":[],"sources":["../../src/push/use-push-subscription.ts"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the ref is read inside deferred callbacks, not during render */\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\nimport {
|
|
1
|
+
{"version":3,"file":"use-push-subscription.cjs","names":[],"sources":["../../src/push/use-push-subscription.ts"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the ref is read inside deferred callbacks, not during render */\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\nimport {\n WebPushClient,\n WebPushUnsubscribeError,\n type WebPushClientConfig,\n} from \"./web-push-client\";\nimport { isPushSupported } from \"./utils\";\n\nexport interface UsePushSubscriptionResult {\n /** True when the browser exposes the Push + Notification APIs. */\n supported: boolean;\n /** Current `Notification.permission` value, or `\"unsupported\"`. */\n permission: NotificationPermission | \"unsupported\";\n /** True when a subscription is active on this device. */\n subscribed: boolean;\n /** True while a subscribe/unsubscribe call is in-flight. */\n loading: boolean;\n /** Last error thrown by subscribe/unsubscribe, if any. */\n error: Error | null;\n subscribe: () => Promise<void>;\n unsubscribe: () => Promise<void>;\n /** Re-read the current subscription/permission state from the browser. */\n refresh: () => Promise<void>;\n}\n\n/**\n * React hook around {@link WebPushClient}. Tracks subscription, permission and\n * loading state; exposes imperative `subscribe`/`unsubscribe` actions.\n *\n * The hook does NOT register the service worker — the host app must do that\n * (e.g. via `vite-plugin-pwa` or a manual `navigator.serviceWorker.register`).\n *\n * @example\n * const push = usePushSubscription({\n * vapidPublicKey: import.meta.env.VITE_VAPID_PUBLIC_KEY,\n * onSubscribe: (sub) => api.post(\"/webpush/subscribe\", { body: sub }),\n * onUnsubscribe: () => api.delete(\"/webpush/my\"),\n * });\n */\nexport function usePushSubscription(config: WebPushClientConfig): UsePushSubscriptionResult {\n const configRef = useLatestRef(config);\n\n const client = useMemo(\n () =>\n new WebPushClient({\n vapidPublicKey: config.vapidPublicKey,\n onSubscribe: (sub) => configRef.current.onSubscribe(sub),\n onUnsubscribe: (sub) => configRef.current.onUnsubscribe?.(sub),\n getRegistration: config.getRegistration,\n }),\n [config.vapidPublicKey, config.getRegistration, configRef],\n );\n\n const [supported] = useState<boolean>(() => isPushSupported());\n const [permission, setPermission] = useState<NotificationPermission | \"unsupported\">(() =>\n client.permission(),\n );\n const [subscribed, setSubscribed] = useState<boolean>(false);\n const [loading, setLoading] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n\n const refresh = useCallback(async () => {\n if (!supported) return;\n setPermission(client.permission());\n try {\n const active = await client.isSubscribed();\n setSubscribed(active);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n }, [client, supported]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n const subscribe = useCallback(async () => {\n if (!supported) return;\n setLoading(true);\n setError(null);\n try {\n await client.subscribe();\n setSubscribed(true);\n setPermission(client.permission());\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n throw err;\n } finally {\n setLoading(false);\n }\n }, [client, supported]);\n\n const unsubscribe = useCallback(async () => {\n if (!supported) return;\n setLoading(true);\n setError(null);\n try {\n await client.unsubscribe();\n setSubscribed(false);\n } catch (err) {\n if (err instanceof WebPushUnsubscribeError) setSubscribed(!err.browserUnsubscribed);\n setError(err instanceof Error ? err : new Error(String(err)));\n throw err;\n } finally {\n setLoading(false);\n }\n }, [client, supported]);\n\n return {\n supported,\n permission,\n subscribed,\n loading,\n error,\n subscribe,\n unsubscribe,\n refresh,\n };\n}\n"],"mappings":"kIAyCA,SAAgB,EAAoB,EAAwD,CACxF,IAAM,EAAY,EAAA,aAAa,CAAM,EAE/B,GAAA,EAAS,EAAA,QAAA,KAEP,IAAI,EAAA,cAAc,CACd,eAAgB,EAAO,eACvB,YAAc,GAAQ,EAAU,QAAQ,YAAY,CAAG,EACvD,cAAgB,GAAQ,EAAU,QAAQ,gBAAgB,CAAG,EAC7D,gBAAiB,EAAO,eAC5B,CAAC,EACL,CAAC,EAAO,eAAgB,EAAO,gBAAiB,CAAS,CAC7D,EAEM,CAAC,IAAA,EAAa,EAAA,SAAA,KAAwB,EAAA,gBAAgB,CAAC,EACvD,CAAC,EAAY,IAAA,EAAiB,EAAA,SAAA,KAChC,EAAO,WAAW,CACtB,EACM,CAAC,EAAY,IAAA,EAAiB,EAAA,SAAA,CAAkB,EAAK,EACrD,CAAC,EAAS,IAAA,EAAc,EAAA,SAAA,CAAkB,EAAK,EAC/C,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAuB,IAAI,EAE/C,GAAA,EAAU,EAAA,YAAA,CAAY,SAAY,CAC/B,KACL,GAAc,EAAO,WAAW,CAAC,EACjC,GAAI,CACA,IAAM,EAAS,MAAM,EAAO,aAAa,EACzC,EAAc,CAAM,CACxB,OAAS,EAAK,CACV,EAAS,aAAe,MAAQ,EAAU,MAAM,OAAO,CAAG,CAAC,CAAC,CAChE,CANiC,CAOrC,EAAG,CAAC,EAAQ,CAAS,CAAC,EAsCtB,OApCA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAa,CACjB,EAAG,CAAC,CAAO,CAAC,EAkCL,CACH,YACA,aACA,aACA,UACA,QACA,WAAA,EAtCc,EAAA,YAAA,CAAY,SAAY,CACjC,KAEL,CADA,EAAW,EAAI,EACf,EAAS,IAAI,EACb,GAAI,CACA,MAAM,EAAO,UAAU,EACvB,EAAc,EAAI,EAClB,EAAc,EAAO,WAAW,CAAC,CACrC,OAAS,EAAK,CAEV,MADA,EAAS,aAAe,MAAQ,EAAU,MAAM,OAAO,CAAG,CAAC,CAAC,EACtD,CACV,QAAU,CACN,EAAW,EAAK,CACpB,CAVa,CAWjB,EAAG,CAAC,EAAQ,CAAS,CAwBjB,EACA,aAAA,EAvBgB,EAAA,YAAA,CAAY,SAAY,CACnC,KAEL,CADA,EAAW,EAAI,EACf,EAAS,IAAI,EACb,GAAI,CACA,MAAM,EAAO,YAAY,EACzB,EAAc,EAAK,CACvB,OAAS,EAAK,CAGV,MAFI,aAAe,EAAA,yBAAyB,EAAc,CAAC,EAAI,mBAAmB,EAClF,EAAS,aAAe,MAAQ,EAAU,MAAM,OAAO,CAAG,CAAC,CAAC,EACtD,CACV,QAAU,CACN,EAAW,EAAK,CACpB,CAVa,CAWjB,EAAG,CAAC,EAAQ,CAAS,CASjB,EACA,SACJ,CACJ"}
|
|
@@ -1,65 +1,65 @@
|
|
|
1
1
|
import { useLatestRef as e } from "../hooks/use-latest-ref.js";
|
|
2
2
|
import { isPushSupported as t } from "./utils.js";
|
|
3
|
-
import { WebPushClient as n } from "./web-push-client.js";
|
|
4
|
-
import { useCallback as
|
|
3
|
+
import { WebPushClient as n, WebPushUnsubscribeError as r } from "./web-push-client.js";
|
|
4
|
+
import { useCallback as i, useEffect as a, useMemo as o, useState as s } from "react";
|
|
5
5
|
//#region src/push/use-push-subscription.ts
|
|
6
|
-
function
|
|
7
|
-
let
|
|
8
|
-
vapidPublicKey:
|
|
9
|
-
onSubscribe: (e) =>
|
|
10
|
-
onUnsubscribe: (e) =>
|
|
11
|
-
getRegistration:
|
|
6
|
+
function c(c) {
|
|
7
|
+
let l = e(c), u = o(() => new n({
|
|
8
|
+
vapidPublicKey: c.vapidPublicKey,
|
|
9
|
+
onSubscribe: (e) => l.current.onSubscribe(e),
|
|
10
|
+
onUnsubscribe: (e) => l.current.onUnsubscribe?.(e),
|
|
11
|
+
getRegistration: c.getRegistration
|
|
12
12
|
}), [
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
]), [
|
|
17
|
-
if (
|
|
18
|
-
|
|
13
|
+
c.vapidPublicKey,
|
|
14
|
+
c.getRegistration,
|
|
15
|
+
l
|
|
16
|
+
]), [d] = s(() => t()), [f, p] = s(() => u.permission()), [m, h] = s(!1), [g, _] = s(!1), [v, y] = s(null), b = i(async () => {
|
|
17
|
+
if (d) {
|
|
18
|
+
p(u.permission());
|
|
19
19
|
try {
|
|
20
|
-
let e = await
|
|
21
|
-
|
|
20
|
+
let e = await u.isSubscribed();
|
|
21
|
+
h(e);
|
|
22
22
|
} catch (e) {
|
|
23
|
-
|
|
23
|
+
y(e instanceof Error ? e : Error(String(e)));
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
}, [
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
}, [
|
|
30
|
-
supported:
|
|
31
|
-
permission:
|
|
32
|
-
subscribed:
|
|
33
|
-
loading:
|
|
34
|
-
error:
|
|
35
|
-
subscribe:
|
|
36
|
-
if (
|
|
37
|
-
|
|
26
|
+
}, [u, d]);
|
|
27
|
+
return a(() => {
|
|
28
|
+
b();
|
|
29
|
+
}, [b]), {
|
|
30
|
+
supported: d,
|
|
31
|
+
permission: f,
|
|
32
|
+
subscribed: m,
|
|
33
|
+
loading: g,
|
|
34
|
+
error: v,
|
|
35
|
+
subscribe: i(async () => {
|
|
36
|
+
if (d) {
|
|
37
|
+
_(!0), y(null);
|
|
38
38
|
try {
|
|
39
|
-
await
|
|
39
|
+
await u.subscribe(), h(!0), p(u.permission());
|
|
40
40
|
} catch (e) {
|
|
41
|
-
throw
|
|
41
|
+
throw y(e instanceof Error ? e : Error(String(e))), e;
|
|
42
42
|
} finally {
|
|
43
|
-
|
|
43
|
+
_(!1);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
}, [
|
|
47
|
-
unsubscribe:
|
|
48
|
-
if (
|
|
49
|
-
|
|
46
|
+
}, [u, d]),
|
|
47
|
+
unsubscribe: i(async () => {
|
|
48
|
+
if (d) {
|
|
49
|
+
_(!0), y(null);
|
|
50
50
|
try {
|
|
51
|
-
await
|
|
51
|
+
await u.unsubscribe(), h(!1);
|
|
52
52
|
} catch (e) {
|
|
53
|
-
throw
|
|
53
|
+
throw e instanceof r && h(!e.browserUnsubscribed), y(e instanceof Error ? e : Error(String(e))), e;
|
|
54
54
|
} finally {
|
|
55
|
-
|
|
55
|
+
_(!1);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
}, [
|
|
59
|
-
refresh:
|
|
58
|
+
}, [u, d]),
|
|
59
|
+
refresh: b
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
//#endregion
|
|
63
|
-
export {
|
|
63
|
+
export { c as usePushSubscription };
|
|
64
64
|
|
|
65
65
|
//# sourceMappingURL=use-push-subscription.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-push-subscription.js","names":[],"sources":["../../src/push/use-push-subscription.ts"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the ref is read inside deferred callbacks, not during render */\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\nimport {
|
|
1
|
+
{"version":3,"file":"use-push-subscription.js","names":[],"sources":["../../src/push/use-push-subscription.ts"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the ref is read inside deferred callbacks, not during render */\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\nimport {\n WebPushClient,\n WebPushUnsubscribeError,\n type WebPushClientConfig,\n} from \"./web-push-client\";\nimport { isPushSupported } from \"./utils\";\n\nexport interface UsePushSubscriptionResult {\n /** True when the browser exposes the Push + Notification APIs. */\n supported: boolean;\n /** Current `Notification.permission` value, or `\"unsupported\"`. */\n permission: NotificationPermission | \"unsupported\";\n /** True when a subscription is active on this device. */\n subscribed: boolean;\n /** True while a subscribe/unsubscribe call is in-flight. */\n loading: boolean;\n /** Last error thrown by subscribe/unsubscribe, if any. */\n error: Error | null;\n subscribe: () => Promise<void>;\n unsubscribe: () => Promise<void>;\n /** Re-read the current subscription/permission state from the browser. */\n refresh: () => Promise<void>;\n}\n\n/**\n * React hook around {@link WebPushClient}. Tracks subscription, permission and\n * loading state; exposes imperative `subscribe`/`unsubscribe` actions.\n *\n * The hook does NOT register the service worker — the host app must do that\n * (e.g. via `vite-plugin-pwa` or a manual `navigator.serviceWorker.register`).\n *\n * @example\n * const push = usePushSubscription({\n * vapidPublicKey: import.meta.env.VITE_VAPID_PUBLIC_KEY,\n * onSubscribe: (sub) => api.post(\"/webpush/subscribe\", { body: sub }),\n * onUnsubscribe: () => api.delete(\"/webpush/my\"),\n * });\n */\nexport function usePushSubscription(config: WebPushClientConfig): UsePushSubscriptionResult {\n const configRef = useLatestRef(config);\n\n const client = useMemo(\n () =>\n new WebPushClient({\n vapidPublicKey: config.vapidPublicKey,\n onSubscribe: (sub) => configRef.current.onSubscribe(sub),\n onUnsubscribe: (sub) => configRef.current.onUnsubscribe?.(sub),\n getRegistration: config.getRegistration,\n }),\n [config.vapidPublicKey, config.getRegistration, configRef],\n );\n\n const [supported] = useState<boolean>(() => isPushSupported());\n const [permission, setPermission] = useState<NotificationPermission | \"unsupported\">(() =>\n client.permission(),\n );\n const [subscribed, setSubscribed] = useState<boolean>(false);\n const [loading, setLoading] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n\n const refresh = useCallback(async () => {\n if (!supported) return;\n setPermission(client.permission());\n try {\n const active = await client.isSubscribed();\n setSubscribed(active);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n }, [client, supported]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n const subscribe = useCallback(async () => {\n if (!supported) return;\n setLoading(true);\n setError(null);\n try {\n await client.subscribe();\n setSubscribed(true);\n setPermission(client.permission());\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n throw err;\n } finally {\n setLoading(false);\n }\n }, [client, supported]);\n\n const unsubscribe = useCallback(async () => {\n if (!supported) return;\n setLoading(true);\n setError(null);\n try {\n await client.unsubscribe();\n setSubscribed(false);\n } catch (err) {\n if (err instanceof WebPushUnsubscribeError) setSubscribed(!err.browserUnsubscribed);\n setError(err instanceof Error ? err : new Error(String(err)));\n throw err;\n } finally {\n setLoading(false);\n }\n }, [client, supported]);\n\n return {\n supported,\n permission,\n subscribed,\n loading,\n error,\n subscribe,\n unsubscribe,\n refresh,\n };\n}\n"],"mappings":";;;;;AAyCA,SAAgB,EAAoB,GAAwD;CACxF,IAAM,IAAY,EAAa,CAAM,GAE/B,IAAS,QAEP,IAAI,EAAc;EACd,gBAAgB,EAAO;EACvB,cAAc,MAAQ,EAAU,QAAQ,YAAY,CAAG;EACvD,gBAAgB,MAAQ,EAAU,QAAQ,gBAAgB,CAAG;EAC7D,iBAAiB,EAAO;CAC5B,CAAC,GACL;EAAC,EAAO;EAAgB,EAAO;EAAiB;CAAS,CAC7D,GAEM,CAAC,KAAa,QAAwB,EAAgB,CAAC,GACvD,CAAC,GAAY,KAAiB,QAChC,EAAO,WAAW,CACtB,GACM,CAAC,GAAY,KAAiB,EAAkB,EAAK,GACrD,CAAC,GAAS,KAAc,EAAkB,EAAK,GAC/C,CAAC,GAAO,KAAY,EAAuB,IAAI,GAE/C,IAAU,EAAY,YAAY;EAC/B,OACL;KAAc,EAAO,WAAW,CAAC;GACjC,IAAI;IACA,IAAM,IAAS,MAAM,EAAO,aAAa;IACzC,EAAc,CAAM;GACxB,SAAS,GAAK;IACV,EAAS,aAAe,QAAQ,IAAU,MAAM,OAAO,CAAG,CAAC,CAAC;GAChE;EANiC;CAOrC,GAAG,CAAC,GAAQ,CAAS,CAAC;CAsCtB,OApCA,QAAgB;EACZ,EAAa;CACjB,GAAG,CAAC,CAAO,CAAC,GAkCL;EACH;EACA;EACA;EACA;EACA;EACA,WAtCc,EAAY,YAAY;GACjC,OAEL;IADA,EAAW,EAAI,GACf,EAAS,IAAI;IACb,IAAI;KAGA,AAFA,MAAM,EAAO,UAAU,GACvB,EAAc,EAAI,GAClB,EAAc,EAAO,WAAW,CAAC;IACrC,SAAS,GAAK;KAEV,MADA,EAAS,aAAe,QAAQ,IAAU,MAAM,OAAO,CAAG,CAAC,CAAC,GACtD;IACV,UAAU;KACN,EAAW,EAAK;IACpB;GAVa;EAWjB,GAAG,CAAC,GAAQ,CAAS,CAwBjB;EACA,aAvBgB,EAAY,YAAY;GACnC,OAEL;IADA,EAAW,EAAI,GACf,EAAS,IAAI;IACb,IAAI;KAEA,AADA,MAAM,EAAO,YAAY,GACzB,EAAc,EAAK;IACvB,SAAS,GAAK;KAGV,MAFI,aAAe,KAAyB,EAAc,CAAC,EAAI,mBAAmB,GAClF,EAAS,aAAe,QAAQ,IAAU,MAAM,OAAO,CAAG,CAAC,CAAC,GACtD;IACV,UAAU;KACN,EAAW,EAAK;IACpB;GAVa;EAWjB,GAAG,CAAC,GAAQ,CAAS,CASjB;EACA;CACJ;AACJ"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./utils.cjs");var t=class extends Error{constructor(){super(`Web Push não suportado neste navegador.`),this.name=`WebPushUnsupportedError`}},n=class extends Error{constructor(){super(`Permissão de notificação negada pelo usuário.`),this.name=`WebPushPermissionDeniedError`}},r=class{config;constructor(e){this.config=e}static isSupported(){return e.isPushSupported()}async registration(){if(!e.isPushSupported())throw new t;return this.config.getRegistration?await this.config.getRegistration():await navigator.serviceWorker.ready}permission(){return typeof Notification>`u`?`unsupported`:Notification.permission}async requestPermission(){if(typeof Notification>`u`)throw new t;return Notification.requestPermission()}async getSubscription(){return(await this.registration()).pushManager.getSubscription()}async isSubscribed(){return e.isPushSupported()?await this.getSubscription()!==null:!1}async subscribe(){if(await this.requestPermission()!==`granted`)throw new n;let t=await this.registration(),r=await t.pushManager.getSubscription();if(r)return await this.config.onSubscribe(r.toJSON()),r;let i=await t.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:e.urlBase64ToUint8Array(this.config.vapidPublicKey)});return await this.config.onSubscribe(i.toJSON()),i}async unsubscribe(){let e=await this.getSubscription();return e
|
|
1
|
+
const e=require("./utils.cjs");var t=class extends Error{constructor(){super(`Web Push não suportado neste navegador.`),this.name=`WebPushUnsupportedError`}},n=class extends Error{constructor(){super(`Permissão de notificação negada pelo usuário.`),this.name=`WebPushPermissionDeniedError`}},r=class extends Error{subscription;browserUnsubscribed;constructor(e,t,n){super(`Falha ao remover a inscrição de push no backend.`,{cause:n}),this.name=`WebPushUnsubscribeError`,this.subscription=e,this.browserUnsubscribed=t}},i=class{config;constructor(e){this.config=e}static isSupported(){return e.isPushSupported()}async registration(){if(!e.isPushSupported())throw new t;return this.config.getRegistration?await this.config.getRegistration():await navigator.serviceWorker.ready}permission(){return typeof Notification>`u`?`unsupported`:Notification.permission}async requestPermission(){if(typeof Notification>`u`)throw new t;return Notification.requestPermission()}async getSubscription(){return(await this.registration()).pushManager.getSubscription()}async isSubscribed(){return e.isPushSupported()?await this.getSubscription()!==null:!1}async subscribe(){if(await this.requestPermission()!==`granted`)throw new n;let t=await this.registration(),r=await t.pushManager.getSubscription();if(r)return await this.config.onSubscribe(r.toJSON()),r;let i=await t.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:e.urlBase64ToUint8Array(this.config.vapidPublicKey)});return await this.config.onSubscribe(i.toJSON()),i}async unsubscribe(){let e=await this.getSubscription();if(!e)return!1;let t=e.toJSON(),n=null;if(this.config.onUnsubscribe)try{await this.config.onUnsubscribe(t)}catch(e){n={cause:e}}if(!n)return e.unsubscribe();let i;try{i=await e.unsubscribe()}catch(e){throw new r(t,!1,AggregateError([n.cause,e],`Backend delete and browser unsubscribe both failed.`))}throw new r(t,i,n.cause)}};exports.WebPushClient=i,exports.WebPushPermissionDeniedError=n,exports.WebPushUnsubscribeError=r,exports.WebPushUnsupportedError=t;
|
|
2
2
|
//# sourceMappingURL=web-push-client.cjs.map
|