@tanstack/router-core 1.167.0 → 1.167.2
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/cjs/Matches.cjs +15 -12
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/cjs/config.cjs +9 -8
- package/dist/cjs/config.cjs.map +1 -1
- package/dist/cjs/defer.cjs +37 -21
- package/dist/cjs/defer.cjs.map +1 -1
- package/dist/cjs/index.cjs +87 -89
- package/dist/cjs/isServer/client.cjs +5 -3
- package/dist/cjs/isServer/client.cjs.map +1 -1
- package/dist/cjs/isServer/development.cjs +5 -3
- package/dist/cjs/isServer/development.cjs.map +1 -1
- package/dist/cjs/isServer/server.cjs +5 -3
- package/dist/cjs/isServer/server.cjs.map +1 -1
- package/dist/cjs/link.cjs +5 -4
- package/dist/cjs/link.cjs.map +1 -1
- package/dist/cjs/load-matches.cjs +619 -766
- package/dist/cjs/load-matches.cjs.map +1 -1
- package/dist/cjs/lru-cache.cjs +67 -64
- package/dist/cjs/lru-cache.cjs.map +1 -1
- package/dist/cjs/new-process-route-tree.cjs +707 -792
- package/dist/cjs/new-process-route-tree.cjs.map +1 -1
- package/dist/cjs/not-found.cjs +20 -7
- package/dist/cjs/not-found.cjs.map +1 -1
- package/dist/cjs/path.cjs +221 -232
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/qss.cjs +62 -28
- package/dist/cjs/qss.cjs.map +1 -1
- package/dist/cjs/redirect.cjs +44 -30
- package/dist/cjs/redirect.cjs.map +1 -1
- package/dist/cjs/rewrite.cjs +56 -56
- package/dist/cjs/rewrite.cjs.map +1 -1
- package/dist/cjs/root.cjs +6 -4
- package/dist/cjs/root.cjs.map +1 -1
- package/dist/cjs/route.cjs +96 -105
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/router.cjs +1153 -1524
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +2 -0
- package/dist/cjs/scroll-restoration.cjs +189 -207
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/searchMiddleware.cjs +48 -37
- package/dist/cjs/searchMiddleware.cjs.map +1 -1
- package/dist/cjs/searchParams.cjs +57 -45
- package/dist/cjs/searchParams.cjs.map +1 -1
- package/dist/cjs/ssr/client.cjs +6 -8
- package/dist/cjs/ssr/constants.cjs +6 -5
- package/dist/cjs/ssr/constants.cjs.map +1 -1
- package/dist/cjs/ssr/createRequestHandler.cjs +41 -59
- package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
- package/dist/cjs/ssr/handlerCallback.cjs +5 -4
- package/dist/cjs/ssr/handlerCallback.cjs.map +1 -1
- package/dist/cjs/ssr/headers.cjs +17 -26
- package/dist/cjs/ssr/headers.cjs.map +1 -1
- package/dist/cjs/ssr/json.cjs +8 -4
- package/dist/cjs/ssr/json.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/RawStream.cjs +268 -268
- package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs +31 -32
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs +12 -12
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -1
- package/dist/cjs/ssr/serializer/transformer.cjs +45 -41
- package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -1
- package/dist/cjs/ssr/server.cjs +12 -14
- package/dist/cjs/ssr/ssr-client.cjs +173 -211
- package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-match-id.cjs +6 -5
- package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.cjs +266 -300
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
- package/dist/cjs/ssr/transformStreamWithRouter.cjs +317 -337
- package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
- package/dist/cjs/ssr/tsrScript.cjs +6 -4
- package/dist/cjs/ssr/tsrScript.cjs.map +1 -1
- package/dist/cjs/ssr/tsrScript.d.cts +1 -0
- package/dist/cjs/utils/batch.cjs +13 -13
- package/dist/cjs/utils/batch.cjs.map +1 -1
- package/dist/cjs/utils.cjs +274 -208
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/esm/Matches.js +16 -13
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/config.js +10 -9
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/defer.js +37 -22
- package/dist/esm/defer.js.map +1 -1
- package/dist/esm/index.js +12 -82
- package/dist/esm/isServer/client.js +6 -5
- package/dist/esm/isServer/client.js.map +1 -1
- package/dist/esm/isServer/development.js +6 -5
- package/dist/esm/isServer/development.js.map +1 -1
- package/dist/esm/isServer/server.js +6 -5
- package/dist/esm/isServer/server.js.map +1 -1
- package/dist/esm/link.js +6 -5
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/load-matches.js +614 -765
- package/dist/esm/load-matches.js.map +1 -1
- package/dist/esm/lru-cache.js +68 -65
- package/dist/esm/lru-cache.js.map +1 -1
- package/dist/esm/new-process-route-tree.js +705 -797
- package/dist/esm/new-process-route-tree.js.map +1 -1
- package/dist/esm/not-found.js +21 -9
- package/dist/esm/not-found.js.map +1 -1
- package/dist/esm/path.js +220 -241
- package/dist/esm/path.js.map +1 -1
- package/dist/esm/qss.js +63 -30
- package/dist/esm/qss.js.map +1 -1
- package/dist/esm/redirect.js +45 -34
- package/dist/esm/redirect.js.map +1 -1
- package/dist/esm/rewrite.js +57 -60
- package/dist/esm/rewrite.js.map +1 -1
- package/dist/esm/root.js +7 -5
- package/dist/esm/root.js.map +1 -1
- package/dist/esm/route.js +92 -105
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +2 -0
- package/dist/esm/router.js +1147 -1527
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration.js +188 -213
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/searchMiddleware.js +48 -38
- package/dist/esm/searchMiddleware.js.map +1 -1
- package/dist/esm/searchParams.js +57 -48
- package/dist/esm/searchParams.js.map +1 -1
- package/dist/esm/ssr/client.js +1 -6
- package/dist/esm/ssr/constants.js +7 -7
- package/dist/esm/ssr/constants.js.map +1 -1
- package/dist/esm/ssr/createRequestHandler.js +39 -58
- package/dist/esm/ssr/createRequestHandler.js.map +1 -1
- package/dist/esm/ssr/handlerCallback.js +6 -5
- package/dist/esm/ssr/handlerCallback.js.map +1 -1
- package/dist/esm/ssr/headers.js +16 -26
- package/dist/esm/ssr/headers.js.map +1 -1
- package/dist/esm/ssr/json.js +9 -5
- package/dist/esm/ssr/json.js.map +1 -1
- package/dist/esm/ssr/serializer/RawStream.js +267 -273
- package/dist/esm/ssr/serializer/RawStream.js.map +1 -1
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js +31 -32
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -1
- package/dist/esm/ssr/serializer/seroval-plugins.js +10 -11
- package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -1
- package/dist/esm/ssr/serializer/transformer.js +44 -43
- package/dist/esm/ssr/serializer/transformer.js.map +1 -1
- package/dist/esm/ssr/server.js +2 -12
- package/dist/esm/ssr/ssr-client.js +169 -209
- package/dist/esm/ssr/ssr-client.js.map +1 -1
- package/dist/esm/ssr/ssr-match-id.js +7 -7
- package/dist/esm/ssr/ssr-match-id.js.map +1 -1
- package/dist/esm/ssr/ssr-server.js +262 -300
- package/dist/esm/ssr/ssr-server.js.map +1 -1
- package/dist/esm/ssr/transformStreamWithRouter.js +315 -338
- package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
- package/dist/esm/ssr/tsrScript.js +6 -5
- package/dist/esm/ssr/tsrScript.js.map +1 -1
- package/dist/esm/utils/batch.js +13 -14
- package/dist/esm/utils/batch.js.map +1 -1
- package/dist/esm/utils.js +273 -224
- package/dist/esm/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/router.ts +2 -0
- package/dist/cjs/index.cjs.map +0 -1
- package/dist/cjs/ssr/client.cjs.map +0 -1
- package/dist/cjs/ssr/server.cjs.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/ssr/client.js.map +0 -1
- package/dist/esm/ssr/server.js.map +0 -1
package/dist/esm/utils.js
CHANGED
|
@@ -1,267 +1,316 @@
|
|
|
1
1
|
import { isServer } from "@tanstack/router-core/isServer";
|
|
2
|
+
//#region src/utils.ts
|
|
3
|
+
/**
|
|
4
|
+
* Return the last element of an array.
|
|
5
|
+
* Intended for non-empty arrays used within router internals.
|
|
6
|
+
*/
|
|
2
7
|
function last(arr) {
|
|
3
|
-
|
|
8
|
+
return arr[arr.length - 1];
|
|
4
9
|
}
|
|
5
10
|
function isFunction(d) {
|
|
6
|
-
|
|
11
|
+
return typeof d === "function";
|
|
7
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Apply a value-or-updater to a previous value.
|
|
15
|
+
* Accepts either a literal value or a function of the previous value.
|
|
16
|
+
*/
|
|
8
17
|
function functionalUpdate(updater, previous) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
return updater;
|
|
18
|
+
if (isFunction(updater)) return updater(previous);
|
|
19
|
+
return updater;
|
|
13
20
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
var hasOwn = Object.prototype.hasOwnProperty;
|
|
22
|
+
var isEnumerable = Object.prototype.propertyIsEnumerable;
|
|
23
|
+
var createNull = () => Object.create(null);
|
|
24
|
+
var nullReplaceEqualDeep = (prev, next) => replaceEqualDeep(prev, next, createNull);
|
|
25
|
+
/**
|
|
26
|
+
* This function returns `prev` if `_next` is deeply equal.
|
|
27
|
+
* If not, it will replace any deeply equal children of `b` with those of `a`.
|
|
28
|
+
* This can be used for structural sharing between immutable JSON values for example.
|
|
29
|
+
* Do not use this with signals
|
|
30
|
+
*/
|
|
18
31
|
function replaceEqualDeep(prev, _next, _makeObj = () => ({}), _depth = 0) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
copy[key] = v;
|
|
52
|
-
if (v === p) equalItems++;
|
|
53
|
-
}
|
|
54
|
-
return prevSize === nextSize && equalItems === prevSize ? prev : copy;
|
|
32
|
+
if (isServer) return _next;
|
|
33
|
+
if (prev === _next) return prev;
|
|
34
|
+
if (_depth > 500) return _next;
|
|
35
|
+
const next = _next;
|
|
36
|
+
const array = isPlainArray(prev) && isPlainArray(next);
|
|
37
|
+
if (!array && !(isPlainObject(prev) && isPlainObject(next))) return next;
|
|
38
|
+
const prevItems = array ? prev : getEnumerableOwnKeys(prev);
|
|
39
|
+
if (!prevItems) return next;
|
|
40
|
+
const nextItems = array ? next : getEnumerableOwnKeys(next);
|
|
41
|
+
if (!nextItems) return next;
|
|
42
|
+
const prevSize = prevItems.length;
|
|
43
|
+
const nextSize = nextItems.length;
|
|
44
|
+
const copy = array ? new Array(nextSize) : _makeObj();
|
|
45
|
+
let equalItems = 0;
|
|
46
|
+
for (let i = 0; i < nextSize; i++) {
|
|
47
|
+
const key = array ? i : nextItems[i];
|
|
48
|
+
const p = prev[key];
|
|
49
|
+
const n = next[key];
|
|
50
|
+
if (p === n) {
|
|
51
|
+
copy[key] = p;
|
|
52
|
+
if (array ? i < prevSize : hasOwn.call(prev, key)) equalItems++;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (p === null || n === null || typeof p !== "object" || typeof n !== "object") {
|
|
56
|
+
copy[key] = n;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const v = replaceEqualDeep(p, n, _makeObj, _depth + 1);
|
|
60
|
+
copy[key] = v;
|
|
61
|
+
if (v === p) equalItems++;
|
|
62
|
+
}
|
|
63
|
+
return prevSize === nextSize && equalItems === prevSize ? prev : copy;
|
|
55
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Equivalent to `Reflect.ownKeys`, but ensures that objects are "clone-friendly":
|
|
67
|
+
* will return false if object has any non-enumerable properties.
|
|
68
|
+
*
|
|
69
|
+
* Optimized for the common case where objects have no symbol properties.
|
|
70
|
+
*/
|
|
56
71
|
function getEnumerableOwnKeys(o) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
return keys;
|
|
72
|
+
const names = Object.getOwnPropertyNames(o);
|
|
73
|
+
for (const name of names) if (!isEnumerable.call(o, name)) return false;
|
|
74
|
+
const symbols = Object.getOwnPropertySymbols(o);
|
|
75
|
+
if (symbols.length === 0) return names;
|
|
76
|
+
const keys = names;
|
|
77
|
+
for (const symbol of symbols) {
|
|
78
|
+
if (!isEnumerable.call(o, symbol)) return false;
|
|
79
|
+
keys.push(symbol);
|
|
80
|
+
}
|
|
81
|
+
return keys;
|
|
69
82
|
}
|
|
70
83
|
function isPlainObject(o) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const prot = ctor.prototype;
|
|
79
|
-
if (!hasObjectPrototype(prot)) {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
if (!prot.hasOwnProperty("isPrototypeOf")) {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
return true;
|
|
84
|
+
if (!hasObjectPrototype(o)) return false;
|
|
85
|
+
const ctor = o.constructor;
|
|
86
|
+
if (typeof ctor === "undefined") return true;
|
|
87
|
+
const prot = ctor.prototype;
|
|
88
|
+
if (!hasObjectPrototype(prot)) return false;
|
|
89
|
+
if (!prot.hasOwnProperty("isPrototypeOf")) return false;
|
|
90
|
+
return true;
|
|
86
91
|
}
|
|
87
92
|
function hasObjectPrototype(o) {
|
|
88
|
-
|
|
93
|
+
return Object.prototype.toString.call(o) === "[object Object]";
|
|
89
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Check if a value is a "plain" array (no extra enumerable keys).
|
|
97
|
+
*/
|
|
90
98
|
function isPlainArray(value) {
|
|
91
|
-
|
|
99
|
+
return Array.isArray(value) && value.length === Object.keys(value).length;
|
|
92
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Perform a deep equality check with options for partial comparison and
|
|
103
|
+
* ignoring `undefined` values. Optimized for router state comparisons.
|
|
104
|
+
*/
|
|
93
105
|
function deepEqual(a, b, opts) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
} else {
|
|
121
|
-
for (const k in a) {
|
|
122
|
-
if (a[k] !== void 0) aCount++;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
let bCount = 0;
|
|
126
|
-
for (const k in b) {
|
|
127
|
-
if (!ignoreUndefined || b[k] !== void 0) {
|
|
128
|
-
bCount++;
|
|
129
|
-
if (bCount > aCount || !deepEqual(a[k], b[k], opts)) return false;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return aCount === bCount;
|
|
133
|
-
}
|
|
134
|
-
return false;
|
|
106
|
+
if (a === b) return true;
|
|
107
|
+
if (typeof a !== typeof b) return false;
|
|
108
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
109
|
+
if (a.length !== b.length) return false;
|
|
110
|
+
for (let i = 0, l = a.length; i < l; i++) if (!deepEqual(a[i], b[i], opts)) return false;
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
114
|
+
const ignoreUndefined = opts?.ignoreUndefined ?? true;
|
|
115
|
+
if (opts?.partial) {
|
|
116
|
+
for (const k in b) if (!ignoreUndefined || b[k] !== void 0) {
|
|
117
|
+
if (!deepEqual(a[k], b[k], opts)) return false;
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
let aCount = 0;
|
|
122
|
+
if (!ignoreUndefined) aCount = Object.keys(a).length;
|
|
123
|
+
else for (const k in a) if (a[k] !== void 0) aCount++;
|
|
124
|
+
let bCount = 0;
|
|
125
|
+
for (const k in b) if (!ignoreUndefined || b[k] !== void 0) {
|
|
126
|
+
bCount++;
|
|
127
|
+
if (bCount > aCount || !deepEqual(a[k], b[k], opts)) return false;
|
|
128
|
+
}
|
|
129
|
+
return aCount === bCount;
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
135
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Create a promise with exposed resolve/reject and status fields.
|
|
135
|
+
* Useful for coordinating async router lifecycle operations.
|
|
136
|
+
*/
|
|
136
137
|
function createControlledPromise(onResolve) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
138
|
+
let resolveLoadPromise;
|
|
139
|
+
let rejectLoadPromise;
|
|
140
|
+
const controlledPromise = new Promise((resolve, reject) => {
|
|
141
|
+
resolveLoadPromise = resolve;
|
|
142
|
+
rejectLoadPromise = reject;
|
|
143
|
+
});
|
|
144
|
+
controlledPromise.status = "pending";
|
|
145
|
+
controlledPromise.resolve = (value) => {
|
|
146
|
+
controlledPromise.status = "resolved";
|
|
147
|
+
controlledPromise.value = value;
|
|
148
|
+
resolveLoadPromise(value);
|
|
149
|
+
onResolve?.(value);
|
|
150
|
+
};
|
|
151
|
+
controlledPromise.reject = (e) => {
|
|
152
|
+
controlledPromise.status = "rejected";
|
|
153
|
+
rejectLoadPromise(e);
|
|
154
|
+
};
|
|
155
|
+
return controlledPromise;
|
|
155
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Heuristically detect dynamic import "module not found" errors
|
|
159
|
+
* across major browsers for lazy route component handling.
|
|
160
|
+
*/
|
|
156
161
|
function isModuleNotFoundError(error) {
|
|
157
|
-
|
|
158
|
-
|
|
162
|
+
if (typeof error?.message !== "string") return false;
|
|
163
|
+
return error.message.startsWith("Failed to fetch dynamically imported module") || error.message.startsWith("error loading dynamically imported module") || error.message.startsWith("Importing a module script failed");
|
|
159
164
|
}
|
|
160
165
|
function isPromise(value) {
|
|
161
|
-
|
|
162
|
-
value && typeof value === "object" && typeof value.then === "function"
|
|
163
|
-
);
|
|
166
|
+
return Boolean(value && typeof value === "object" && typeof value.then === "function");
|
|
164
167
|
}
|
|
165
168
|
function findLast(array, predicate) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return void 0;
|
|
169
|
+
for (let i = array.length - 1; i >= 0; i--) {
|
|
170
|
+
const item = array[i];
|
|
171
|
+
if (predicate(item)) return item;
|
|
172
|
+
}
|
|
171
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Remove control characters that can cause open redirect vulnerabilities.
|
|
176
|
+
* Characters like \r (CR) and \n (LF) can trick URL parsers into interpreting
|
|
177
|
+
* paths like "/\r/evil.com" as "http://evil.com".
|
|
178
|
+
*/
|
|
172
179
|
function sanitizePathSegment(segment) {
|
|
173
|
-
|
|
180
|
+
return segment.replace(/[\x00-\x1f\x7f]/g, "");
|
|
174
181
|
}
|
|
175
182
|
function decodeSegment(segment) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
let decoded;
|
|
184
|
+
try {
|
|
185
|
+
decoded = decodeURI(segment);
|
|
186
|
+
} catch {
|
|
187
|
+
decoded = segment.replaceAll(/%[0-9A-F]{2}/gi, (match) => {
|
|
188
|
+
try {
|
|
189
|
+
return decodeURI(match);
|
|
190
|
+
} catch {
|
|
191
|
+
return match;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return sanitizePathSegment(decoded);
|
|
189
196
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Default list of URL protocols to allow in links, redirects, and navigation.
|
|
199
|
+
* Any absolute URL protocol not in this list is treated as dangerous by default.
|
|
200
|
+
*/
|
|
201
|
+
var DEFAULT_PROTOCOL_ALLOWLIST = [
|
|
202
|
+
"http:",
|
|
203
|
+
"https:",
|
|
204
|
+
"mailto:",
|
|
205
|
+
"tel:"
|
|
197
206
|
];
|
|
207
|
+
/**
|
|
208
|
+
* Check if a URL string uses a protocol that is not in the allowlist.
|
|
209
|
+
* Returns true for blocked protocols like javascript:, blob:, data:, etc.
|
|
210
|
+
*
|
|
211
|
+
* The URL constructor correctly normalizes:
|
|
212
|
+
* - Mixed case (JavaScript: → javascript:)
|
|
213
|
+
* - Whitespace/control characters (java\nscript: → javascript:)
|
|
214
|
+
* - Leading whitespace
|
|
215
|
+
*
|
|
216
|
+
* For relative URLs (no protocol), returns false (safe).
|
|
217
|
+
*
|
|
218
|
+
* @param url - The URL string to check
|
|
219
|
+
* @param allowlist - Set of protocols to allow
|
|
220
|
+
* @returns true if the URL uses a protocol that is not allowed
|
|
221
|
+
*/
|
|
198
222
|
function isDangerousProtocol(url, allowlist) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
223
|
+
if (!url) return false;
|
|
224
|
+
try {
|
|
225
|
+
const parsed = new URL(url);
|
|
226
|
+
return !allowlist.has(parsed.protocol);
|
|
227
|
+
} catch {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
206
230
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
231
|
+
var HTML_ESCAPE_LOOKUP = {
|
|
232
|
+
"&": "\\u0026",
|
|
233
|
+
">": "\\u003e",
|
|
234
|
+
"<": "\\u003c",
|
|
235
|
+
"\u2028": "\\u2028",
|
|
236
|
+
"\u2029": "\\u2029"
|
|
213
237
|
};
|
|
214
|
-
|
|
238
|
+
var HTML_ESCAPE_REGEX = /[&><\u2028\u2029]/g;
|
|
239
|
+
/**
|
|
240
|
+
* Escape HTML special characters in a string to prevent XSS attacks
|
|
241
|
+
* when embedding strings in script tags during SSR.
|
|
242
|
+
*
|
|
243
|
+
* This is essential for preventing XSS vulnerabilities when user-controlled
|
|
244
|
+
* content is embedded in inline scripts.
|
|
245
|
+
*/
|
|
215
246
|
function escapeHtml(str) {
|
|
216
|
-
|
|
247
|
+
return str.replace(HTML_ESCAPE_REGEX, (match) => HTML_ESCAPE_LOOKUP[match]);
|
|
217
248
|
}
|
|
218
249
|
function decodePath(path) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
250
|
+
if (!path) return {
|
|
251
|
+
path,
|
|
252
|
+
handledProtocolRelativeURL: false
|
|
253
|
+
};
|
|
254
|
+
if (!/[%\\\x00-\x1f\x7f]/.test(path) && !path.startsWith("//")) return {
|
|
255
|
+
path,
|
|
256
|
+
handledProtocolRelativeURL: false
|
|
257
|
+
};
|
|
258
|
+
const re = /%25|%5C/gi;
|
|
259
|
+
let cursor = 0;
|
|
260
|
+
let result = "";
|
|
261
|
+
let match;
|
|
262
|
+
while (null !== (match = re.exec(path))) {
|
|
263
|
+
result += decodeSegment(path.slice(cursor, match.index)) + match[0];
|
|
264
|
+
cursor = re.lastIndex;
|
|
265
|
+
}
|
|
266
|
+
result = result + decodeSegment(cursor ? path.slice(cursor) : path);
|
|
267
|
+
let handledProtocolRelativeURL = false;
|
|
268
|
+
if (result.startsWith("//")) {
|
|
269
|
+
handledProtocolRelativeURL = true;
|
|
270
|
+
result = "/" + result.replace(/^\/+/, "");
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
path: result,
|
|
274
|
+
handledProtocolRelativeURL
|
|
275
|
+
};
|
|
238
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Encodes a path the same way `new URL()` would, but without the overhead of full URL parsing.
|
|
279
|
+
*
|
|
280
|
+
* This function encodes:
|
|
281
|
+
* - Whitespace characters (spaces → %20, tabs → %09, etc.)
|
|
282
|
+
* - Non-ASCII/Unicode characters (emojis, accented characters, etc.)
|
|
283
|
+
*
|
|
284
|
+
* It preserves:
|
|
285
|
+
* - Already percent-encoded sequences (won't double-encode %2F, %25, etc.)
|
|
286
|
+
* - ASCII special characters valid in URL paths (@, $, &, +, etc.)
|
|
287
|
+
* - Forward slashes as path separators
|
|
288
|
+
*
|
|
289
|
+
* Used to generate proper href values for SSR without constructing URL objects.
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* encodePathLikeUrl('/path/file name.pdf') // '/path/file%20name.pdf'
|
|
293
|
+
* encodePathLikeUrl('/path/日本語') // '/path/%E6%97%A5%E6%9C%AC%E8%AA%9E'
|
|
294
|
+
* encodePathLikeUrl('/path/already%20encoded') // '/path/already%20encoded' (preserved)
|
|
295
|
+
*/
|
|
239
296
|
function encodePathLikeUrl(path) {
|
|
240
|
-
|
|
241
|
-
|
|
297
|
+
if (!/\s|[^\u0000-\u007F]/.test(path)) return path;
|
|
298
|
+
return path.replace(/\s|[^\u0000-\u007F]/gu, encodeURIComponent);
|
|
242
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* Builds the dev-mode CSS styles URL for route-scoped CSS collection.
|
|
302
|
+
* Used by HeadContent components in all framework implementations to construct
|
|
303
|
+
* the URL for the `/@tanstack-start/styles.css` endpoint.
|
|
304
|
+
*
|
|
305
|
+
* @param basepath - The router's basepath (may or may not have leading slash)
|
|
306
|
+
* @param routeIds - Array of matched route IDs to include in the CSS collection
|
|
307
|
+
* @returns The full URL path for the dev styles CSS endpoint
|
|
308
|
+
*/
|
|
243
309
|
function buildDevStylesUrl(basepath, routeIds) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
return `${normalizedBasepath}/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(","))}`;
|
|
310
|
+
const trimmedBasepath = basepath.replace(/^\/+|\/+$/g, "");
|
|
311
|
+
return `${trimmedBasepath === "" ? "" : `/${trimmedBasepath}`}/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(","))}`;
|
|
247
312
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
decodePath,
|
|
253
|
-
deepEqual,
|
|
254
|
-
encodePathLikeUrl,
|
|
255
|
-
escapeHtml,
|
|
256
|
-
findLast,
|
|
257
|
-
functionalUpdate,
|
|
258
|
-
isDangerousProtocol,
|
|
259
|
-
isModuleNotFoundError,
|
|
260
|
-
isPlainArray,
|
|
261
|
-
isPlainObject,
|
|
262
|
-
isPromise,
|
|
263
|
-
last,
|
|
264
|
-
nullReplaceEqualDeep,
|
|
265
|
-
replaceEqualDeep
|
|
266
|
-
};
|
|
267
|
-
//# sourceMappingURL=utils.js.map
|
|
313
|
+
//#endregion
|
|
314
|
+
export { DEFAULT_PROTOCOL_ALLOWLIST, buildDevStylesUrl, createControlledPromise, decodePath, deepEqual, encodePathLikeUrl, escapeHtml, findLast, functionalUpdate, isDangerousProtocol, isModuleNotFoundError, isPlainArray, isPlainObject, isPromise, last, nullReplaceEqualDeep, replaceEqualDeep };
|
|
315
|
+
|
|
316
|
+
//# sourceMappingURL=utils.js.map
|