@antelopejs/dms-frontend 0.5.1 → 0.5.3
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/package.json
CHANGED
|
@@ -537,11 +537,54 @@ function parseRoute(runtime: DmsFrontendRuntime, url: string): DmsRoute {
|
|
|
537
537
|
};
|
|
538
538
|
}
|
|
539
539
|
|
|
540
|
+
function hasSameEntries(
|
|
541
|
+
left: Record<string, string>,
|
|
542
|
+
right: Record<string, string>,
|
|
543
|
+
): boolean {
|
|
544
|
+
const keys = Object.keys(left);
|
|
545
|
+
return (
|
|
546
|
+
keys.length === Object.keys(right).length &&
|
|
547
|
+
keys.every((key) => right[key] === left[key])
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Every parse builds a new query, params and matched: assigning them as they
|
|
552
|
+
// come would re-run each watcher of the route on every call of useDmsRoute,
|
|
553
|
+
// though the URL did not change. They are only replaced when their content did.
|
|
540
554
|
function updateRoute(runtime: DmsFrontendRuntime, url: string): void {
|
|
541
555
|
// The meta is the page's, not the URL's: a page declares it, then reads its
|
|
542
556
|
// route, and the middleware the meta selected must still find it.
|
|
543
|
-
const {
|
|
544
|
-
|
|
557
|
+
const {
|
|
558
|
+
meta: _pageMeta,
|
|
559
|
+
query,
|
|
560
|
+
params,
|
|
561
|
+
matched,
|
|
562
|
+
...location
|
|
563
|
+
} = parseRoute(runtime, url);
|
|
564
|
+
const route = runtime.route;
|
|
565
|
+
if (!hasSameEntries(query, route.query)) route.query = query;
|
|
566
|
+
if (!hasSameEntries(params, route.params)) route.params = params;
|
|
567
|
+
if (matched.length !== route.matched.length) route.matched = matched;
|
|
568
|
+
Object.assign(route, location);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const URL_BASE = "http://frontend.local";
|
|
572
|
+
|
|
573
|
+
// The URL the browser shows: it may be ahead of the route, which follows
|
|
574
|
+
// Inertia's page, when a component wrote it in place (history.replaceState).
|
|
575
|
+
function currentUrl(runtime: DmsFrontendRuntime): string {
|
|
576
|
+
if (runtime.isServer || typeof window === "undefined")
|
|
577
|
+
return new URL(runtime.route.fullPath, URL_BASE).href;
|
|
578
|
+
return window.location.href;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// One spelling per URL: `a b`, `a+b` and `a%20b` are the same query, as are
|
|
582
|
+
// `:` and `%3A`. The origin is kept, so a URL of another site never matches
|
|
583
|
+
// the current one, whatever its path.
|
|
584
|
+
function normalizeUrl(url: string, base: string): string {
|
|
585
|
+
const parsed = new URL(url, base);
|
|
586
|
+
const search = parsed.searchParams.toString();
|
|
587
|
+
return `${parsed.origin}${parsed.pathname}${search ? `?${search}` : ""}${parsed.hash}`;
|
|
545
588
|
}
|
|
546
589
|
|
|
547
590
|
function locationToUrl(
|
|
@@ -645,10 +688,13 @@ export function useDmsRoute(pattern?: string): DmsRoute {
|
|
|
645
688
|
|
|
646
689
|
export function useDmsRouter(): DmsRouter {
|
|
647
690
|
const runtime = useDmsRuntime();
|
|
648
|
-
const navigate = (to: RouteLocationRaw, options?: DmsNavigationOptions) =>
|
|
649
|
-
|
|
691
|
+
const navigate = (to: RouteLocationRaw, options?: DmsNavigationOptions) => {
|
|
692
|
+
const current = currentUrl(runtime);
|
|
693
|
+
return normalizeUrl(locationToUrl(runtime, to), current) ===
|
|
694
|
+
normalizeUrl(current, current)
|
|
650
695
|
? Promise.resolve()
|
|
651
696
|
: visit(runtime, to, options);
|
|
697
|
+
};
|
|
652
698
|
return {
|
|
653
699
|
currentRoute: computed(() => runtime.route),
|
|
654
700
|
push: navigate,
|
|
@@ -14,6 +14,32 @@ const MAX_AGE = 60 * 60 * 24 * 30;
|
|
|
14
14
|
const UUID =
|
|
15
15
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* A suffix that keeps this instance's cookies apart from another's on the same
|
|
19
|
+
* host. Browsers scope cookies by hostname, not port: without it, two DMS
|
|
20
|
+
* frontends on localhost:3000 and localhost:3001 would overwrite each other's
|
|
21
|
+
* session, each failing to decrypt the other's and signing the user out. The
|
|
22
|
+
* suffix follows the backend, so a restart keeps the cookies it wrote.
|
|
23
|
+
*/
|
|
24
|
+
function cookieScope() {
|
|
25
|
+
const backendUrl = (process.env.DMS_API_BASE_URL ?? "").trim();
|
|
26
|
+
let canonical = backendUrl;
|
|
27
|
+
try {
|
|
28
|
+
const url = new URL(backendUrl);
|
|
29
|
+
if (url.hostname === "localhost" || url.hostname === "[::1]")
|
|
30
|
+
url.hostname = "127.0.0.1";
|
|
31
|
+
canonical = url.toString().replace(/\/$/, "");
|
|
32
|
+
} catch {
|
|
33
|
+
// Not a URL: the raw string still tells two backends apart.
|
|
34
|
+
}
|
|
35
|
+
return createHash("sha256").update(canonical).digest("hex").slice(0, 12);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The name `name` takes in this instance's cookie jar. */
|
|
39
|
+
export function scopedCookieName(name) {
|
|
40
|
+
return `${name}_${cookieScope()}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
17
43
|
function key() {
|
|
18
44
|
const secret = process.env.DMS_SESSION_SECRET;
|
|
19
45
|
if (!secret || secret.length < 32)
|
|
@@ -54,10 +80,11 @@ function decode(value) {
|
|
|
54
80
|
}
|
|
55
81
|
|
|
56
82
|
function cookieValue(request, name) {
|
|
83
|
+
const scoped = scopedCookieName(name);
|
|
57
84
|
const cookies = request.headers.cookie?.split(";") ?? [];
|
|
58
85
|
return cookies
|
|
59
86
|
.map((part) => part.trim().split("="))
|
|
60
|
-
.find(([key]) => key ===
|
|
87
|
+
.find(([key]) => key === scoped)?.[1];
|
|
61
88
|
}
|
|
62
89
|
|
|
63
90
|
/**
|
|
@@ -83,7 +110,8 @@ function lifetime(persistent) {
|
|
|
83
110
|
return persistent ? MAX_AGE : undefined;
|
|
84
111
|
}
|
|
85
112
|
|
|
86
|
-
function appendCookie(response,
|
|
113
|
+
function appendCookie(response, name, value, cookieAttributes) {
|
|
114
|
+
const cookie = `${scopedCookieName(name)}=${value}; ${cookieAttributes}`;
|
|
87
115
|
const current = response.getHeader("set-cookie");
|
|
88
116
|
response.setHeader(
|
|
89
117
|
"set-cookie",
|
|
@@ -122,12 +150,14 @@ export function setRequestSession(request, session) {
|
|
|
122
150
|
export function writeSession(response, session) {
|
|
123
151
|
appendCookie(
|
|
124
152
|
response,
|
|
125
|
-
|
|
153
|
+
SESSION_COOKIE,
|
|
154
|
+
encode(session),
|
|
155
|
+
attributes(lifetime(isPersistent(session))),
|
|
126
156
|
);
|
|
127
157
|
}
|
|
128
158
|
|
|
129
159
|
export function clearSession(response) {
|
|
130
|
-
appendCookie(response,
|
|
160
|
+
appendCookie(response, SESSION_COOKIE, "", attributes(0));
|
|
131
161
|
}
|
|
132
162
|
|
|
133
163
|
export function readAccounts(request) {
|
|
@@ -139,7 +169,9 @@ function writeAccountIndex(response, accounts) {
|
|
|
139
169
|
const persistent = Object.values(accounts).some(isPersistent);
|
|
140
170
|
appendCookie(
|
|
141
171
|
response,
|
|
142
|
-
|
|
172
|
+
ACCOUNT_INDEX_COOKIE,
|
|
173
|
+
encode(accounts),
|
|
174
|
+
attributes(lifetime(persistent)),
|
|
143
175
|
);
|
|
144
176
|
}
|
|
145
177
|
|
|
@@ -156,7 +188,9 @@ export function storeAccount(request, response, session, accountId) {
|
|
|
156
188
|
writeAccountIndex(response, accounts);
|
|
157
189
|
appendCookie(
|
|
158
190
|
response,
|
|
159
|
-
|
|
191
|
+
accountCookieName(id),
|
|
192
|
+
encode({ refreshToken: session.refreshToken, ...accountDescriptor }),
|
|
193
|
+
attributes(lifetime(accountDescriptor.persistent)),
|
|
160
194
|
);
|
|
161
195
|
return accountDescriptor;
|
|
162
196
|
}
|
|
@@ -186,10 +220,7 @@ export function removeAccount(request, response, accountId) {
|
|
|
186
220
|
delete accounts[accountId];
|
|
187
221
|
writeAccountIndex(response, accounts);
|
|
188
222
|
if (UUID.test(accountId ?? ""))
|
|
189
|
-
appendCookie(
|
|
190
|
-
response,
|
|
191
|
-
`${accountCookieName(accountId)}=; ${attributes(0)}`,
|
|
192
|
-
);
|
|
223
|
+
appendCookie(response, accountCookieName(accountId), "", attributes(0));
|
|
193
224
|
}
|
|
194
225
|
|
|
195
226
|
export function readSealedCookie(request, name) {
|
|
@@ -204,12 +235,9 @@ export function writeSealedCookie(
|
|
|
204
235
|
maxAge,
|
|
205
236
|
path = "/auth/oauth",
|
|
206
237
|
) {
|
|
207
|
-
appendCookie(
|
|
208
|
-
response,
|
|
209
|
-
`${name}=${encode(value)}; ${attributes(maxAge, path)}`,
|
|
210
|
-
);
|
|
238
|
+
appendCookie(response, name, encode(value), attributes(maxAge, path));
|
|
211
239
|
}
|
|
212
240
|
|
|
213
241
|
export function clearSealedCookie(response, name, path = "/auth/oauth") {
|
|
214
|
-
appendCookie(response,
|
|
242
|
+
appendCookie(response, name, "", attributes(0, path));
|
|
215
243
|
}
|