@rpcbase/client 0.330.0 → 0.332.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/dist/index.js CHANGED
@@ -79,6 +79,130 @@ const cleanupURL = () => {
79
79
  document.addEventListener("DOMContentLoaded", runCleanup, { once: true });
80
80
  }
81
81
  };
82
+ const DEFAULT_PRIORITY = 0;
83
+ const canBlockNavigation = (guard, {
84
+ isPathnameChange,
85
+ isSearchChange
86
+ }) => {
87
+ if (!guard.enabled) return false;
88
+ if (isPathnameChange) return true;
89
+ if (isSearchChange && guard.blockOnSearch) return true;
90
+ return false;
91
+ };
92
+ const pickNavigationGuard = (args) => {
93
+ const isPathnameChange = args.currentLocation.pathname !== args.nextLocation.pathname;
94
+ const isSearchChange = args.currentLocation.search !== args.nextLocation.search;
95
+ if (!isPathnameChange && !isSearchChange) {
96
+ return null;
97
+ }
98
+ const eligibleGuards = getNavigationGuards().filter((guard) => canBlockNavigation(guard, { isPathnameChange, isSearchChange })).filter((guard) => guard.shouldBlockNavigation(args));
99
+ if (eligibleGuards.length === 0) {
100
+ return null;
101
+ }
102
+ return eligibleGuards.reduce((best, guard) => {
103
+ const bestPriority = best.priority ?? DEFAULT_PRIORITY;
104
+ const guardPriority = guard.priority ?? DEFAULT_PRIORITY;
105
+ return guardPriority > bestPriority ? guard : best;
106
+ }, eligibleGuards[0]);
107
+ };
108
+ const getLocationDedupKey = (location) => location.key ?? `${location.pathname}${location.search}`;
109
+ const hasUnloadBlockers = () => getNavigationGuards().some((guard) => guard.enabled && guard.shouldBlockUnload);
110
+ const getWindowState = () => {
111
+ if (typeof window === "undefined") return null;
112
+ const key = "__rpcbaseNavigationGuardWindowState";
113
+ const globalAny = window;
114
+ if (globalAny[key]) {
115
+ return globalAny[key];
116
+ }
117
+ const created = {
118
+ suppressBeforeUnloadUntil: 0,
119
+ lastBeforeUnloadAt: 0
120
+ };
121
+ globalAny[key] = created;
122
+ return created;
123
+ };
124
+ const NATIVE_PROMPT_COOLDOWN_MS = 1e3;
125
+ const SUPPRESS_BEFOREUNLOAD_MS = 1e3;
126
+ const getGlobalGuardWeakSet = () => {
127
+ const key = "__rpcbaseNavigationGuardInstalledRouters";
128
+ const globalAny = globalThis;
129
+ const existing = globalAny[key];
130
+ if (existing && existing instanceof WeakSet) {
131
+ return existing;
132
+ }
133
+ const created = /* @__PURE__ */ new WeakSet();
134
+ globalAny[key] = created;
135
+ return created;
136
+ };
137
+ const installGlobalNavigationGuard = (router) => {
138
+ const installedRouters = getGlobalGuardWeakSet();
139
+ if (installedRouters.has(router)) {
140
+ return;
141
+ }
142
+ installedRouters.add(router);
143
+ const blockerKey = "rpcbase:navigation-guards";
144
+ const windowState = getWindowState();
145
+ let lastArgs = null;
146
+ let lastPromptedLocationKey = null;
147
+ router.getBlocker(blockerKey, (args) => {
148
+ lastArgs = args;
149
+ return pickNavigationGuard(args) !== null;
150
+ });
151
+ router.subscribe((state) => {
152
+ const blocker = state.blockers.get(blockerKey);
153
+ if (!blocker || blocker.state !== "blocked") {
154
+ lastPromptedLocationKey = null;
155
+ return;
156
+ }
157
+ const blockedLocation = blocker.location;
158
+ const dedupKey = getLocationDedupKey(blockedLocation);
159
+ if (lastPromptedLocationKey === dedupKey) {
160
+ return;
161
+ }
162
+ lastPromptedLocationKey = dedupKey;
163
+ if (windowState && Date.now() - windowState.lastBeforeUnloadAt < NATIVE_PROMPT_COOLDOWN_MS) {
164
+ blocker.reset();
165
+ return;
166
+ }
167
+ const args = lastArgs;
168
+ const guard = args ? pickNavigationGuard(args) : null;
169
+ if (!guard) {
170
+ blocker.proceed();
171
+ return;
172
+ }
173
+ const ok = window.confirm(guard.message);
174
+ if (ok) {
175
+ if (windowState) {
176
+ windowState.suppressBeforeUnloadUntil = Date.now() + SUPPRESS_BEFOREUNLOAD_MS;
177
+ }
178
+ blocker.proceed();
179
+ } else {
180
+ blocker.reset();
181
+ }
182
+ });
183
+ if (typeof window !== "undefined") {
184
+ const key = "__rpcbaseBeforeUnloadNavigationGuardInstalled";
185
+ const globalAny = window;
186
+ if (!globalAny[key]) {
187
+ globalAny[key] = true;
188
+ window.addEventListener("beforeunload", (event) => {
189
+ const state = getWindowState();
190
+ if (state && state.suppressBeforeUnloadUntil > 0 && Date.now() < state.suppressBeforeUnloadUntil) {
191
+ state.suppressBeforeUnloadUntil = 0;
192
+ return;
193
+ }
194
+ if (!hasUnloadBlockers()) {
195
+ return;
196
+ }
197
+ if (state) {
198
+ state.lastBeforeUnloadAt = Date.now();
199
+ }
200
+ event.preventDefault();
201
+ event.returnValue = "";
202
+ });
203
+ }
204
+ }
205
+ };
82
206
  const SSR_ERROR_STATE_GLOBAL_KEY = "__RPCBASE_SSR_ERROR__";
83
207
  const ESCAPED_LT = /</g;
84
208
  const ESCAPED_U2028 = /\u2028/g;
@@ -220,6 +344,7 @@ const initWithRoutes = async (routesElement, opts) => {
220
344
  const routes = createRoutesFromElements(routesElement);
221
345
  applyLoaderTimeouts(routes);
222
346
  const router = createBrowserRouter(routes, {});
347
+ installGlobalNavigationGuard(router);
223
348
  const toError = (error) => error instanceof Error ? error : new Error(String(error));
224
349
  const mentionsHydration = (value, depth = 0) => {
225
350
  if (depth > 5) {
@@ -1 +1 @@
1
- {"version":3,"file":"initWithRoutes.d.ts","sourceRoot":"","sources":["../src/initWithRoutes.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAmC,MAAM,OAAO,CAAA;AAElE,OAAO,EAAsB,wBAAwB,EAAiB,MAAM,iBAAiB,CAAA;AAM7F,OAAO,EAGL,oBAAoB,EACrB,MAAM,iBAAiB,CAAA;AAgFxB,KAAK,qBAAqB,GAAG;IAC3B,0BAA0B,CAAC,EAAE,OAAO,CAAA;IACpC,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,SAAS,CAAA;CAC9D,CAAA;AAED,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC,CAAC,CAAC,CAAA;AA0CnE,eAAO,MAAM,cAAc,GACzB,eAAe,aAAa,EAC5B,OAAO,qBAAqB,kBAqF7B,CAAA"}
1
+ {"version":3,"file":"initWithRoutes.d.ts","sourceRoot":"","sources":["../src/initWithRoutes.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAmC,MAAM,OAAO,CAAA;AAElE,OAAO,EAAsB,wBAAwB,EAAiB,MAAM,iBAAiB,CAAA;AAO7F,OAAO,EAGL,oBAAoB,EACrB,MAAM,iBAAiB,CAAA;AAgFxB,KAAK,qBAAqB,GAAG;IAC3B,0BAA0B,CAAC,EAAE,OAAO,CAAA;IACpC,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,SAAS,CAAA;CAC9D,CAAA;AAED,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC,CAAC,CAAC,CAAA;AA0CnE,eAAO,MAAM,cAAc,GACzB,eAAe,aAAa,EAC5B,OAAO,qBAAqB,kBAsF7B,CAAA"}
@@ -0,0 +1,3 @@
1
+ import { DataRouter } from '../../../router/src';
2
+ export declare const installGlobalNavigationGuard: (router: DataRouter) => void;
3
+ //# sourceMappingURL=installGlobalNavigationGuard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installGlobalNavigationGuard.d.ts","sourceRoot":"","sources":["../../src/navigationGuard/installGlobalNavigationGuard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAGvB,MAAM,iBAAiB,CAAA;AA2FxB,eAAO,MAAM,4BAA4B,GAAI,QAAQ,UAAU,KAAG,IAoFjE,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=installGlobalNavigationGuard.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installGlobalNavigationGuard.test.d.ts","sourceRoot":"","sources":["../../src/navigationGuard/installGlobalNavigationGuard.test.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpcbase/client",
3
- "version": "0.330.0",
3
+ "version": "0.332.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"