@cedarjs/web 8.0.0-canary.3331 → 8.0.0-canary.3335

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.
@@ -0,0 +1,49 @@
1
+ export interface UseNewVersionAvailableOptions {
2
+ /**
3
+ * How often to check for a new version, in milliseconds.
4
+ *
5
+ * @default 60000
6
+ */
7
+ intervalMs?: number;
8
+ }
9
+ /**
10
+ * Detects that the server is serving a newer build of the app than the one
11
+ * running in the current tab, so you can ask the user to reload.
12
+ *
13
+ * The running build is identified by the `<script type="module" src="...">`
14
+ * elements in the current document, which Vite gives content-hashed file
15
+ * names. The hook periodically fetches the URL the tab was loaded from (and
16
+ * also checks when the tab becomes visible again or the browser comes back
17
+ * online) and compares the module scripts in the response with the running
18
+ * ones. The request is sent without cookies, so hosts with skew protection
19
+ * (like Netlify) answer with the latest deploy. Once a new version is
20
+ * detected the hook returns `true` and stops checking.
21
+ *
22
+ * The hook always returns `false` during server rendering, in development
23
+ * and when the current document has no module scripts.
24
+ *
25
+ * Components using the hook with the same options share a single polling
26
+ * loop.
27
+ *
28
+ * @example
29
+ * ```jsx
30
+ * import { useNewVersionAvailable } from '@cedarjs/web'
31
+ *
32
+ * const NewVersionBanner = () => {
33
+ * const newVersionAvailable = useNewVersionAvailable()
34
+ *
35
+ * if (!newVersionAvailable) {
36
+ * return null
37
+ * }
38
+ *
39
+ * return (
40
+ * <div role="status">
41
+ * A new version of the app is available.
42
+ * <button onClick={() => window.location.reload()}>Reload</button>
43
+ * </div>
44
+ * )
45
+ * }
46
+ * ```
47
+ */
48
+ export declare function useNewVersionAvailable(options?: UseNewVersionAvailableOptions): boolean;
49
+ //# sourceMappingURL=useNewVersionAvailable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useNewVersionAvailable.d.ts","sourceRoot":"","sources":["../../src/components/useNewVersionAvailable.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAwPD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAST"}
@@ -0,0 +1,169 @@
1
+ import { useSyncExternalStore } from "react";
2
+ const DEFAULT_INTERVAL_MS = 6e4;
3
+ const CHECK_TIMEOUT_MS = 3e4;
4
+ const initialDocumentUrl = typeof document === "undefined" ? void 0 : document.URL;
5
+ let runningModuleScripts;
6
+ const checkers = /* @__PURE__ */ new Map();
7
+ function getModuleScriptPathnames(doc, baseUrl) {
8
+ const pathnames = /* @__PURE__ */ new Set();
9
+ doc.querySelectorAll('script[type="module"][src]').forEach((script) => {
10
+ const src = script.getAttribute("src");
11
+ if (!src) {
12
+ return;
13
+ }
14
+ try {
15
+ pathnames.add(new URL(src, baseUrl).pathname);
16
+ } catch {
17
+ }
18
+ });
19
+ return pathnames;
20
+ }
21
+ function setsAreEqual(a, b) {
22
+ if (a.size !== b.size) {
23
+ return false;
24
+ }
25
+ for (const value of a) {
26
+ if (!b.has(value)) {
27
+ return false;
28
+ }
29
+ }
30
+ return true;
31
+ }
32
+ function createVersionChecker(url, intervalMs, runningScripts, lifecycle) {
33
+ const listeners = /* @__PURE__ */ new Set();
34
+ let newVersionAvailable = false;
35
+ let checkInFlight = false;
36
+ let intervalId;
37
+ const notify = () => {
38
+ listeners.forEach((listener) => listener());
39
+ };
40
+ const check = async () => {
41
+ if (newVersionAvailable || checkInFlight) {
42
+ return;
43
+ }
44
+ checkInFlight = true;
45
+ const controller = new AbortController();
46
+ const timeoutId = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
47
+ try {
48
+ const requestUrl = new URL(url);
49
+ requestUrl.hash = "";
50
+ const response = await fetch(requestUrl.href, {
51
+ cache: "no-store",
52
+ credentials: "omit",
53
+ signal: controller.signal
54
+ });
55
+ if (response.redirected) {
56
+ return;
57
+ }
58
+ const html = await response.text();
59
+ const fetchedDocument = new DOMParser().parseFromString(html, "text/html");
60
+ const fetchedScripts = getModuleScriptPathnames(
61
+ fetchedDocument,
62
+ response.url || requestUrl.href
63
+ );
64
+ if (fetchedScripts.size === 0) {
65
+ return;
66
+ }
67
+ if (!setsAreEqual(runningScripts, fetchedScripts)) {
68
+ newVersionAvailable = true;
69
+ stop();
70
+ notify();
71
+ }
72
+ } catch {
73
+ } finally {
74
+ clearTimeout(timeoutId);
75
+ checkInFlight = false;
76
+ }
77
+ };
78
+ const onVisibilityChange = () => {
79
+ if (document.visibilityState === "visible") {
80
+ void check();
81
+ }
82
+ };
83
+ const onOnline = () => {
84
+ void check();
85
+ };
86
+ function start() {
87
+ intervalId = setInterval(() => void check(), intervalMs);
88
+ document.addEventListener("visibilitychange", onVisibilityChange);
89
+ window.addEventListener("online", onOnline);
90
+ }
91
+ function stop() {
92
+ if (intervalId !== void 0) {
93
+ clearInterval(intervalId);
94
+ intervalId = void 0;
95
+ }
96
+ document.removeEventListener("visibilitychange", onVisibilityChange);
97
+ window.removeEventListener("online", onOnline);
98
+ }
99
+ return {
100
+ subscribe: (listener) => {
101
+ listeners.add(listener);
102
+ if (listeners.size === 1) {
103
+ lifecycle.onActive();
104
+ if (!newVersionAvailable) {
105
+ start();
106
+ }
107
+ }
108
+ return () => {
109
+ listeners.delete(listener);
110
+ if (listeners.size === 0) {
111
+ stop();
112
+ lifecycle.onIdle();
113
+ }
114
+ };
115
+ },
116
+ getSnapshot: () => newVersionAvailable
117
+ };
118
+ }
119
+ function getVersionChecker(intervalMs) {
120
+ if (typeof window === "undefined" || !initialDocumentUrl) {
121
+ return void 0;
122
+ }
123
+ if (process.env.NODE_ENV === "development") {
124
+ return void 0;
125
+ }
126
+ runningModuleScripts ??= getModuleScriptPathnames(document, document.baseURI);
127
+ if (runningModuleScripts.size === 0) {
128
+ return void 0;
129
+ }
130
+ const existingChecker = checkers.get(intervalMs);
131
+ if (existingChecker) {
132
+ return existingChecker;
133
+ }
134
+ const checker = createVersionChecker(
135
+ initialDocumentUrl,
136
+ intervalMs,
137
+ runningModuleScripts,
138
+ {
139
+ onActive: () => {
140
+ checkers.set(intervalMs, checker);
141
+ },
142
+ // A checker that found a new version is kept, so components mounting
143
+ // later see `true` right away. Idle checkers are dropped, so intervals
144
+ // that are no longer used don't accumulate
145
+ onIdle: () => {
146
+ if (!checker.getSnapshot()) {
147
+ checkers.delete(intervalMs);
148
+ }
149
+ }
150
+ }
151
+ );
152
+ checkers.set(intervalMs, checker);
153
+ return checker;
154
+ }
155
+ const subscribeNoop = () => () => {
156
+ };
157
+ const getFalse = () => false;
158
+ function useNewVersionAvailable(options = {}) {
159
+ const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
160
+ const checker = getVersionChecker(intervalMs);
161
+ return useSyncExternalStore(
162
+ checker?.subscribe ?? subscribeNoop,
163
+ checker?.getSnapshot ?? getFalse,
164
+ getFalse
165
+ );
166
+ }
167
+ export {
168
+ useNewVersionAvailable
169
+ };
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@ import './apollo/typeOverrides.js';
3
3
  import './config.js';
4
4
  export { FatalErrorBoundary } from './components/FatalErrorBoundary.js';
5
5
  export { FetchConfigProvider, useFetchConfig, } from './components/FetchConfigProvider.js';
6
+ export { useNewVersionAvailable } from './components/useNewVersionAvailable.js';
7
+ export type { UseNewVersionAvailableOptions } from './components/useNewVersionAvailable.js';
6
8
  export { useQuery, useMutation, useSubscription } from '@apollo/client/react';
7
9
  export { useFragment } from './apollo/fragmentRegistry.js';
8
10
  export type { FragmentHookOptions, FragmentHookResult, } from './apollo/fragmentRegistry.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,8BAA8B,CAAA;AACrC,OAAO,2BAA2B,CAAA;AAClC,OAAO,aAAa,CAAA;AAEpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAEvE,OAAO,EACL,mBAAmB,EACnB,cAAc,GACf,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC1D,YAAY,EACV,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,8BAA8B,CAAA;AAErC,cAAc,uCAAuC,CAAA;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAA;AAE5E,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,UAAU,GACX,MAAM,gCAAgC,CAAA;AAEvC,cAAc,cAAc,CAAA;AAE5B,cAAc,+BAA+B,CAAA;AAE7C,cAAc,0BAA0B,CAAA;AACxC,cAAc,0BAA0B,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,MAAM,IAAI,IAAI,EAAE,MAAM,EAAE,CAAA;AAEjC,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AAErC,cAAc,8BAA8B,CAAA;AAE5C,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,8BAA8B,CAAA;AACrC,OAAO,2BAA2B,CAAA;AAClC,OAAO,aAAa,CAAA;AAEpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAEvE,OAAO,EACL,mBAAmB,EACnB,cAAc,GACf,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAA;AAC/E,YAAY,EAAE,6BAA6B,EAAE,MAAM,wCAAwC,CAAA;AAC3F,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC1D,YAAY,EACV,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,8BAA8B,CAAA;AAErC,cAAc,uCAAuC,CAAA;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAA;AAE5E,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,UAAU,GACX,MAAM,gCAAgC,CAAA;AAEvC,cAAc,cAAc,CAAA;AAE5B,cAAc,+BAA+B,CAAA;AAE7C,cAAc,0BAA0B,CAAA;AACxC,cAAc,0BAA0B,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,MAAM,IAAI,IAAI,EAAE,MAAM,EAAE,CAAA;AAEjC,cAAc,0BAA0B,CAAA;AACxC,cAAc,uBAAuB,CAAA;AAErC,cAAc,8BAA8B,CAAA;AAE5C,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA"}
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  FetchConfigProvider,
7
7
  useFetchConfig
8
8
  } from "./components/FetchConfigProvider.js";
9
+ import { useNewVersionAvailable } from "./components/useNewVersionAvailable.js";
9
10
  import { useQuery, useMutation, useSubscription } from "@apollo/client/react";
10
11
  import { useFragment } from "./apollo/fragmentRegistry.js";
11
12
  export * from "./components/cell/CellCacheContext.js";
@@ -31,6 +32,7 @@ export {
31
32
  useFetchConfig,
32
33
  useFragment,
33
34
  useMutation,
35
+ useNewVersionAvailable,
34
36
  useQuery,
35
37
  useSubscription
36
38
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/web",
3
- "version": "8.0.0-canary.3331",
3
+ "version": "8.0.0-canary.3335",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/cedarjs/cedar.git",
@@ -79,7 +79,7 @@
79
79
  },
80
80
  "dependencies": {
81
81
  "@apollo/client": "4.3.1",
82
- "@cedarjs/server-store": "8.0.0-canary.3331",
82
+ "@cedarjs/server-store": "8.0.0-canary.3335",
83
83
  "@dr.pogodin/react-helmet": "2.0.4",
84
84
  "@whatwg-node/fetch": "0.12.1",
85
85
  "apollo-upload-client": "19.0.0",
@@ -95,9 +95,9 @@
95
95
  "devDependencies": {
96
96
  "@apollo/client-react-streaming": "0.14.5",
97
97
  "@arethetypeswrong/cli": "0.18.5",
98
- "@cedarjs/auth": "8.0.0-canary.3331",
99
- "@cedarjs/framework-tools": "8.0.0-canary.3331",
100
- "@cedarjs/internal": "8.0.0-canary.3331",
98
+ "@cedarjs/auth": "8.0.0-canary.3335",
99
+ "@cedarjs/framework-tools": "8.0.0-canary.3335",
100
+ "@cedarjs/internal": "8.0.0-canary.3335",
101
101
  "@testing-library/dom": "10.4.2",
102
102
  "@testing-library/jest-dom": "6.9.1",
103
103
  "@testing-library/react": "16.3.3",
@@ -106,7 +106,7 @@
106
106
  "@types/react-dom": "^19.2.0",
107
107
  "concurrently": "9.2.4",
108
108
  "nodemon": "3.1.14",
109
- "publint": "0.3.24",
109
+ "publint": "0.3.25",
110
110
  "react": "19.3.0",
111
111
  "react-dom": "19.3.0",
112
112
  "tstyche": "5.0.2",
@@ -114,7 +114,7 @@
114
114
  "vitest": "4.1.11"
115
115
  },
116
116
  "peerDependencies": {
117
- "@cedarjs/auth": "8.0.0-canary.3331",
117
+ "@cedarjs/auth": "8.0.0-canary.3335",
118
118
  "react": "19.3.0",
119
119
  "react-dom": "19.3.0"
120
120
  },