@swoff/cli 0.4.1 → 0.4.4
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/dist/lib/commands/generate-guide.js +15 -15
- package/dist/lib/commands/generate-guide.js.map +1 -1
- package/dist/lib/commands/init.js +52 -6
- package/dist/lib/commands/init.js.map +1 -1
- package/dist/lib/config/validator.js +1 -1
- package/dist/lib/config/validator.js.map +1 -1
- package/dist/lib/generators/file-generators/client-injector-bundle.js +2 -1
- package/dist/lib/generators/file-generators/client-injector-bundle.js.map +1 -1
- package/dist/lib/generators/file-generators/client-injector.js +2 -1
- package/dist/lib/generators/file-generators/client-injector.js.map +1 -1
- package/dist/lib/generators/file-generators/context.js +4 -1
- package/dist/lib/generators/file-generators/context.js.map +1 -1
- package/dist/lib/generators/file-generators/generate-framework-adapters.js +28 -22
- package/dist/lib/generators/file-generators/generate-framework-adapters.js.map +1 -1
- package/dist/lib/generators/sw-sections/activate-handler.js +1 -5
- package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
- package/dist/lib/generators/sw-sections/background-precache.js +8 -8
- package/dist/lib/generators/sw-sections/batch-refresh-queue.js +2 -5
- package/dist/lib/generators/sw-sections/batch-refresh-queue.js.map +1 -1
- package/dist/lib/generators/sw-sections/fetch-handler.js +14 -3
- package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
- package/dist/lib/shared/config-types.js +1 -0
- package/dist/lib/shared/config-types.js.map +1 -1
- package/dist/lib/utils/detect-framework.js +16 -0
- package/dist/lib/utils/detect-framework.js.map +1 -1
- package/dist/runtime/client-injector-bundle.js +2 -2
- package/dist/runtime/client-injector-bundle.js.map +1 -1
- package/dist/runtime/client-injector.js +3 -2
- package/dist/runtime/client-injector.js.map +1 -1
- package/dist/runtime/gql-wrapper.js +3 -1
- package/dist/runtime/gql-wrapper.js.map +1 -1
- package/dist/runtime/swoff-api-bundle.js +2 -1
- package/dist/runtime/swoff-api-bundle.js.map +1 -1
- package/package.json +1 -1
- package/templates/react/{useOfflineAnalytics.jsx → useSwoffAnalytics.jsx} +3 -3
- package/templates/react/{useOfflineAnalytics.tsx → useSwoffAnalytics.tsx} +3 -3
- package/templates/react/{useAuth.jsx → useSwoffAuth.jsx} +2 -2
- package/templates/react/{useAuth.tsx → useSwoffAuth.tsx} +2 -2
- package/templates/react/{useCachedFetch.jsx → useSwoffFetch.jsx} +6 -6
- package/templates/react/{useCachedFetch.tsx → useSwoffFetch.tsx} +7 -7
- package/templates/react/{useMutation.jsx → useSwoffMutation.jsx} +3 -3
- package/templates/react/{useMutation.tsx → useSwoffMutation.tsx} +3 -3
- package/templates/react/{useMutationState.jsx → useSwoffMutationState.jsx} +1 -1
- package/templates/react/{useMutationState.tsx → useSwoffMutationState.tsx} +2 -2
- package/templates/react/{useNetworkStatus.jsx → useSwoffNetwork.jsx} +2 -2
- package/templates/react/{useNetworkStatus.tsx → useSwoffNetwork.tsx} +2 -2
- package/templates/react/{usePrecacheProgress.jsx → useSwoffPrecache.jsx} +4 -7
- package/templates/react/{usePrecacheProgress.tsx → useSwoffPrecache.tsx} +4 -7
- package/templates/react/{usePrefetch.jsx → useSwoffPrefetch.jsx} +2 -2
- package/templates/react/{usePrefetch.tsx → useSwoffPrefetch.tsx} +2 -2
- package/templates/react/{usePushSubscription.jsx → useSwoffPush.jsx} +1 -1
- package/templates/react/{usePushSubscription.tsx → useSwoffPush.tsx} +1 -1
- package/templates/react/{usePwaInstall.jsx → useSwoffPwa.jsx} +1 -1
- package/templates/react/{usePwaInstall.tsx → useSwoffPwa.tsx} +1 -1
- package/templates/react/{useMutationQueue.jsx → useSwoffQueue.jsx} +2 -2
- package/templates/react/{useMutationQueue.tsx → useSwoffQueue.tsx} +2 -2
- package/templates/react/{useStorageEstimate.jsx → useSwoffStorage.jsx} +2 -2
- package/templates/react/{useStorageEstimate.tsx → useSwoffStorage.tsx} +2 -2
- package/templates/react/{useBackgroundSync.jsx → useSwoffSync.jsx} +1 -1
- package/templates/react/{useBackgroundSync.tsx → useSwoffSync.tsx} +1 -1
- package/templates/svelte/useSwoffAnalytics.js +31 -0
- package/templates/svelte/useSwoffAnalytics.ts +37 -0
- package/templates/svelte/useSwoffAuth.js +95 -0
- package/templates/svelte/useSwoffAuth.ts +96 -0
- package/templates/svelte/useSwoffFetch.js +97 -0
- package/templates/svelte/useSwoffFetch.ts +112 -0
- package/templates/svelte/useSwoffMutation.js +110 -0
- package/templates/svelte/useSwoffMutation.ts +141 -0
- package/templates/svelte/useSwoffMutationState.js +27 -0
- package/templates/svelte/useSwoffMutationState.ts +28 -0
- package/templates/svelte/useSwoffNetwork.js +75 -0
- package/templates/svelte/useSwoffNetwork.ts +75 -0
- package/templates/svelte/useSwoffPrecache.js +19 -0
- package/templates/svelte/useSwoffPrecache.ts +19 -0
- package/templates/svelte/useSwoffPrefetch.js +19 -0
- package/templates/svelte/useSwoffPrefetch.ts +20 -0
- package/templates/svelte/useSwoffPush.js +82 -0
- package/templates/svelte/useSwoffPush.ts +82 -0
- package/templates/svelte/useSwoffPwa.js +27 -0
- package/templates/svelte/useSwoffPwa.ts +27 -0
- package/templates/svelte/useSwoffQueue.js +57 -0
- package/templates/svelte/useSwoffQueue.ts +67 -0
- package/templates/svelte/useSwoffReset.js +21 -0
- package/templates/svelte/useSwoffReset.ts +32 -0
- package/templates/svelte/useSwoffStorage.js +47 -0
- package/templates/svelte/useSwoffStorage.ts +47 -0
- package/templates/svelte/useSwoffSync.js +31 -0
- package/templates/svelte/useSwoffSync.ts +31 -0
- package/templates/vue/useSwoffAnalytics.js +46 -0
- package/templates/vue/useSwoffAnalytics.ts +54 -0
- package/templates/vue/useSwoffAuth.js +106 -0
- package/templates/vue/useSwoffAuth.ts +118 -0
- package/templates/vue/useSwoffFetch.js +127 -0
- package/templates/vue/useSwoffFetch.ts +143 -0
- package/templates/vue/useSwoffMutation.js +124 -0
- package/templates/vue/useSwoffMutation.ts +155 -0
- package/templates/vue/useSwoffMutationState.js +35 -0
- package/templates/vue/useSwoffMutationState.ts +36 -0
- package/templates/vue/useSwoffNetwork.js +86 -0
- package/templates/vue/useSwoffNetwork.ts +89 -0
- package/templates/vue/useSwoffPrecache.js +19 -0
- package/templates/vue/useSwoffPrecache.ts +19 -0
- package/templates/vue/useSwoffPrefetch.js +33 -0
- package/templates/vue/useSwoffPrefetch.ts +41 -0
- package/templates/vue/useSwoffPush.js +81 -0
- package/templates/vue/useSwoffPush.ts +81 -0
- package/templates/vue/useSwoffPwa.js +27 -0
- package/templates/vue/useSwoffPwa.ts +27 -0
- package/templates/vue/useSwoffQueue.js +70 -0
- package/templates/vue/useSwoffQueue.ts +82 -0
- package/templates/vue/useSwoffReset.js +32 -0
- package/templates/vue/useSwoffReset.ts +39 -0
- package/templates/vue/useSwoffStorage.js +56 -0
- package/templates/vue/useSwoffStorage.ts +57 -0
- package/templates/vue/useSwoffSync.js +30 -0
- package/templates/vue/useSwoffSync.ts +30 -0
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { useState, useEffect } from "react";
|
|
2
2
|
|
|
3
|
-
export function
|
|
4
|
-
const [
|
|
5
|
-
status: "idle" as "idle" | "installing",
|
|
6
|
-
progress: 0,
|
|
7
|
-
}));
|
|
3
|
+
export function useSwoffPrecache() {
|
|
4
|
+
const [progress, setProgress] = useState(0);
|
|
8
5
|
|
|
9
6
|
useEffect(() => {
|
|
10
7
|
const onProgress = (e: WindowEventMap["sw-progress"]) =>
|
|
11
|
-
|
|
8
|
+
setProgress(e.detail.percent);
|
|
12
9
|
|
|
13
10
|
window.addEventListener("sw-progress", onProgress);
|
|
14
11
|
return () => {
|
|
@@ -16,5 +13,5 @@ export function usePrecacheProgress() {
|
|
|
16
13
|
};
|
|
17
14
|
}, []);
|
|
18
15
|
|
|
19
|
-
return
|
|
16
|
+
return { progress };
|
|
20
17
|
}
|
|
@@ -5,7 +5,7 @@ import { prefetchCache } from "../fetch/core.js";
|
|
|
5
5
|
* Hook that returns a stable prefetch callback, observable prefetch list, and clear function.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
|
-
* const { prefetch, prefetchList, clear } =
|
|
8
|
+
* const { prefetch, prefetchList, clear } = useSwoffPrefetch();
|
|
9
9
|
*
|
|
10
10
|
* // Prefetch when user hovers a link:
|
|
11
11
|
* <Link onMouseEnter={() => prefetch("/api/todos")} to="/todos" />
|
|
@@ -16,7 +16,7 @@ import { prefetchCache } from "../fetch/core.js";
|
|
|
16
16
|
* // Clear prefetched data:
|
|
17
17
|
* <button onClick={clear}>Clear prefetch</button>
|
|
18
18
|
*/
|
|
19
|
-
export function
|
|
19
|
+
export function useSwoffPrefetch() {
|
|
20
20
|
const [prefetchList, setPrefetchList] = useState([]);
|
|
21
21
|
|
|
22
22
|
const prefetch = useCallback((url, options) => {
|
|
@@ -6,7 +6,7 @@ import type { FetchWithCacheOptions } from "../fetch/core.ts";
|
|
|
6
6
|
* Hook that returns a stable prefetch callback, observable prefetch list, and clear function.
|
|
7
7
|
*
|
|
8
8
|
* Usage:
|
|
9
|
-
* const { prefetch, prefetchList, clear } =
|
|
9
|
+
* const { prefetch, prefetchList, clear } = useSwoffPrefetch();
|
|
10
10
|
*
|
|
11
11
|
* // Prefetch when user hovers a link:
|
|
12
12
|
* <Link onMouseEnter={() => prefetch("/api/todos")} to="/todos" />
|
|
@@ -19,7 +19,7 @@ import type { FetchWithCacheOptions } from "../fetch/core.ts";
|
|
|
19
19
|
*
|
|
20
20
|
* @returns {{ prefetch: (url: string, options?: FetchWithCacheOptions) => void, prefetchList: string[], clear: () => void }}
|
|
21
21
|
*/
|
|
22
|
-
export function
|
|
22
|
+
export function useSwoffPrefetch(): {
|
|
23
23
|
prefetch: (url: string, options?: FetchWithCacheOptions) => void;
|
|
24
24
|
prefetchList: string[];
|
|
25
25
|
clear: () => void;
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getPushSubscription,
|
|
7
7
|
} from "../realtime/notifications.ts";
|
|
8
8
|
|
|
9
|
-
export function
|
|
9
|
+
export function useSwoffPush() {
|
|
10
10
|
const [state, setState] = useState(() => ({
|
|
11
11
|
subscribed: false,
|
|
12
12
|
subscription: null as PushSubscription | null,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback } from "react";
|
|
2
2
|
import { isInstallable, promptInstall } from "../pwa/prompt.js";
|
|
3
3
|
|
|
4
|
-
export function
|
|
4
|
+
export function useSwoffPwa() {
|
|
5
5
|
const [canInstall, setCanInstall] = useState(false);
|
|
6
6
|
|
|
7
7
|
useEffect(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback } from "react";
|
|
2
2
|
import { isInstallable, promptInstall } from "../pwa/prompt.ts";
|
|
3
3
|
|
|
4
|
-
export function
|
|
4
|
+
export function useSwoffPwa() {
|
|
5
5
|
const [canInstall, setCanInstall] = useState(false);
|
|
6
6
|
|
|
7
7
|
useEffect(() => {
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
* Reactive mutation queue state: pending count, items, last sync info, and processing flag.
|
|
10
10
|
*
|
|
11
11
|
* Usage:
|
|
12
|
-
* const { pending, items, lastSync, isProcessing, retryAll } =
|
|
12
|
+
* const { pending, items, lastSync, isProcessing, retryAll } = useSwoffQueue();
|
|
13
13
|
*
|
|
14
14
|
* // Show pending badge:
|
|
15
15
|
* {pending > 0 && <Badge>{pending}</Badge>}
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
* {isProcessing ? "Syncing..." : "Retry All"}
|
|
20
20
|
* </button>
|
|
21
21
|
*/
|
|
22
|
-
export function
|
|
22
|
+
export function useSwoffQueue() {
|
|
23
23
|
const [state, setState] = useState({
|
|
24
24
|
pending: 0,
|
|
25
25
|
items: [],
|
|
@@ -17,7 +17,7 @@ export interface MutationQueueState {
|
|
|
17
17
|
* Reactive mutation queue state: pending count, items, last sync info, and processing flag.
|
|
18
18
|
*
|
|
19
19
|
* Usage:
|
|
20
|
-
* const { pending, items, lastSync, isProcessing, retryAll } =
|
|
20
|
+
* const { pending, items, lastSync, isProcessing, retryAll } = useSwoffQueue();
|
|
21
21
|
*
|
|
22
22
|
* // Show pending badge:
|
|
23
23
|
* {pending > 0 && <Badge>{pending}</Badge>}
|
|
@@ -29,7 +29,7 @@ export interface MutationQueueState {
|
|
|
29
29
|
*
|
|
30
30
|
* @returns {{ pending, items, lastSync, isProcessing, retryAll }}
|
|
31
31
|
*/
|
|
32
|
-
export function
|
|
32
|
+
export function useSwoffQueue(): MutationQueueState & {
|
|
33
33
|
retryAll: () => Promise<void>;
|
|
34
34
|
} {
|
|
35
35
|
const [state, setState] = useState<MutationQueueState>({
|
|
@@ -5,13 +5,13 @@ import { getStorageEstimate, formatBytes } from "../storage";
|
|
|
5
5
|
* Reactive storage estimate. Shows available quota and usage.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
|
-
* const { usage, quota, percentUsed, formattedUsage, formattedQuota, loading } =
|
|
8
|
+
* const { usage, quota, percentUsed, formattedUsage, formattedQuota, loading } = useSwoffStorage();
|
|
9
9
|
*
|
|
10
10
|
* if (!loading && percentUsed > 80) return <StorageWarning />;
|
|
11
11
|
*
|
|
12
12
|
* @param autoRefresh - Re-check on visibility change (default true)
|
|
13
13
|
*/
|
|
14
|
-
export function
|
|
14
|
+
export function useSwoffStorage(autoRefresh = true) {
|
|
15
15
|
const [state, setState] = useState({
|
|
16
16
|
usage: 0,
|
|
17
17
|
quota: 0,
|
|
@@ -15,14 +15,14 @@ interface StorageEstimateState {
|
|
|
15
15
|
* Reactive storage estimate. Shows available quota and usage.
|
|
16
16
|
*
|
|
17
17
|
* Usage:
|
|
18
|
-
* const { usage, quota, percentUsed, formattedUsage, formattedQuota, loading } =
|
|
18
|
+
* const { usage, quota, percentUsed, formattedUsage, formattedQuota, loading } = useSwoffStorage();
|
|
19
19
|
*
|
|
20
20
|
* if (!loading && percentUsed > 80) return <StorageWarning />;
|
|
21
21
|
*
|
|
22
22
|
* @param autoRefresh - Re-check on visibility change (default true)
|
|
23
23
|
* @returns {{ usage: number, quota: number, percentUsed: number, formattedUsage: string, formattedQuota: string, loading: boolean, error: string | null }}
|
|
24
24
|
*/
|
|
25
|
-
export function
|
|
25
|
+
export function useSwoffStorage(autoRefresh = true): StorageEstimateState {
|
|
26
26
|
const [state, setState] = useState<StorageEstimateState>({
|
|
27
27
|
usage: 0,
|
|
28
28
|
quota: 0,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback } from "react";
|
|
2
2
|
import { retrySync } from "../mutation/sync.js";
|
|
3
3
|
|
|
4
|
-
export function
|
|
4
|
+
export function useSwoffSync() {
|
|
5
5
|
const [state, setState] = useState(() => ({
|
|
6
6
|
supported:
|
|
7
7
|
typeof window !== "undefined" && typeof navigator !== "undefined"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback } from "react";
|
|
2
2
|
import { retrySync } from "../mutation/sync.ts";
|
|
3
3
|
|
|
4
|
-
export function
|
|
4
|
+
export function useSwoffSync() {
|
|
5
5
|
const [state, setState] = useState(() => ({
|
|
6
6
|
supported:
|
|
7
7
|
typeof window !== "undefined" && typeof navigator !== "undefined"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { writable } from "svelte/store";
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
|
|
4
|
+
export function useSwoffAnalytics(callback) {
|
|
5
|
+
const events = writable([]);
|
|
6
|
+
const lastEvent = writable(null);
|
|
7
|
+
|
|
8
|
+
function handler(e) {
|
|
9
|
+
if (e.data?.type === "OFFLINE_FALLBACK_ACTIVATED" && e.data?.detail) {
|
|
10
|
+
const detail = e.data.detail;
|
|
11
|
+
events.update((prev) => [...prev, detail]);
|
|
12
|
+
lastEvent.set(detail);
|
|
13
|
+
callback?.(detail);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
onMount(() => {
|
|
18
|
+
if (typeof navigator === "undefined" || !navigator.serviceWorker) return;
|
|
19
|
+
navigator.serviceWorker.addEventListener("message", handler);
|
|
20
|
+
return () => {
|
|
21
|
+
navigator.serviceWorker.removeEventListener("message", handler);
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function clear() {
|
|
26
|
+
events.set([]);
|
|
27
|
+
lastEvent.set(null);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { lastEvent, events, clear };
|
|
31
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { writable } from "svelte/store";
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
|
|
4
|
+
export interface OfflineFallbackEvent {
|
|
5
|
+
route: string;
|
|
6
|
+
fallbackLevel: "route-fallback" | "offline-page" | "spa-shell" | "inline-503";
|
|
7
|
+
timestamp: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function useSwoffAnalytics(callback?: (event: OfflineFallbackEvent) => void) {
|
|
11
|
+
const events = writable<OfflineFallbackEvent[]>([]);
|
|
12
|
+
const lastEvent = writable<OfflineFallbackEvent | null>(null);
|
|
13
|
+
|
|
14
|
+
function handler(e: MessageEvent) {
|
|
15
|
+
if (e.data?.type === "OFFLINE_FALLBACK_ACTIVATED" && e.data?.detail) {
|
|
16
|
+
const detail = e.data.detail as OfflineFallbackEvent;
|
|
17
|
+
events.update((prev) => [...prev, detail]);
|
|
18
|
+
lastEvent.set(detail);
|
|
19
|
+
callback?.(detail);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
onMount(() => {
|
|
24
|
+
if (typeof navigator === "undefined" || !navigator.serviceWorker) return;
|
|
25
|
+
navigator.serviceWorker.addEventListener("message", handler);
|
|
26
|
+
return () => {
|
|
27
|
+
navigator.serviceWorker.removeEventListener("message", handler);
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
function clear() {
|
|
32
|
+
events.set([]);
|
|
33
|
+
lastEvent.set(null);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { lastEvent, events, clear };
|
|
37
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { writable } from "svelte/store";
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
import { getAuthState } from "../auth/state.js";
|
|
4
|
+
import { setAuth, clearAuth, ensureValidAuth } from "../auth/store.js";
|
|
5
|
+
|
|
6
|
+
export function useSwoffAuth() {
|
|
7
|
+
const authenticated = writable(false);
|
|
8
|
+
const auth = writable(null);
|
|
9
|
+
const online = writable(
|
|
10
|
+
typeof navigator !== "undefined" ? navigator.onLine : true,
|
|
11
|
+
);
|
|
12
|
+
const isLoading = writable(false);
|
|
13
|
+
const error = writable(null);
|
|
14
|
+
|
|
15
|
+
async function refreshState() {
|
|
16
|
+
try {
|
|
17
|
+
const authState = await getAuthState();
|
|
18
|
+
authenticated.set(authState.authenticated);
|
|
19
|
+
auth.set(authState.auth);
|
|
20
|
+
error.set(null);
|
|
21
|
+
} catch {
|
|
22
|
+
// ignore — auth not initialized
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
onMount(() => {
|
|
27
|
+
refreshState();
|
|
28
|
+
|
|
29
|
+
function onOnline() { online.set(true); }
|
|
30
|
+
function onOffline() { online.set(false); }
|
|
31
|
+
function onAuthChange() { refreshState(); }
|
|
32
|
+
|
|
33
|
+
window.addEventListener("online", onOnline);
|
|
34
|
+
window.addEventListener("offline", onOffline);
|
|
35
|
+
window.addEventListener("sw-auth-state-change", onAuthChange);
|
|
36
|
+
|
|
37
|
+
return () => {
|
|
38
|
+
window.removeEventListener("online", onOnline);
|
|
39
|
+
window.removeEventListener("offline", onOffline);
|
|
40
|
+
window.removeEventListener("sw-auth-state-change", onAuthChange);
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
async function doSetAuth(authData) {
|
|
45
|
+
isLoading.set(true);
|
|
46
|
+
error.set(null);
|
|
47
|
+
try {
|
|
48
|
+
await setAuth(authData);
|
|
49
|
+
await refreshState();
|
|
50
|
+
} catch (err) {
|
|
51
|
+
error.set(err instanceof Error ? err : new Error(String(err)));
|
|
52
|
+
} finally {
|
|
53
|
+
isLoading.set(false);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function doClearAuth() {
|
|
58
|
+
isLoading.set(true);
|
|
59
|
+
error.set(null);
|
|
60
|
+
try {
|
|
61
|
+
await clearAuth();
|
|
62
|
+
await refreshState();
|
|
63
|
+
} catch (err) {
|
|
64
|
+
error.set(err instanceof Error ? err : new Error(String(err)));
|
|
65
|
+
} finally {
|
|
66
|
+
isLoading.set(false);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function doEnsureValid() {
|
|
71
|
+
isLoading.set(true);
|
|
72
|
+
error.set(null);
|
|
73
|
+
try {
|
|
74
|
+
const result = await ensureValidAuth();
|
|
75
|
+
await refreshState();
|
|
76
|
+
return result;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
error.set(err instanceof Error ? err : new Error(String(err)));
|
|
79
|
+
return null;
|
|
80
|
+
} finally {
|
|
81
|
+
isLoading.set(false);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
authenticated,
|
|
87
|
+
auth,
|
|
88
|
+
online,
|
|
89
|
+
isLoading,
|
|
90
|
+
error,
|
|
91
|
+
setAuth: doSetAuth,
|
|
92
|
+
clearAuth: doClearAuth,
|
|
93
|
+
ensureValid: doEnsureValid,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { writable } from "svelte/store";
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
import { getAuthState } from "../auth/state";
|
|
4
|
+
import { setAuth, clearAuth, ensureValidAuth } from "../auth/store";
|
|
5
|
+
import type { AuthData } from "../auth/adapter";
|
|
6
|
+
|
|
7
|
+
export function useSwoffAuth() {
|
|
8
|
+
const authenticated = writable(false);
|
|
9
|
+
const auth = writable<AuthData | null>(null);
|
|
10
|
+
const online = writable(
|
|
11
|
+
typeof navigator !== "undefined" ? navigator.onLine : true,
|
|
12
|
+
);
|
|
13
|
+
const isLoading = writable(false);
|
|
14
|
+
const error = writable<Error | null>(null);
|
|
15
|
+
|
|
16
|
+
async function refreshState() {
|
|
17
|
+
try {
|
|
18
|
+
const authState = await getAuthState();
|
|
19
|
+
authenticated.set(authState.authenticated);
|
|
20
|
+
auth.set(authState.auth);
|
|
21
|
+
error.set(null);
|
|
22
|
+
} catch {
|
|
23
|
+
// ignore — auth not initialized
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
onMount(() => {
|
|
28
|
+
refreshState();
|
|
29
|
+
|
|
30
|
+
function onOnline() { online.set(true); }
|
|
31
|
+
function onOffline() { online.set(false); }
|
|
32
|
+
function onAuthChange() { refreshState(); }
|
|
33
|
+
|
|
34
|
+
window.addEventListener("online", onOnline);
|
|
35
|
+
window.addEventListener("offline", onOffline);
|
|
36
|
+
window.addEventListener("sw-auth-state-change", onAuthChange);
|
|
37
|
+
|
|
38
|
+
return () => {
|
|
39
|
+
window.removeEventListener("online", onOnline);
|
|
40
|
+
window.removeEventListener("offline", onOffline);
|
|
41
|
+
window.removeEventListener("sw-auth-state-change", onAuthChange);
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
async function doSetAuth(authData: AuthData) {
|
|
46
|
+
isLoading.set(true);
|
|
47
|
+
error.set(null);
|
|
48
|
+
try {
|
|
49
|
+
await setAuth(authData);
|
|
50
|
+
await refreshState();
|
|
51
|
+
} catch (err) {
|
|
52
|
+
error.set(err instanceof Error ? err : new Error(String(err)));
|
|
53
|
+
} finally {
|
|
54
|
+
isLoading.set(false);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function doClearAuth() {
|
|
59
|
+
isLoading.set(true);
|
|
60
|
+
error.set(null);
|
|
61
|
+
try {
|
|
62
|
+
await clearAuth();
|
|
63
|
+
await refreshState();
|
|
64
|
+
} catch (err) {
|
|
65
|
+
error.set(err instanceof Error ? err : new Error(String(err)));
|
|
66
|
+
} finally {
|
|
67
|
+
isLoading.set(false);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function doEnsureValid(): Promise<AuthData | null> {
|
|
72
|
+
isLoading.set(true);
|
|
73
|
+
error.set(null);
|
|
74
|
+
try {
|
|
75
|
+
const result = await ensureValidAuth();
|
|
76
|
+
await refreshState();
|
|
77
|
+
return result;
|
|
78
|
+
} catch (err) {
|
|
79
|
+
error.set(err instanceof Error ? err : new Error(String(err)));
|
|
80
|
+
return null;
|
|
81
|
+
} finally {
|
|
82
|
+
isLoading.set(false);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
authenticated,
|
|
88
|
+
auth,
|
|
89
|
+
online,
|
|
90
|
+
isLoading,
|
|
91
|
+
error,
|
|
92
|
+
setAuth: doSetAuth,
|
|
93
|
+
clearAuth: doClearAuth,
|
|
94
|
+
ensureValid: doEnsureValid,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { writable } from "svelte/store";
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
import { fetchWithCache } from "../fetch/core.js";
|
|
4
|
+
import { generateTags } from "../cache/tags.js";
|
|
5
|
+
|
|
6
|
+
export function useSwoffFetch(url, options = {}) {
|
|
7
|
+
const {
|
|
8
|
+
select,
|
|
9
|
+
retry: retryOpt,
|
|
10
|
+
placeholderData,
|
|
11
|
+
onSuccess,
|
|
12
|
+
onError,
|
|
13
|
+
...fetchOptions
|
|
14
|
+
} = options;
|
|
15
|
+
|
|
16
|
+
const data = writable(placeholderData ?? null);
|
|
17
|
+
const error = writable(null);
|
|
18
|
+
const loading = writable(url != null && options.enabled !== false);
|
|
19
|
+
|
|
20
|
+
const isEnabled = options.enabled !== false && url != null;
|
|
21
|
+
let refetchCount = 0;
|
|
22
|
+
|
|
23
|
+
function refetch() {
|
|
24
|
+
refetchCount++;
|
|
25
|
+
doFetch();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function doFetch() {
|
|
29
|
+
if (!isEnabled) return;
|
|
30
|
+
const currentFetchId = refetchCount;
|
|
31
|
+
|
|
32
|
+
let retriesLeft =
|
|
33
|
+
retryOpt === true
|
|
34
|
+
? Infinity
|
|
35
|
+
: typeof retryOpt === "number"
|
|
36
|
+
? retryOpt
|
|
37
|
+
: 0;
|
|
38
|
+
|
|
39
|
+
loading.set(true);
|
|
40
|
+
try {
|
|
41
|
+
const { response } = await fetchWithCache(url, fetchOptions);
|
|
42
|
+
if (currentFetchId !== refetchCount) return;
|
|
43
|
+
if (response) {
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
46
|
+
try {
|
|
47
|
+
const errBody = await response.json();
|
|
48
|
+
errorMessage = errBody?.message || errBody?.error || errorMessage;
|
|
49
|
+
} catch {}
|
|
50
|
+
throw new Error(errorMessage);
|
|
51
|
+
}
|
|
52
|
+
const raw = await response.json();
|
|
53
|
+
const selected = select ? select(raw) : raw;
|
|
54
|
+
data.set(selected);
|
|
55
|
+
error.set(null);
|
|
56
|
+
onSuccess?.(selected);
|
|
57
|
+
} else {
|
|
58
|
+
data.set(null);
|
|
59
|
+
}
|
|
60
|
+
} catch (err) {
|
|
61
|
+
if (currentFetchId !== refetchCount) return;
|
|
62
|
+
if (retriesLeft > 0) {
|
|
63
|
+
retriesLeft--;
|
|
64
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
65
|
+
return doFetch();
|
|
66
|
+
}
|
|
67
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
68
|
+
error.set(e);
|
|
69
|
+
onError?.(e);
|
|
70
|
+
} finally {
|
|
71
|
+
if (currentFetchId === refetchCount) {
|
|
72
|
+
loading.set(false);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onMount(() => {
|
|
78
|
+
if (isEnabled) doFetch();
|
|
79
|
+
|
|
80
|
+
function onInvalidated(e) {
|
|
81
|
+
const detail = e.detail;
|
|
82
|
+
const tags = detail?.tags;
|
|
83
|
+
if (!tags || !Array.isArray(tags)) return;
|
|
84
|
+
const urlTags = generateTags(url);
|
|
85
|
+
if (tags.some((t) => urlTags.includes(t))) {
|
|
86
|
+
refetch();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
window.addEventListener("cache-invalidated", onInvalidated);
|
|
91
|
+
return () => {
|
|
92
|
+
window.removeEventListener("cache-invalidated", onInvalidated);
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return { data, error, loading, refetch };
|
|
97
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { writable } from "svelte/store";
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
import { fetchWithCache } from "../fetch/core";
|
|
4
|
+
import { generateTags } from "../cache/tags";
|
|
5
|
+
import type { FetchWithCacheOptions } from "../fetch/core";
|
|
6
|
+
|
|
7
|
+
export function useSwoffFetch<T = unknown, R = T>(
|
|
8
|
+
url: string | null,
|
|
9
|
+
options: FetchWithCacheOptions & {
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
select?: (data: T) => R;
|
|
12
|
+
keepPreviousData?: boolean;
|
|
13
|
+
retry?: number | boolean;
|
|
14
|
+
placeholderData?: R;
|
|
15
|
+
onSuccess?: (data: R) => void;
|
|
16
|
+
onError?: (error: Error) => void;
|
|
17
|
+
} = {},
|
|
18
|
+
) {
|
|
19
|
+
const {
|
|
20
|
+
select,
|
|
21
|
+
retry: retryOpt,
|
|
22
|
+
placeholderData,
|
|
23
|
+
onSuccess,
|
|
24
|
+
onError,
|
|
25
|
+
...fetchOptions
|
|
26
|
+
} = options;
|
|
27
|
+
|
|
28
|
+
const data = writable<R | null>(placeholderData ?? null);
|
|
29
|
+
const error = writable<Error | null>(null);
|
|
30
|
+
const loading = writable(url != null && options.enabled !== false);
|
|
31
|
+
|
|
32
|
+
const isEnabled = options.enabled !== false && url != null;
|
|
33
|
+
let refetchCount = 0;
|
|
34
|
+
|
|
35
|
+
function refetch() {
|
|
36
|
+
refetchCount++;
|
|
37
|
+
doFetch();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let cancelled = false;
|
|
41
|
+
|
|
42
|
+
async function doFetch() {
|
|
43
|
+
if (!isEnabled) return;
|
|
44
|
+
const currentFetchId = refetchCount;
|
|
45
|
+
|
|
46
|
+
let retriesLeft: number =
|
|
47
|
+
retryOpt === true
|
|
48
|
+
? Infinity
|
|
49
|
+
: typeof retryOpt === "number"
|
|
50
|
+
? retryOpt
|
|
51
|
+
: 0;
|
|
52
|
+
|
|
53
|
+
loading.set(true);
|
|
54
|
+
try {
|
|
55
|
+
const { response } = await fetchWithCache(url, fetchOptions);
|
|
56
|
+
if (currentFetchId !== refetchCount) return;
|
|
57
|
+
if (response) {
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
60
|
+
try {
|
|
61
|
+
const errBody = await response.json();
|
|
62
|
+
errorMessage = errBody?.message || errBody?.error || errorMessage;
|
|
63
|
+
} catch {}
|
|
64
|
+
throw new Error(errorMessage);
|
|
65
|
+
}
|
|
66
|
+
const raw: T = await response.json();
|
|
67
|
+
const selected = select ? select(raw) : (raw as unknown as R);
|
|
68
|
+
data.set(selected);
|
|
69
|
+
error.set(null);
|
|
70
|
+
onSuccess?.(selected);
|
|
71
|
+
} else {
|
|
72
|
+
data.set(null);
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (currentFetchId !== refetchCount) return;
|
|
76
|
+
if (retriesLeft > 0) {
|
|
77
|
+
retriesLeft--;
|
|
78
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
79
|
+
return doFetch();
|
|
80
|
+
}
|
|
81
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
82
|
+
error.set(e);
|
|
83
|
+
onError?.(e);
|
|
84
|
+
} finally {
|
|
85
|
+
if (currentFetchId === refetchCount) {
|
|
86
|
+
loading.set(false);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
onMount(() => {
|
|
92
|
+
if (isEnabled) doFetch();
|
|
93
|
+
|
|
94
|
+
function onInvalidated(e: Event) {
|
|
95
|
+
const detail = (e as CustomEvent).detail;
|
|
96
|
+
const tags = detail?.tags;
|
|
97
|
+
if (!tags || !Array.isArray(tags)) return;
|
|
98
|
+
const urlTags = generateTags(url!);
|
|
99
|
+
if (tags.some((t: string) => urlTags.includes(t))) {
|
|
100
|
+
refetch();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
window.addEventListener("cache-invalidated", onInvalidated);
|
|
105
|
+
return () => {
|
|
106
|
+
cancelled = true;
|
|
107
|
+
window.removeEventListener("cache-invalidated", onInvalidated);
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return { data, error, loading, refetch };
|
|
112
|
+
}
|