@swoff/cli 0.3.5 → 0.3.7

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.
Files changed (121) hide show
  1. package/README.md +610 -24
  2. package/bin/swoff +8 -14
  3. package/dist/__tests__/assemble-sw.test.js +17 -3
  4. package/dist/__tests__/assemble-sw.test.js.map +1 -1
  5. package/dist/__tests__/cli-integration.test.js +9 -9
  6. package/dist/__tests__/cli-integration.test.js.map +1 -1
  7. package/dist/__tests__/config-types.test.js +10 -9
  8. package/dist/__tests__/config-types.test.js.map +1 -1
  9. package/dist/__tests__/file-generators.test.js +65 -68
  10. package/dist/__tests__/file-generators.test.js.map +1 -1
  11. package/dist/__tests__/loader.test.js +12 -14
  12. package/dist/__tests__/loader.test.js.map +1 -1
  13. package/dist/__tests__/validator.test.js +41 -22
  14. package/dist/__tests__/validator.test.js.map +1 -1
  15. package/dist/index.js +2 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/lib/cli/help.js +3 -3
  18. package/dist/lib/cli/help.js.map +1 -1
  19. package/dist/lib/commands/add.js +11 -5
  20. package/dist/lib/commands/add.js.map +1 -1
  21. package/dist/lib/commands/clean.js +5 -0
  22. package/dist/lib/commands/clean.js.map +1 -1
  23. package/dist/lib/commands/generate-guide.js +43 -13
  24. package/dist/lib/commands/generate-guide.js.map +1 -1
  25. package/dist/lib/commands/generate.js +7 -3
  26. package/dist/lib/commands/generate.js.map +1 -1
  27. package/dist/lib/commands/info.js +149 -32
  28. package/dist/lib/commands/info.js.map +1 -1
  29. package/dist/lib/commands/init.js +6 -2
  30. package/dist/lib/commands/init.js.map +1 -1
  31. package/dist/lib/commands/validate.js +3 -1
  32. package/dist/lib/commands/validate.js.map +1 -1
  33. package/dist/lib/config/validator.js +25 -16
  34. package/dist/lib/config/validator.js.map +1 -1
  35. package/dist/lib/generators/file-generators/auth-fetch.js +29 -34
  36. package/dist/lib/generators/file-generators/auth-fetch.js.map +1 -1
  37. package/dist/lib/generators/file-generators/auth-state.js +8 -5
  38. package/dist/lib/generators/file-generators/auth-state.js.map +1 -1
  39. package/dist/lib/generators/file-generators/auth-store.js +181 -41
  40. package/dist/lib/generators/file-generators/auth-store.js.map +1 -1
  41. package/dist/lib/generators/file-generators/auth-user.js +28 -32
  42. package/dist/lib/generators/file-generators/auth-user.js.map +1 -1
  43. package/dist/lib/generators/file-generators/background-sync.js +9 -5
  44. package/dist/lib/generators/file-generators/background-sync.js.map +1 -1
  45. package/dist/lib/generators/file-generators/cache.js +8 -5
  46. package/dist/lib/generators/file-generators/cache.js.map +1 -1
  47. package/dist/lib/generators/file-generators/client-injector.js +101 -0
  48. package/dist/lib/generators/file-generators/client-injector.js.map +1 -0
  49. package/dist/lib/generators/file-generators/context.js +7 -6
  50. package/dist/lib/generators/file-generators/context.js.map +1 -1
  51. package/dist/lib/generators/file-generators/fetch-wrapper.js +196 -41
  52. package/dist/lib/generators/file-generators/fetch-wrapper.js.map +1 -1
  53. package/dist/lib/generators/file-generators/generate-hooks.js +23 -151
  54. package/dist/lib/generators/file-generators/generate-hooks.js.map +1 -1
  55. package/dist/lib/generators/file-generators/guide-generator.js +450 -0
  56. package/dist/lib/generators/file-generators/guide-generator.js.map +1 -0
  57. package/dist/lib/generators/file-generators/invalidation-tags.js +12 -10
  58. package/dist/lib/generators/file-generators/invalidation-tags.js.map +1 -1
  59. package/dist/lib/generators/file-generators/mutation-queue.js +65 -87
  60. package/dist/lib/generators/file-generators/mutation-queue.js.map +1 -1
  61. package/dist/lib/generators/file-generators/mutation-reconcile.js +57 -0
  62. package/dist/lib/generators/file-generators/mutation-reconcile.js.map +1 -0
  63. package/dist/lib/generators/file-generators/push.js +143 -0
  64. package/dist/lib/generators/file-generators/push.js.map +1 -0
  65. package/dist/lib/generators/file-generators/pwa-install.js +41 -9
  66. package/dist/lib/generators/file-generators/pwa-install.js.map +1 -1
  67. package/dist/lib/generators/file-generators/quick-readme.js +88 -0
  68. package/dist/lib/generators/file-generators/quick-readme.js.map +1 -0
  69. package/dist/lib/generators/file-generators/reconcile.js +7 -12
  70. package/dist/lib/generators/file-generators/reconcile.js.map +1 -1
  71. package/dist/lib/generators/file-generators/store.js +30 -20
  72. package/dist/lib/generators/file-generators/store.js.map +1 -1
  73. package/dist/lib/generators/file-generators/sw-generator-build.js +38 -17
  74. package/dist/lib/generators/file-generators/sw-generator-build.js.map +1 -1
  75. package/dist/lib/generators/file-generators/sw-injector.js +82 -111
  76. package/dist/lib/generators/file-generators/sw-injector.js.map +1 -1
  77. package/dist/lib/generators/file-generators/sw-push.js +67 -0
  78. package/dist/lib/generators/file-generators/sw-push.js.map +1 -0
  79. package/dist/lib/generators/file-generators/sw-template.js +3 -1
  80. package/dist/lib/generators/file-generators/sw-template.js.map +1 -1
  81. package/dist/lib/generators/file-generators/type-definitions.js +45 -22
  82. package/dist/lib/generators/file-generators/type-definitions.js.map +1 -1
  83. package/dist/lib/generators/sw-generator.js +27 -19
  84. package/dist/lib/generators/sw-generator.js.map +1 -1
  85. package/dist/lib/generators/sw-sections/activate-handler.js +6 -2
  86. package/dist/lib/generators/sw-sections/activate-handler.js.map +1 -1
  87. package/dist/lib/generators/sw-sections/assemble-sw.js +36 -12
  88. package/dist/lib/generators/sw-sections/assemble-sw.js.map +1 -1
  89. package/dist/lib/generators/sw-sections/config-header.js +1 -1
  90. package/dist/lib/generators/sw-sections/config-header.js.map +1 -1
  91. package/dist/lib/generators/sw-sections/default-template.js +1 -30
  92. package/dist/lib/generators/sw-sections/default-template.js.map +1 -1
  93. package/dist/lib/generators/sw-sections/fetch-handler.js +176 -90
  94. package/dist/lib/generators/sw-sections/fetch-handler.js.map +1 -1
  95. package/dist/lib/generators/sw-sections/install-handler.js +12 -10
  96. package/dist/lib/generators/sw-sections/install-handler.js.map +1 -1
  97. package/dist/lib/generators/sw-sections/tag-management.js +36 -5
  98. package/dist/lib/generators/sw-sections/tag-management.js.map +1 -1
  99. package/dist/lib/generators/swoff-files-generator.js +14 -12
  100. package/dist/lib/generators/swoff-files-generator.js.map +1 -1
  101. package/dist/lib/shared/config-types.js +26 -9
  102. package/dist/lib/shared/config-types.js.map +1 -1
  103. package/dist/lib/utils/build-script.js +35 -0
  104. package/dist/lib/utils/build-script.js.map +1 -0
  105. package/package.json +3 -2
  106. package/templates/hooks/useAuth.jsx +30 -0
  107. package/templates/hooks/useAuth.tsx +30 -0
  108. package/templates/hooks/useBackgroundSync.jsx +31 -0
  109. package/templates/hooks/useBackgroundSync.tsx +31 -0
  110. package/templates/hooks/useCacheInvalidation.jsx +19 -0
  111. package/templates/hooks/useCacheInvalidation.tsx +19 -0
  112. package/templates/hooks/useCachedFetch.jsx +55 -0
  113. package/templates/hooks/useCachedFetch.tsx +69 -0
  114. package/templates/hooks/useMutationQueue.jsx +34 -0
  115. package/templates/hooks/useMutationQueue.tsx +34 -0
  116. package/templates/hooks/useNetworkStatus.jsx +19 -0
  117. package/templates/hooks/useNetworkStatus.tsx +19 -0
  118. package/templates/hooks/usePushSubscription.jsx +67 -0
  119. package/templates/hooks/usePushSubscription.tsx +67 -0
  120. package/templates/hooks/useSWUpdate.jsx +76 -0
  121. package/templates/hooks/useSWUpdate.tsx +76 -0
@@ -4,9 +4,9 @@ export const KNOWN_FEATURES = [
4
4
  "auth",
5
5
  "crossTabSync",
6
6
  "tagInvalidation",
7
- "clientRegistration",
7
+ "pushNotifications",
8
8
  ];
9
- export const OBJECT_FEATURES = ["pwa", "serviceWorker", "auth"];
9
+ export const OBJECT_FEATURES = ["pwa", "serviceWorker", "auth", "pushNotifications"];
10
10
  export const VALID_STRATEGIES = [
11
11
  "cache-first",
12
12
  "network-first",
@@ -22,6 +22,11 @@ function normalizeAuth(auth) {
22
22
  return { ...defaultAuth, ...auth };
23
23
  return defaultAuth;
24
24
  }
25
+ function normalizeSwVersion(ver) {
26
+ if (ver && typeof ver === "object")
27
+ return { ...defaultVersionConfig, ...ver };
28
+ return defaultVersionConfig;
29
+ }
25
30
  export function mergeConfigs(base, override) {
26
31
  return {
27
32
  ...base,
@@ -30,7 +35,11 @@ export function mergeConfigs(base, override) {
30
35
  ...base.features,
31
36
  ...override.features,
32
37
  pwa: { ...base.features.pwa, ...override.features?.pwa },
33
- serviceWorker: { ...base.features.serviceWorker, ...override.features?.serviceWorker },
38
+ serviceWorker: {
39
+ ...base.features.serviceWorker,
40
+ ...override.features?.serviceWorker,
41
+ version: normalizeSwVersion(override.features?.serviceWorker?.version),
42
+ },
34
43
  auth: normalizeAuth(override.features?.auth),
35
44
  },
36
45
  build: { ...base.build, ...override.build },
@@ -42,22 +51,27 @@ export const defaultAuth = {
42
51
  refreshPath: "/api/refresh",
43
52
  userEndpoint: "/api/me",
44
53
  };
45
- export const defaultConfig = {
54
+ export const defaultVersionConfig = {
46
55
  enabled: true,
47
- version: "from-package",
56
+ source: "from-package",
48
57
  minSupportedVersion: "0.0.0",
58
+ };
59
+ export const defaultConfig = {
60
+ enabled: true,
49
61
  features: {
50
62
  pwa: {
51
63
  enabled: true,
52
64
  preventDefaultInstall: false,
53
65
  },
54
66
  serviceWorker: {
55
- versionedSw: true,
56
- autoRegister: true,
67
+ version: { ...defaultVersionConfig },
68
+ autoUpdate: true,
57
69
  autoActivate: false,
58
70
  defaultStrategy: "cache-first",
59
71
  strategies: {},
72
+ cacheStrategy: "all",
60
73
  clearRuntimeOnUpdate: false,
74
+ navigationPreload: true,
61
75
  navigationMode: "spa",
62
76
  spaEntry: "/index.html",
63
77
  },
@@ -66,7 +80,7 @@ export const defaultConfig = {
66
80
  auth: { ...defaultAuth },
67
81
  crossTabSync: true,
68
82
  tagInvalidation: true,
69
- clientRegistration: true,
83
+ pushNotifications: { enabled: false },
70
84
  },
71
85
  build: {
72
86
  outputDir: "dist",
@@ -76,11 +90,14 @@ export const defaultConfig = {
76
90
  export const defaultInitConfig = {
77
91
  ...defaultConfig,
78
92
  $schema: "https://swoff.netlify.app/schema/v1.json",
79
- minSupportedVersion: "1.0.0",
80
93
  features: {
81
94
  ...defaultConfig.features,
82
95
  serviceWorker: {
83
96
  ...defaultConfig.features.serviceWorker,
97
+ version: {
98
+ ...defaultVersionConfig,
99
+ minSupportedVersion: "1.0.0",
100
+ },
84
101
  strategies: {
85
102
  "/api/*": "network-first",
86
103
  "/static/*": "cache-first",
@@ -1 +1 @@
1
- {"version":3,"file":"config-types.js","sourceRoot":"","sources":["../../../src/lib/shared/config-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config-types.js","sourceRoot":"","sources":["../../../src/lib/shared/config-types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Utilities for managing the SW generator step in package.json build scripts.
3
+ */
4
+ import { readFileSync, writeFileSync, existsSync } from "fs";
5
+ import { join } from "path";
6
+ const SW_GENERATOR_STEP = " && node swoff/sw/generator.js";
7
+ export function appendGeneratorToBuildScript(projectRoot) {
8
+ const pkgPath = join(projectRoot, "package.json");
9
+ if (!existsSync(pkgPath))
10
+ return false;
11
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
12
+ const build = pkg.scripts?.build;
13
+ if (!build || typeof build !== "string")
14
+ return false;
15
+ if (build.includes(SW_GENERATOR_STEP))
16
+ return false;
17
+ pkg.scripts.build = build + SW_GENERATOR_STEP;
18
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
19
+ return true;
20
+ }
21
+ export function removeGeneratorFromBuildScript(projectRoot) {
22
+ const pkgPath = join(projectRoot, "package.json");
23
+ if (!existsSync(pkgPath))
24
+ return false;
25
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
26
+ const build = pkg.scripts?.build;
27
+ if (!build || typeof build !== "string")
28
+ return false;
29
+ if (!build.includes(SW_GENERATOR_STEP))
30
+ return false;
31
+ pkg.scripts.build = build.replace(SW_GENERATOR_STEP, "");
32
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
33
+ return true;
34
+ }
35
+ //# sourceMappingURL=build-script.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-script.js","sourceRoot":"","sources":["../../../src/lib/utils/build-script.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,MAAM,iBAAiB,GAAG,gCAAgC,CAAC;AAE3D,MAAM,UAAU,4BAA4B,CAAC,WAAmB;IAC9D,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IAClD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IAEvC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC;IAEjC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,IAAI,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,KAAK,CAAC;IAEpD,GAAG,CAAC,OAAO,CAAC,KAAK,GAAG,KAAK,GAAG,iBAAiB,CAAC;IAC9C,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC5D,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,WAAmB;IAChE,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IAClD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IAEvC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC;IAEjC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QAAE,OAAO,KAAK,CAAC;IAErD,GAAG,CAAC,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;IACzD,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC5D,OAAO,IAAI,CAAC;AACd,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swoff/cli",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "type": "module",
5
5
  "description": "CLI for Swoff - Offline-first web apps made easy",
6
6
  "bin": {
@@ -18,7 +18,8 @@
18
18
  },
19
19
  "files": [
20
20
  "bin/",
21
- "dist/"
21
+ "dist/",
22
+ "templates/"
22
23
  ],
23
24
  "keywords": [
24
25
  "service-worker",
@@ -0,0 +1,30 @@
1
+ import { useState, useEffect } from "react";
2
+ import { getAuthState } from "../auth/state.js";
3
+
4
+ export function useAuth() {
5
+ const [state, setState] = useState({
6
+ authenticated: false,
7
+ user: null,
8
+ online: navigator.onLine,
9
+ });
10
+
11
+ useEffect(() => {
12
+ getAuthState().then(setState);
13
+
14
+ const onOnline = () => setState((s) => ({ ...s, online: true }));
15
+ const onOffline = () => setState((s) => ({ ...s, online: false }));
16
+ const onAuthChange = () => getAuthState().then(setState);
17
+
18
+ window.addEventListener("online", onOnline);
19
+ window.addEventListener("offline", onOffline);
20
+ window.addEventListener("sw-auth-state-change", onAuthChange);
21
+
22
+ return () => {
23
+ window.removeEventListener("online", onOnline);
24
+ window.removeEventListener("offline", onOffline);
25
+ window.removeEventListener("sw-auth-state-change", onAuthChange);
26
+ };
27
+ }, []);
28
+
29
+ return state;
30
+ }
@@ -0,0 +1,30 @@
1
+ import { useState, useEffect } from "react";
2
+ import { getAuthState } from "../auth/state.ts";
3
+
4
+ export function useAuth() {
5
+ const [state, setState] = useState({
6
+ authenticated: false,
7
+ user: null as Record<string, unknown> | null,
8
+ online: navigator.onLine,
9
+ });
10
+
11
+ useEffect(() => {
12
+ getAuthState().then(setState);
13
+
14
+ const onOnline = () => setState((s) => ({ ...s, online: true }));
15
+ const onOffline = () => setState((s) => ({ ...s, online: false }));
16
+ const onAuthChange = () => getAuthState().then(setState);
17
+
18
+ window.addEventListener("online", onOnline);
19
+ window.addEventListener("offline", onOffline);
20
+ window.addEventListener("sw-auth-state-change", onAuthChange);
21
+
22
+ return () => {
23
+ window.removeEventListener("online", onOnline);
24
+ window.removeEventListener("offline", onOffline);
25
+ window.removeEventListener("sw-auth-state-change", onAuthChange);
26
+ };
27
+ }, []);
28
+
29
+ return state;
30
+ }
@@ -0,0 +1,31 @@
1
+ import { useState, useEffect, useCallback } from "react";
2
+ import { retrySync } from "../background-sync.js";
3
+
4
+ export function useBackgroundSync() {
5
+ const [state, setState] = useState({
6
+ supported: "serviceWorker" in navigator && "SyncManager" in window,
7
+ registered: false,
8
+ lastSync: null,
9
+ });
10
+
11
+ useEffect(() => {
12
+ const onSyncComplete = (e) => {
13
+ setState((s) => ({
14
+ ...s,
15
+ registered: true,
16
+ lastSync: { succeeded: e.detail.succeeded, failed: e.detail.failed },
17
+ }));
18
+ };
19
+
20
+ window.addEventListener("background-sync-complete", onSyncComplete);
21
+ return () => {
22
+ window.removeEventListener("background-sync-complete", onSyncComplete);
23
+ };
24
+ }, []);
25
+
26
+ const triggerSync = useCallback(async () => {
27
+ await retrySync();
28
+ }, []);
29
+
30
+ return { ...state, triggerSync };
31
+ }
@@ -0,0 +1,31 @@
1
+ import { useState, useEffect, useCallback } from "react";
2
+ import { retrySync } from "../background-sync.ts";
3
+
4
+ export function useBackgroundSync() {
5
+ const [state, setState] = useState({
6
+ supported: "serviceWorker" in navigator && "SyncManager" in window,
7
+ registered: false,
8
+ lastSync: null as { succeeded: number; failed: number } | null,
9
+ });
10
+
11
+ useEffect(() => {
12
+ const onSyncComplete = (e: CustomEvent) => {
13
+ setState((s) => ({
14
+ ...s,
15
+ registered: true,
16
+ lastSync: { succeeded: e.detail.succeeded, failed: e.detail.failed },
17
+ }));
18
+ };
19
+
20
+ window.addEventListener("background-sync-complete", onSyncComplete);
21
+ return () => {
22
+ window.removeEventListener("background-sync-complete", onSyncComplete);
23
+ };
24
+ }, []);
25
+
26
+ const triggerSync = useCallback(async () => {
27
+ await retrySync();
28
+ }, []);
29
+
30
+ return { ...state, triggerSync };
31
+ }
@@ -0,0 +1,19 @@
1
+ import { useCallback } from "react";
2
+ import { invalidateByTag, invalidateByTags } from "../cache.js";
3
+ import { invalidateUrl } from "../invalidation-tags.js";
4
+
5
+ export function useCacheInvalidation() {
6
+ const invalidateTag = useCallback(async (tag) => {
7
+ await invalidateByTag(tag);
8
+ }, []);
9
+
10
+ const invalidateTags = useCallback(async (tags) => {
11
+ await invalidateByTags(tags);
12
+ }, []);
13
+
14
+ const invalidateCacheUrl = useCallback(async (url) => {
15
+ await invalidateUrl(url);
16
+ }, []);
17
+
18
+ return { invalidateByTag: invalidateTag, invalidateByTags: invalidateTags, invalidateUrl: invalidateCacheUrl };
19
+ }
@@ -0,0 +1,19 @@
1
+ import { useCallback } from "react";
2
+ import { invalidateByTag, invalidateByTags } from "../cache.ts";
3
+ import { invalidateUrl } from "../invalidation-tags.ts";
4
+
5
+ export function useCacheInvalidation() {
6
+ const invalidateTag = useCallback(async (tag: string) => {
7
+ await invalidateByTag(tag);
8
+ }, []);
9
+
10
+ const invalidateTags = useCallback(async (tags: string[]) => {
11
+ await invalidateByTags(tags);
12
+ }, []);
13
+
14
+ const invalidateCacheUrl = useCallback(async (url: string) => {
15
+ await invalidateUrl(url);
16
+ }, []);
17
+
18
+ return { invalidateByTag: invalidateTag, invalidateByTags: invalidateTags, invalidateUrl: invalidateCacheUrl };
19
+ }
@@ -0,0 +1,55 @@
1
+ import { useState, useEffect, useCallback, useRef, startTransition } from "react";
2
+ import { fetchWithCache } from "../fetch-wrapper.js";
3
+ import { generateTags } from "../invalidation-tags.js";
4
+
5
+ export function useCachedFetch(url, options = {}) {
6
+ const [data, setData] = useState(null);
7
+ const [error, setError] = useState(null);
8
+ const [loading, setLoading] = useState(true);
9
+ const [refetchCount, setRefetchCount] = useState(0);
10
+ const optionsRef = useRef(options);
11
+ useEffect(() => { optionsRef.current = options; }, [options]);
12
+
13
+ const refetch = useCallback(() => setRefetchCount((c) => c + 1), []);
14
+
15
+ useEffect(() => {
16
+ let cancelled = false;
17
+ startTransition(() => setLoading(true));
18
+
19
+ const doFetch = async () => {
20
+ try {
21
+ const { response } = await fetchWithCache(url, optionsRef.current);
22
+ if (cancelled) return;
23
+ if (response) {
24
+ setData(await response.json());
25
+ } else {
26
+ setData(null);
27
+ }
28
+ if (!cancelled) setError(null);
29
+ } catch (err) {
30
+ if (!cancelled) setError(err instanceof Error ? err : new Error(String(err)));
31
+ } finally {
32
+ if (!cancelled) setLoading(false);
33
+ }
34
+ };
35
+
36
+ doFetch();
37
+ return () => { cancelled = true; };
38
+ }, [url, refetchCount]);
39
+
40
+ useEffect(() => {
41
+ const onInvalidated = (e) => {
42
+ const detail = e.detail;
43
+ const tags = detail?.tags;
44
+ if (!tags || !Array.isArray(tags)) return;
45
+ const urlTags = generateTags(url);
46
+ if (tags.some((t) => urlTags.includes(t))) {
47
+ setRefetchCount((c) => c + 1);
48
+ }
49
+ };
50
+ window.addEventListener("cache-invalidated", onInvalidated);
51
+ return () => window.removeEventListener("cache-invalidated", onInvalidated);
52
+ }, [url]);
53
+
54
+ return { data, error, loading, refetch };
55
+ }
@@ -0,0 +1,69 @@
1
+ import {
2
+ useState,
3
+ useEffect,
4
+ useCallback,
5
+ useRef,
6
+ startTransition,
7
+ } from "react";
8
+ import { fetchWithCache } from "../fetch-wrapper.ts";
9
+ import { generateTags } from "../invalidation-tags.ts";
10
+
11
+ export function useCachedFetch<T>(
12
+ url: string,
13
+ options: Parameters<typeof fetchWithCache>[1] = {},
14
+ ) {
15
+ const [data, setData] = useState<T | null>(null);
16
+ const [error, setError] = useState<Error | null>(null);
17
+ const [loading, setLoading] = useState(true);
18
+ const [refetchCount, setRefetchCount] = useState(0);
19
+ const optionsRef = useRef(options);
20
+ useEffect(() => {
21
+ optionsRef.current = options;
22
+ }, [options]);
23
+
24
+ const refetch = useCallback(() => setRefetchCount((c) => c + 1), []);
25
+
26
+ useEffect(() => {
27
+ let cancelled = false;
28
+ startTransition(() => setLoading(true));
29
+
30
+ const doFetch = async () => {
31
+ try {
32
+ const { response } = await fetchWithCache(url, optionsRef.current);
33
+ if (cancelled) return;
34
+ if (response) {
35
+ setData(await response.json());
36
+ } else {
37
+ setData(null);
38
+ }
39
+ if (!cancelled) setError(null);
40
+ } catch (err) {
41
+ if (!cancelled)
42
+ setError(err instanceof Error ? err : new Error(String(err)));
43
+ } finally {
44
+ if (!cancelled) setLoading(false);
45
+ }
46
+ };
47
+
48
+ doFetch();
49
+ return () => {
50
+ cancelled = true;
51
+ };
52
+ }, [url, refetchCount]);
53
+
54
+ useEffect(() => {
55
+ const onInvalidated = (e: Event) => {
56
+ const detail = (e as CustomEvent).detail;
57
+ const tags = detail?.tags;
58
+ if (!tags || !Array.isArray(tags)) return;
59
+ const urlTags = generateTags(url);
60
+ if (tags.some((t: string) => urlTags.includes(t))) {
61
+ setRefetchCount((c) => c + 1);
62
+ }
63
+ };
64
+ window.addEventListener("cache-invalidated", onInvalidated);
65
+ return () => window.removeEventListener("cache-invalidated", onInvalidated);
66
+ }, [url]);
67
+
68
+ return { data, error, loading, refetch };
69
+ }
@@ -0,0 +1,34 @@
1
+ import { useState, useEffect } from "react";
2
+ import { getPendingCount } from "../mutation-queue.js";
3
+
4
+ export function useMutationQueue() {
5
+ const [state, setState] = useState({
6
+ pending: 0,
7
+ lastSync: null,
8
+ });
9
+
10
+ useEffect(() => {
11
+ getPendingCount().then((count) => setState((s) => ({ ...s, pending: count })));
12
+
13
+ const onSync = (e) => {
14
+ getPendingCount().then((count) => {
15
+ setState({
16
+ pending: count,
17
+ lastSync: { succeeded: e.detail.succeeded, failed: e.detail.failed },
18
+ });
19
+ });
20
+ };
21
+ const onChange = () => {
22
+ getPendingCount().then((count) => setState((s) => ({ ...s, pending: count })));
23
+ };
24
+
25
+ window.addEventListener("mutation-sync-complete", onSync);
26
+ window.addEventListener("mutation-queue-changed", onChange);
27
+ return () => {
28
+ window.removeEventListener("mutation-sync-complete", onSync);
29
+ window.removeEventListener("mutation-queue-changed", onChange);
30
+ };
31
+ }, []);
32
+
33
+ return state;
34
+ }
@@ -0,0 +1,34 @@
1
+ import { useState, useEffect } from "react";
2
+ import { getPendingCount } from "../mutation-queue.ts";
3
+
4
+ export function useMutationQueue() {
5
+ const [state, setState] = useState<{
6
+ pending: number;
7
+ lastSync: { succeeded: number; failed: number } | null;
8
+ }>({ pending: 0, lastSync: null });
9
+
10
+ useEffect(() => {
11
+ getPendingCount().then((count: number) => setState((s) => ({ ...s, pending: count })));
12
+
13
+ const onSync = (e: CustomEvent) => {
14
+ getPendingCount().then((count: number) => {
15
+ setState({
16
+ pending: count,
17
+ lastSync: { succeeded: e.detail.succeeded, failed: e.detail.failed },
18
+ });
19
+ });
20
+ };
21
+ const onChange = () => {
22
+ getPendingCount().then((count: number) => setState((s) => ({ ...s, pending: count })));
23
+ };
24
+
25
+ window.addEventListener("mutation-sync-complete", onSync);
26
+ window.addEventListener("mutation-queue-changed", onChange);
27
+ return () => {
28
+ window.removeEventListener("mutation-sync-complete", onSync);
29
+ window.removeEventListener("mutation-queue-changed", onChange);
30
+ };
31
+ }, []);
32
+
33
+ return state;
34
+ }
@@ -0,0 +1,19 @@
1
+ import { useState, useEffect } from "react";
2
+
3
+ export function useNetworkStatus() {
4
+ const [online, setOnline] = useState(navigator.onLine);
5
+
6
+ useEffect(() => {
7
+ const onOnline = () => setOnline(true);
8
+ const onOffline = () => setOnline(false);
9
+
10
+ window.addEventListener("online", onOnline);
11
+ window.addEventListener("offline", onOffline);
12
+ return () => {
13
+ window.removeEventListener("online", onOnline);
14
+ window.removeEventListener("offline", onOffline);
15
+ };
16
+ }, []);
17
+
18
+ return online;
19
+ }
@@ -0,0 +1,19 @@
1
+ import { useState, useEffect } from "react";
2
+
3
+ export function useNetworkStatus() {
4
+ const [online, setOnline] = useState(navigator.onLine);
5
+
6
+ useEffect(() => {
7
+ const onOnline = () => setOnline(true);
8
+ const onOffline = () => setOnline(false);
9
+
10
+ window.addEventListener("online", onOnline);
11
+ window.addEventListener("offline", onOffline);
12
+ return () => {
13
+ window.removeEventListener("online", onOnline);
14
+ window.removeEventListener("offline", onOffline);
15
+ };
16
+ }, []);
17
+
18
+ return online;
19
+ }
@@ -0,0 +1,67 @@
1
+ import { useState, useEffect, useCallback } from "react";
2
+ import {
3
+ subscribeToPush,
4
+ unsubscribeFromPush,
5
+ isSubscribed,
6
+ getPushSubscription,
7
+ } from "../push.js";
8
+
9
+ export function usePushSubscription(vapidPublicKey) {
10
+ const [state, setState] = useState({
11
+ subscribed: false,
12
+ subscription: null,
13
+ permission: Notification.permission,
14
+ loading: true,
15
+ });
16
+
17
+ useEffect(() => {
18
+ let cancelled = false;
19
+
20
+ isSubscribed().then((subscribed) => {
21
+ if (cancelled) return;
22
+ if (subscribed) {
23
+ getPushSubscription().then((sub) => {
24
+ if (cancelled) return;
25
+ setState({ subscribed: true, subscription: sub, permission: Notification.permission, loading: false });
26
+ });
27
+ } else {
28
+ setState({ subscribed: false, subscription: null, permission: Notification.permission, loading: false });
29
+ }
30
+ });
31
+
32
+ const onSubChanged = (e) => {
33
+ if (cancelled) return;
34
+ if (!e.detail.subscribed) {
35
+ setState({ subscribed: false, subscription: null, permission: Notification.permission, loading: false });
36
+ } else {
37
+ getPushSubscription().then((sub) => {
38
+ if (cancelled) return;
39
+ setState({ subscribed: true, subscription: sub, permission: Notification.permission, loading: false });
40
+ });
41
+ }
42
+ };
43
+ const onPermissionChanged = (e) => {
44
+ if (cancelled) return;
45
+ setState((s) => ({ ...s, permission: e.detail.permission }));
46
+ };
47
+
48
+ window.addEventListener("push-subscription-changed", onSubChanged);
49
+ window.addEventListener("push-permission-changed", onPermissionChanged);
50
+ return () => {
51
+ cancelled = true;
52
+ window.removeEventListener("push-subscription-changed", onSubChanged);
53
+ window.removeEventListener("push-permission-changed", onPermissionChanged);
54
+ };
55
+ }, []);
56
+
57
+ const subscribe = useCallback(async () => {
58
+ const sub = await subscribeToPush(vapidPublicKey);
59
+ return sub !== null;
60
+ }, [vapidPublicKey]);
61
+
62
+ const unsubscribe = useCallback(async () => {
63
+ await unsubscribeFromPush();
64
+ }, []);
65
+
66
+ return { ...state, subscribe, unsubscribe };
67
+ }