oidc-spa 8.3.5 → 8.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.
- package/core/StateDataCookie.d.ts +34 -0
- package/core/StateDataCookie.js +142 -0
- package/core/StateDataCookie.js.map +1 -0
- package/core/createOidc.js +23 -5
- package/core/createOidc.js.map +1 -1
- package/core/earlyInit.d.ts +0 -1
- package/core/earlyInit.js +4 -13
- package/core/earlyInit.js.map +1 -1
- package/core/loginOrGoToAuthServer.d.ts +1 -0
- package/core/loginOrGoToAuthServer.js +20 -9
- package/core/loginOrGoToAuthServer.js.map +1 -1
- package/entrypoint.d.ts +0 -1
- package/entrypoint.js +1 -3
- package/entrypoint.js.map +1 -1
- package/esm/core/StateDataCookie.d.ts +34 -0
- package/esm/core/StateDataCookie.js +135 -0
- package/esm/core/StateDataCookie.js.map +1 -0
- package/esm/core/createOidc.js +23 -5
- package/esm/core/createOidc.js.map +1 -1
- package/esm/core/earlyInit.d.ts +0 -1
- package/esm/core/earlyInit.js +4 -13
- package/esm/core/earlyInit.js.map +1 -1
- package/esm/core/loginOrGoToAuthServer.d.ts +1 -0
- package/esm/core/loginOrGoToAuthServer.js +20 -9
- package/esm/core/loginOrGoToAuthServer.js.map +1 -1
- package/esm/entrypoint.d.ts +0 -1
- package/esm/entrypoint.js +0 -1
- package/esm/entrypoint.js.map +1 -1
- package/esm/tanstack-start/react/createOidcSpaApi.js +2 -0
- package/esm/tanstack-start/react/createOidcSpaApi.js.map +1 -1
- package/esm/tanstack-start/react/index.d.ts +1 -1
- package/esm/tanstack-start/react/index.js +1 -1
- package/esm/tanstack-start/react/index.js.map +1 -1
- package/esm/tanstack-start/react/withOidcSpaServerEntry.d.ts +5 -0
- package/esm/tanstack-start/react/withOidcSpaServerEntry.js +38 -0
- package/esm/tanstack-start/react/withOidcSpaServerEntry.js.map +1 -0
- package/package.json +1 -1
- package/src/core/StateDataCookie.ts +217 -0
- package/src/core/createOidc.ts +32 -4
- package/src/core/earlyInit.ts +3 -17
- package/src/core/loginOrGoToAuthServer.ts +25 -9
- package/src/entrypoint.ts +0 -1
- package/src/tanstack-start/react/createOidcSpaApi.tsx +3 -0
- package/src/tanstack-start/react/index.ts +1 -1
- package/src/tanstack-start/react/withOidcSpaServerEntry.ts +60 -0
- package/src/vite-plugin/handleClientEntrypoint.ts +10 -67
- package/src/vite-plugin/handleServerEntrypoint.ts +129 -0
- package/src/vite-plugin/transformTanstackRouterCreateFileRoute.ts +0 -64
- package/src/vite-plugin/utils.ts +64 -0
- package/src/vite-plugin/vite-plugin.ts +31 -10
- package/vite-plugin/handleClientEntrypoint.d.ts +7 -5
- package/vite-plugin/handleClientEntrypoint.js +16 -62
- package/vite-plugin/handleClientEntrypoint.js.map +1 -1
- package/vite-plugin/handleServerEntrypoint.d.ts +12 -0
- package/vite-plugin/handleServerEntrypoint.js +113 -0
- package/vite-plugin/handleServerEntrypoint.js.map +1 -0
- package/vite-plugin/transformTanstackRouterCreateFileRoute.js +0 -39
- package/vite-plugin/transformTanstackRouterCreateFileRoute.js.map +1 -1
- package/vite-plugin/utils.d.ts +12 -0
- package/vite-plugin/utils.js +88 -0
- package/vite-plugin/utils.js.map +1 -0
- package/vite-plugin/vite-plugin.d.ts +1 -1
- package/vite-plugin/vite-plugin.js +21 -5
- package/vite-plugin/vite-plugin.js.map +1 -1
- package/core/requiredPostHydrationReplaceNavigationUrl.d.ts +0 -6
- package/core/requiredPostHydrationReplaceNavigationUrl.js +0 -12
- package/core/requiredPostHydrationReplaceNavigationUrl.js.map +0 -1
- package/esm/core/requiredPostHydrationReplaceNavigationUrl.d.ts +0 -6
- package/esm/core/requiredPostHydrationReplaceNavigationUrl.js +0 -8
- package/esm/core/requiredPostHydrationReplaceNavigationUrl.js.map +0 -1
- package/esm/tanstack-start/react/withHandlingOidcPostLoginNavigation.d.ts +0 -2
- package/esm/tanstack-start/react/withHandlingOidcPostLoginNavigation.js +0 -36
- package/esm/tanstack-start/react/withHandlingOidcPostLoginNavigation.js.map +0 -1
- package/src/core/requiredPostHydrationReplaceNavigationUrl.ts +0 -11
- package/src/tanstack-start/react/withHandlingOidcPostLoginNavigation.tsx +0 -46
package/src/core/earlyInit.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getStateData, getIsStatQueryParamValue } from "./StateData";
|
|
2
2
|
import { assert, type Equals } from "../tools/tsafe/assert";
|
|
3
3
|
import type { AuthResponse } from "./AuthResponse";
|
|
4
|
-
import { setOidcRequiredPostHydrationReplaceNavigationUrl } from "./requiredPostHydrationReplaceNavigationUrl";
|
|
5
4
|
import { setBASE_URL } from "./BASE_URL";
|
|
6
5
|
import { resolvePrShouldLoadApp } from "./prShouldLoadApp";
|
|
7
6
|
import { isBrowser } from "../tools/isBrowser";
|
|
@@ -17,7 +16,6 @@ export function oidcEarlyInit(params: {
|
|
|
17
16
|
freezeWebSocket?: boolean;
|
|
18
17
|
freezePromise?: boolean;
|
|
19
18
|
safeMode?: boolean;
|
|
20
|
-
isPostLoginRedirectManual?: boolean;
|
|
21
19
|
BASE_URL?: string;
|
|
22
20
|
}) {
|
|
23
21
|
if (hasEarlyInitBeenCalled) {
|
|
@@ -36,11 +34,10 @@ export function oidcEarlyInit(params: {
|
|
|
36
34
|
freezeWebSocket,
|
|
37
35
|
freezePromise,
|
|
38
36
|
safeMode = false,
|
|
39
|
-
isPostLoginRedirectManual = false,
|
|
40
37
|
BASE_URL
|
|
41
38
|
} = params;
|
|
42
39
|
|
|
43
|
-
const { shouldLoadApp } = handleOidcCallback(
|
|
40
|
+
const { shouldLoadApp } = handleOidcCallback();
|
|
44
41
|
|
|
45
42
|
if (shouldLoadApp) {
|
|
46
43
|
const createWriteError = (target: string) =>
|
|
@@ -160,8 +157,6 @@ export function oidcEarlyInit(params: {
|
|
|
160
157
|
}
|
|
161
158
|
}
|
|
162
159
|
|
|
163
|
-
Object.freeze(original);
|
|
164
|
-
|
|
165
160
|
Object.defineProperty(window, name, {
|
|
166
161
|
configurable: false,
|
|
167
162
|
enumerable: true,
|
|
@@ -287,11 +282,9 @@ export function getRootRelativeOriginalLocationHref() {
|
|
|
287
282
|
return rootRelativeOriginalLocationHref;
|
|
288
283
|
}
|
|
289
284
|
|
|
290
|
-
function handleOidcCallback(
|
|
285
|
+
function handleOidcCallback(): {
|
|
291
286
|
shouldLoadApp: boolean;
|
|
292
287
|
} {
|
|
293
|
-
const { isPostLoginRedirectManual } = params;
|
|
294
|
-
|
|
295
288
|
const location_urlObj = new URL(window.location.href);
|
|
296
289
|
|
|
297
290
|
const locationHrefAssessment = (() => {
|
|
@@ -392,7 +385,6 @@ function handleOidcCallback(params: { isPostLoginRedirectManual?: boolean }): {
|
|
|
392
385
|
return { shouldLoadApp: false };
|
|
393
386
|
case "redirect": {
|
|
394
387
|
redirectAuthResponse = authResponse;
|
|
395
|
-
|
|
396
388
|
const rootRelativeRedirectUrl = (() => {
|
|
397
389
|
if (stateData.action === "login" && authResponse.error === "consent_required") {
|
|
398
390
|
return stateData.rootRelativeRedirectUrl_consentRequiredCase;
|
|
@@ -400,13 +392,7 @@ function handleOidcCallback(params: { isPostLoginRedirectManual?: boolean }): {
|
|
|
400
392
|
return stateData.rootRelativeRedirectUrl;
|
|
401
393
|
})();
|
|
402
394
|
|
|
403
|
-
|
|
404
|
-
setOidcRequiredPostHydrationReplaceNavigationUrl({ rootRelativeRedirectUrl });
|
|
405
|
-
history.replaceState({}, "", rootRelativeOriginalLocationHref);
|
|
406
|
-
} else {
|
|
407
|
-
history.replaceState({}, "", rootRelativeRedirectUrl);
|
|
408
|
-
}
|
|
409
|
-
|
|
395
|
+
history.replaceState({}, "", rootRelativeRedirectUrl);
|
|
410
396
|
return { shouldLoadApp: true };
|
|
411
397
|
}
|
|
412
398
|
default:
|
|
@@ -8,6 +8,7 @@ import { createStatefulEvt } from "../tools/StatefulEvt";
|
|
|
8
8
|
import { Deferred } from "../tools/Deferred";
|
|
9
9
|
import { addOrUpdateSearchParam, getAllSearchParams } from "../tools/urlSearchParams";
|
|
10
10
|
import { getIsOnline } from "../tools/getIsOnline";
|
|
11
|
+
import { setStateDataCookieIfEnabled } from "./StateDataCookie";
|
|
11
12
|
|
|
12
13
|
const globalContext = {
|
|
13
14
|
evtHasLoginBeenCalled: createStatefulEvt(() => false)
|
|
@@ -65,7 +66,9 @@ export function createLoginOrGoToAuthServer(params: {
|
|
|
65
66
|
getExtraTokenParams: (() => Record<string, string | undefined>) | undefined;
|
|
66
67
|
|
|
67
68
|
homeUrl: string;
|
|
69
|
+
stateUrlParamValue_instance: string;
|
|
68
70
|
evtInitializationOutcomeUserNotLoggedIn: NonPostableEvt<void>;
|
|
71
|
+
|
|
69
72
|
log: typeof console.log | undefined;
|
|
70
73
|
}) {
|
|
71
74
|
const {
|
|
@@ -78,6 +81,7 @@ export function createLoginOrGoToAuthServer(params: {
|
|
|
78
81
|
getExtraTokenParams,
|
|
79
82
|
|
|
80
83
|
homeUrl,
|
|
84
|
+
stateUrlParamValue_instance,
|
|
81
85
|
evtInitializationOutcomeUserNotLoggedIn,
|
|
82
86
|
|
|
83
87
|
log
|
|
@@ -204,20 +208,32 @@ export function createLoginOrGoToAuthServer(params: {
|
|
|
204
208
|
|
|
205
209
|
log?.(`redirectUrl: ${rootRelativeRedirectUrl}`);
|
|
206
210
|
|
|
207
|
-
const
|
|
211
|
+
const rootRelativeRedirectUrl_consentRequiredCase = (() => {
|
|
212
|
+
switch (rest.action) {
|
|
213
|
+
case "login":
|
|
214
|
+
return (lastPublicUrl ?? homeUrl).slice(window.location.origin.length);
|
|
215
|
+
case "go to auth server":
|
|
216
|
+
return rootRelativeRedirectUrl;
|
|
217
|
+
}
|
|
218
|
+
})();
|
|
219
|
+
|
|
220
|
+
setStateDataCookieIfEnabled({
|
|
221
|
+
homeUrl,
|
|
222
|
+
stateUrlParamValue_instance,
|
|
223
|
+
stateDataCookie: {
|
|
224
|
+
action: "login",
|
|
225
|
+
rootRelativeRedirectUrl,
|
|
226
|
+
rootRelativeRedirectUrl_consentRequiredCase
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const stateData: StateData.Redirect = {
|
|
208
231
|
context: "redirect",
|
|
209
232
|
rootRelativeRedirectUrl,
|
|
210
233
|
extraQueryParams: {},
|
|
211
234
|
configId,
|
|
212
235
|
action: "login",
|
|
213
|
-
rootRelativeRedirectUrl_consentRequiredCase
|
|
214
|
-
switch (rest.action) {
|
|
215
|
-
case "login":
|
|
216
|
-
return (lastPublicUrl ?? homeUrl).slice(window.location.origin.length);
|
|
217
|
-
case "go to auth server":
|
|
218
|
-
return rootRelativeRedirectUrl;
|
|
219
|
-
}
|
|
220
|
-
})()
|
|
236
|
+
rootRelativeRedirectUrl_consentRequiredCase
|
|
221
237
|
};
|
|
222
238
|
|
|
223
239
|
const isSilent = rest.action === "login" && rest.interaction === "ensure no interaction";
|
package/src/entrypoint.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { toFullyQualifiedUrl } from "../../tools/toFullyQualifiedUrl";
|
|
|
25
25
|
import { BEFORE_LOAD_FN_BRAND_PROPERTY_NAME } from "./disableSsrIfLoginEnforced";
|
|
26
26
|
import { setDesiredPostLoginRedirectUrl } from "../../core/desiredPostLoginRedirectUrl";
|
|
27
27
|
import type { MaybeAsync } from "../../tools/MaybeAsync";
|
|
28
|
+
import { enableStateDataCookie } from "../../core/StateDataCookie";
|
|
28
29
|
|
|
29
30
|
export function createOidcSpaApi<
|
|
30
31
|
AutoLogin extends boolean,
|
|
@@ -640,6 +641,8 @@ export function createOidcSpaApi<
|
|
|
640
641
|
break;
|
|
641
642
|
case "real":
|
|
642
643
|
{
|
|
644
|
+
enableStateDataCookie();
|
|
645
|
+
|
|
643
646
|
const { createOidc } = await prModuleCore;
|
|
644
647
|
|
|
645
648
|
let oidcCoreOrInitializationError:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { withHandlingOidcPostLoginNavigation } from "./withHandlingOidcPostLoginNavigation";
|
|
2
1
|
export { __disableSsrIfLoginEnforced } from "./disableSsrIfLoginEnforced";
|
|
2
|
+
export { __withOidcSpaServerEntry } from "./withOidcSpaServerEntry";
|
|
3
3
|
export type * from "./types";
|
|
4
4
|
import { oidcSpaApiBuilder } from "./apiBuilder";
|
|
5
5
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Register } from "@tanstack/react-router";
|
|
2
|
+
// NOTE: This is actually "@tanstack/react-start/server" but since our module is not labeled as ESM we import it from here.
|
|
3
|
+
// it does not matter since it's type level only.
|
|
4
|
+
import type { RequestHandler } from "@tanstack/react-start-server";
|
|
5
|
+
import { getStateDataCookies } from "../../core/StateDataCookie";
|
|
6
|
+
|
|
7
|
+
export function __withOidcSpaServerEntry<T extends { fetch: RequestHandler<Register> }>(
|
|
8
|
+
serverEntry_original: T
|
|
9
|
+
): T {
|
|
10
|
+
return {
|
|
11
|
+
...serverEntry_original,
|
|
12
|
+
fetch: async (request, requestOpts) => {
|
|
13
|
+
render_deepLink_instead_of_home_on_authResponse: {
|
|
14
|
+
const url = new URL(request.url);
|
|
15
|
+
|
|
16
|
+
const stateUrlParamValue = url.searchParams.get("state");
|
|
17
|
+
|
|
18
|
+
if (stateUrlParamValue === null) {
|
|
19
|
+
break render_deepLink_instead_of_home_on_authResponse;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { stateDataCookies } = getStateDataCookies({
|
|
23
|
+
cookieHeaderParamValue: request.headers.get("cookie")
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const entry = stateDataCookies.find(
|
|
27
|
+
entry => entry.stateUrlParamValue === stateUrlParamValue
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (entry === undefined) {
|
|
31
|
+
break render_deepLink_instead_of_home_on_authResponse;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { stateDataCookie } = entry;
|
|
35
|
+
|
|
36
|
+
const rootRelativeRedirectUrl = (() => {
|
|
37
|
+
if (
|
|
38
|
+
stateDataCookie.action === "login" &&
|
|
39
|
+
url.searchParams.get("error") === "consent_required"
|
|
40
|
+
) {
|
|
41
|
+
return stateDataCookie.rootRelativeRedirectUrl_consentRequiredCase;
|
|
42
|
+
}
|
|
43
|
+
return stateDataCookie.rootRelativeRedirectUrl;
|
|
44
|
+
})();
|
|
45
|
+
|
|
46
|
+
url.pathname = "/";
|
|
47
|
+
url.search = "";
|
|
48
|
+
url.hash = "";
|
|
49
|
+
|
|
50
|
+
const url_str_new = `${url.href.slice(0, -1)}${rootRelativeRedirectUrl}`;
|
|
51
|
+
|
|
52
|
+
const request_new = new Request(url_str_new, request);
|
|
53
|
+
|
|
54
|
+
return serverEntry_original.fetch(request_new, requestOpts);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return serverEntry_original.fetch(request, requestOpts);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import type { OidcSpaVitePluginParams } from "./vite-plugin";
|
|
2
2
|
import type { ResolvedConfig } from "vite";
|
|
3
3
|
import type { PluginContext } from "rollup";
|
|
4
|
-
import { existsSync } from "node:fs";
|
|
5
4
|
import { promises as fs } from "node:fs";
|
|
6
5
|
import * as path from "node:path";
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { normalizePath } from "vite";
|
|
9
6
|
import { assert } from "../tools/tsafe/assert";
|
|
10
7
|
import type { Equals } from "../tools/tsafe/Equals";
|
|
11
8
|
import type { ProjectType } from "./projectType";
|
|
9
|
+
import {
|
|
10
|
+
resolveCandidate,
|
|
11
|
+
resolvePackageFile,
|
|
12
|
+
normalizeAbsolute,
|
|
13
|
+
splitId,
|
|
14
|
+
normalizeRequestPath
|
|
15
|
+
} from "./utils";
|
|
12
16
|
|
|
13
17
|
type EntryResolution = {
|
|
14
18
|
absolutePath: string;
|
|
@@ -29,7 +33,7 @@ const REACT_ROUTER_ENTRY_CANDIDATES = [
|
|
|
29
33
|
|
|
30
34
|
const TANSTACK_ENTRY_CANDIDATES = ["client.tsx", "client.ts", "client.jsx", "client.js"];
|
|
31
35
|
|
|
32
|
-
export function
|
|
36
|
+
export function createHandleClientEntrypoint(params: {
|
|
33
37
|
oidcSpaVitePluginParams: OidcSpaVitePluginParams;
|
|
34
38
|
resolvedConfig: ResolvedConfig;
|
|
35
39
|
projectType: ProjectType;
|
|
@@ -41,7 +45,7 @@ export function createLoadHandleEntrypoint(params: {
|
|
|
41
45
|
projectType
|
|
42
46
|
});
|
|
43
47
|
|
|
44
|
-
async function
|
|
48
|
+
async function load_handleClientEntrypoint(params: {
|
|
45
49
|
id: string;
|
|
46
50
|
pluginContext: PluginContext;
|
|
47
51
|
}): Promise<null | string> {
|
|
@@ -77,7 +81,6 @@ export function createLoadHandleEntrypoint(params: {
|
|
|
77
81
|
` freezeWebSocket: ${freezeWebSocket},`,
|
|
78
82
|
` freezePromise: ${freezePromise},`,
|
|
79
83
|
` safeMode: ${safeMode},`,
|
|
80
|
-
` isPostLoginRedirectManual: ${projectType === "tanstack-start"},`,
|
|
81
84
|
` BASE_URL: "${resolvedConfig.base}"`,
|
|
82
85
|
`});`,
|
|
83
86
|
``,
|
|
@@ -93,7 +96,7 @@ export function createLoadHandleEntrypoint(params: {
|
|
|
93
96
|
return stubSourceCache;
|
|
94
97
|
}
|
|
95
98
|
|
|
96
|
-
return
|
|
99
|
+
return { load_handleClientEntrypoint };
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
function resolveEntryForProject({
|
|
@@ -192,63 +195,3 @@ function loadOriginalModule(
|
|
|
192
195
|
entry.watchFiles.forEach(file => context.addWatchFile(file));
|
|
193
196
|
return fs.readFile(entry.absolutePath, "utf8");
|
|
194
197
|
}
|
|
195
|
-
|
|
196
|
-
function resolveCandidate({
|
|
197
|
-
root,
|
|
198
|
-
subDirectories,
|
|
199
|
-
filenames
|
|
200
|
-
}: {
|
|
201
|
-
root: string;
|
|
202
|
-
subDirectories: string[];
|
|
203
|
-
filenames: string[];
|
|
204
|
-
}): string | undefined {
|
|
205
|
-
for (const subDirectory of subDirectories) {
|
|
206
|
-
for (const filename of filenames) {
|
|
207
|
-
const candidate = path.resolve(root, subDirectory, filename);
|
|
208
|
-
if (existsSync(candidate)) {
|
|
209
|
-
return candidate;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return undefined;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function resolvePackageFile(packageName: string, segments: string[]): string {
|
|
217
|
-
const pkgPath = require.resolve(`${packageName}/package.json`);
|
|
218
|
-
return path.resolve(path.dirname(pkgPath), ...segments);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function normalizeAbsolute(filePath: string): string {
|
|
222
|
-
return normalizePath(filePath);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function splitId(id: string): { path: string; queryParams: URLSearchParams } {
|
|
226
|
-
const queryIndex = id.indexOf("?");
|
|
227
|
-
if (queryIndex === -1) {
|
|
228
|
-
return { path: id, queryParams: new URLSearchParams() };
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const pathPart = id.slice(0, queryIndex);
|
|
232
|
-
const queryString = id.slice(queryIndex + 1);
|
|
233
|
-
return { path: pathPart, queryParams: new URLSearchParams(queryString) };
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function normalizeRequestPath(id: string): string {
|
|
237
|
-
let requestPath = id;
|
|
238
|
-
|
|
239
|
-
if (requestPath.startsWith("\0")) {
|
|
240
|
-
requestPath = requestPath.slice(1);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (requestPath.startsWith("/@fs/")) {
|
|
244
|
-
requestPath = requestPath.slice("/@fs/".length);
|
|
245
|
-
} else if (requestPath.startsWith("file://")) {
|
|
246
|
-
requestPath = fileURLToPath(requestPath);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (path.isAbsolute(requestPath) || requestPath.startsWith(".")) {
|
|
250
|
-
return normalizePath(requestPath);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return normalizePath(requestPath);
|
|
254
|
-
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { ResolvedConfig } from "vite";
|
|
2
|
+
import type { PluginContext } from "rollup";
|
|
3
|
+
import { promises as fs } from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { assert } from "../tools/tsafe/assert";
|
|
6
|
+
import type { Equals } from "../tools/tsafe/Equals";
|
|
7
|
+
import type { ProjectType } from "./projectType";
|
|
8
|
+
import {
|
|
9
|
+
resolveCandidate,
|
|
10
|
+
resolvePackageFile,
|
|
11
|
+
normalizeAbsolute,
|
|
12
|
+
splitId,
|
|
13
|
+
normalizeRequestPath
|
|
14
|
+
} from "./utils";
|
|
15
|
+
|
|
16
|
+
type EntryResolution = {
|
|
17
|
+
absolutePath: string;
|
|
18
|
+
normalizedPath: string;
|
|
19
|
+
watchFiles: string[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const ORIGINAL_QUERY_PARAM = "oidc-spa-original";
|
|
23
|
+
|
|
24
|
+
export function createHandleServerEntrypoint(params: {
|
|
25
|
+
resolvedConfig: ResolvedConfig;
|
|
26
|
+
projectType: ProjectType;
|
|
27
|
+
}) {
|
|
28
|
+
const { resolvedConfig, projectType } = params;
|
|
29
|
+
|
|
30
|
+
const entryResolution = resolveEntryForProject({
|
|
31
|
+
config: resolvedConfig,
|
|
32
|
+
projectType
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
async function load_handleServerEntrypoint(params: {
|
|
36
|
+
id: string;
|
|
37
|
+
pluginContext: PluginContext;
|
|
38
|
+
}): Promise<null | string> {
|
|
39
|
+
if (entryResolution === undefined) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { id, pluginContext } = params;
|
|
44
|
+
const { path: rawPath, queryParams } = splitId(id);
|
|
45
|
+
const normalizedRequestPath = normalizeRequestPath(rawPath);
|
|
46
|
+
if (!normalizedRequestPath) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (normalizedRequestPath !== entryResolution.normalizedPath) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const isOriginalRequest = queryParams.getAll(ORIGINAL_QUERY_PARAM).includes("true");
|
|
55
|
+
|
|
56
|
+
if (isOriginalRequest) {
|
|
57
|
+
return loadOriginalModule(entryResolution, pluginContext);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
entryResolution.watchFiles.forEach(file => pluginContext.addWatchFile(file));
|
|
61
|
+
|
|
62
|
+
const stubSourceCache = [
|
|
63
|
+
`import serverEntry_original from "./${path.basename(
|
|
64
|
+
entryResolution.absolutePath
|
|
65
|
+
)}?${ORIGINAL_QUERY_PARAM}=true";`,
|
|
66
|
+
`import { __withOidcSpaServerEntry } from "oidc-spa/react-tanstack-start";`,
|
|
67
|
+
``,
|
|
68
|
+
`const serverEntry = __withOidcSpaServerEntry(serverEntry_original);`,
|
|
69
|
+
``,
|
|
70
|
+
`export default serverEntry;`
|
|
71
|
+
].join("\n");
|
|
72
|
+
|
|
73
|
+
return stubSourceCache;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { load_handleServerEntrypoint };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function resolveEntryForProject({
|
|
80
|
+
config,
|
|
81
|
+
projectType
|
|
82
|
+
}: {
|
|
83
|
+
config: ResolvedConfig;
|
|
84
|
+
projectType: ProjectType;
|
|
85
|
+
}): EntryResolution | undefined {
|
|
86
|
+
const root = config.root;
|
|
87
|
+
|
|
88
|
+
switch (projectType) {
|
|
89
|
+
case "tanstack-start": {
|
|
90
|
+
const candidate = resolveCandidate({
|
|
91
|
+
root,
|
|
92
|
+
subDirectories: ["src"],
|
|
93
|
+
filenames: ["server.ts", "server.js", "server.tsx", "server.jsx"]
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const entryPath =
|
|
97
|
+
candidate ??
|
|
98
|
+
resolvePackageFile("@tanstack/react-start", [
|
|
99
|
+
"dist",
|
|
100
|
+
"plugin",
|
|
101
|
+
"default-entry",
|
|
102
|
+
"server.ts"
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
const normalized = normalizeAbsolute(entryPath);
|
|
106
|
+
|
|
107
|
+
const resolution: EntryResolution = {
|
|
108
|
+
absolutePath: entryPath,
|
|
109
|
+
normalizedPath: normalized,
|
|
110
|
+
watchFiles: [entryPath]
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return resolution;
|
|
114
|
+
}
|
|
115
|
+
case "react-router-framework":
|
|
116
|
+
case "other":
|
|
117
|
+
return undefined;
|
|
118
|
+
default:
|
|
119
|
+
assert<Equals<typeof projectType, never>>(false);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function loadOriginalModule(
|
|
124
|
+
entry: EntryResolution,
|
|
125
|
+
context: { addWatchFile(id: string): void }
|
|
126
|
+
): Promise<string> {
|
|
127
|
+
entry.watchFiles.forEach(file => context.addWatchFile(file));
|
|
128
|
+
return fs.readFile(entry.absolutePath, "utf8");
|
|
129
|
+
}
|
|
@@ -4,8 +4,6 @@ import { babelParser, babelTraverse, babelTypes as t } from "../vendor/build-run
|
|
|
4
4
|
const DISABLE_SSR_SPECIFIER = "__disableSsrIfLoginEnforced";
|
|
5
5
|
const DISABLE_SSR_SOURCE = "oidc-spa/react-tanstack-start";
|
|
6
6
|
const CREATE_FILE_ROUTE_IDENTIFIER = "createFileRoute";
|
|
7
|
-
const POST_LOGIN_IMPORT_SPECIFIER = "withHandlingOidcPostLoginNavigation";
|
|
8
|
-
const POST_LOGIN_IMPORT_SOURCE = "oidc-spa/react-tanstack-start";
|
|
9
7
|
|
|
10
8
|
type TransformParams = {
|
|
11
9
|
code: string;
|
|
@@ -38,9 +36,7 @@ export function transformCreateFileRoute(params: TransformParams): TransformResu
|
|
|
38
36
|
const magicString = new MagicString(code);
|
|
39
37
|
let hasCreateFileRouteImport = false;
|
|
40
38
|
let hasEnableImport = false;
|
|
41
|
-
let hasPostLoginImport = false;
|
|
42
39
|
let requiresEnableImport = false;
|
|
43
|
-
let requiresPostLoginImport = false;
|
|
44
40
|
let lastImportEnd: number | undefined;
|
|
45
41
|
let mutated = false;
|
|
46
42
|
|
|
@@ -79,18 +75,6 @@ export function transformCreateFileRoute(params: TransformParams): TransformResu
|
|
|
79
75
|
hasEnableImport = true;
|
|
80
76
|
}
|
|
81
77
|
}
|
|
82
|
-
|
|
83
|
-
if (sourceValue === POST_LOGIN_IMPORT_SOURCE) {
|
|
84
|
-
if (
|
|
85
|
-
path.node.specifiers.some(
|
|
86
|
-
specifier =>
|
|
87
|
-
t.isImportSpecifier(specifier) &&
|
|
88
|
-
t.isIdentifier(specifier.imported, { name: POST_LOGIN_IMPORT_SPECIFIER })
|
|
89
|
-
)
|
|
90
|
-
) {
|
|
91
|
-
hasPostLoginImport = true;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
78
|
},
|
|
95
79
|
CallExpression(path) {
|
|
96
80
|
const callee = path.get("callee");
|
|
@@ -128,33 +112,6 @@ export function transformCreateFileRoute(params: TransformParams): TransformResu
|
|
|
128
112
|
}
|
|
129
113
|
}
|
|
130
114
|
|
|
131
|
-
const innerArgs = callee.node.arguments ?? [];
|
|
132
|
-
const isRootRoute =
|
|
133
|
-
innerArgs.length > 0 && t.isStringLiteral(innerArgs[0]) && innerArgs[0].value === "/";
|
|
134
|
-
|
|
135
|
-
if (isRootRoute) {
|
|
136
|
-
const componentProp = findComponentProperty(configNode);
|
|
137
|
-
if (componentProp) {
|
|
138
|
-
const valueNode = componentProp.value as t.Expression;
|
|
139
|
-
|
|
140
|
-
if (!isWrappedWithHandling(valueNode)) {
|
|
141
|
-
const start = valueNode.start ?? undefined;
|
|
142
|
-
const end = valueNode.end ?? undefined;
|
|
143
|
-
|
|
144
|
-
if (typeof start === "number" && typeof end === "number") {
|
|
145
|
-
const original = code.slice(start, end);
|
|
146
|
-
magicString.overwrite(
|
|
147
|
-
start,
|
|
148
|
-
end,
|
|
149
|
-
`${POST_LOGIN_IMPORT_SPECIFIER}(${original})`
|
|
150
|
-
);
|
|
151
|
-
requiresPostLoginImport = true;
|
|
152
|
-
localMutated = true;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
115
|
if (localMutated) {
|
|
159
116
|
mutated = true;
|
|
160
117
|
}
|
|
@@ -171,12 +128,6 @@ export function transformCreateFileRoute(params: TransformParams): TransformResu
|
|
|
171
128
|
importStatements.push(`import { ${DISABLE_SSR_SPECIFIER} } from "${DISABLE_SSR_SOURCE}";`);
|
|
172
129
|
}
|
|
173
130
|
|
|
174
|
-
if (requiresPostLoginImport && !hasPostLoginImport) {
|
|
175
|
-
importStatements.push(
|
|
176
|
-
`import { ${POST_LOGIN_IMPORT_SPECIFIER} } from "${POST_LOGIN_IMPORT_SOURCE}";`
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
131
|
if (importStatements.length > 0) {
|
|
181
132
|
const insertionPoint = lastImportEnd ?? 0;
|
|
182
133
|
const prefix = insertionPoint === 0 ? "" : "\n";
|
|
@@ -190,15 +141,6 @@ export function transformCreateFileRoute(params: TransformParams): TransformResu
|
|
|
190
141
|
};
|
|
191
142
|
}
|
|
192
143
|
|
|
193
|
-
function findComponentProperty(node: t.ObjectExpression): t.ObjectProperty | undefined {
|
|
194
|
-
return node.properties.find(
|
|
195
|
-
prop =>
|
|
196
|
-
t.isObjectProperty(prop) &&
|
|
197
|
-
((t.isIdentifier(prop.key) && prop.key.name === "component") ||
|
|
198
|
-
(t.isStringLiteral(prop.key) && prop.key.value === "component"))
|
|
199
|
-
) as t.ObjectProperty | undefined;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
144
|
function objectContainsLoaderOrBeforeLoad(node: t.ObjectExpression): boolean {
|
|
203
145
|
return node.properties.some(prop => {
|
|
204
146
|
if (!t.isObjectProperty(prop)) {
|
|
@@ -218,12 +160,6 @@ function objectContainsLoaderOrBeforeLoad(node: t.ObjectExpression): boolean {
|
|
|
218
160
|
});
|
|
219
161
|
}
|
|
220
162
|
|
|
221
|
-
function isWrappedWithHandling(node: t.Node): boolean {
|
|
222
|
-
return (
|
|
223
|
-
t.isCallExpression(node) && t.isIdentifier(node.callee, { name: POST_LOGIN_IMPORT_SPECIFIER })
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
163
|
function isCandidateFile(id: string): boolean {
|
|
228
164
|
if (id.includes("node_modules")) {
|
|
229
165
|
return false;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { normalizePath } from "vite";
|
|
5
|
+
|
|
6
|
+
export function resolveCandidate({
|
|
7
|
+
root,
|
|
8
|
+
subDirectories,
|
|
9
|
+
filenames
|
|
10
|
+
}: {
|
|
11
|
+
root: string;
|
|
12
|
+
subDirectories: string[];
|
|
13
|
+
filenames: string[];
|
|
14
|
+
}): string | undefined {
|
|
15
|
+
for (const subDirectory of subDirectories) {
|
|
16
|
+
for (const filename of filenames) {
|
|
17
|
+
const candidate = path.resolve(root, subDirectory, filename);
|
|
18
|
+
if (existsSync(candidate)) {
|
|
19
|
+
return candidate;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function resolvePackageFile(packageName: string, segments: string[]): string {
|
|
27
|
+
const pkgPath = require.resolve(`${packageName}/package.json`);
|
|
28
|
+
return path.resolve(path.dirname(pkgPath), ...segments);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function normalizeAbsolute(filePath: string): string {
|
|
32
|
+
return normalizePath(filePath);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function splitId(id: string): { path: string; queryParams: URLSearchParams } {
|
|
36
|
+
const queryIndex = id.indexOf("?");
|
|
37
|
+
if (queryIndex === -1) {
|
|
38
|
+
return { path: id, queryParams: new URLSearchParams() };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const pathPart = id.slice(0, queryIndex);
|
|
42
|
+
const queryString = id.slice(queryIndex + 1);
|
|
43
|
+
return { path: pathPart, queryParams: new URLSearchParams(queryString) };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function normalizeRequestPath(id: string): string {
|
|
47
|
+
let requestPath = id;
|
|
48
|
+
|
|
49
|
+
if (requestPath.startsWith("\0")) {
|
|
50
|
+
requestPath = requestPath.slice(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (requestPath.startsWith("/@fs/")) {
|
|
54
|
+
requestPath = requestPath.slice("/@fs/".length);
|
|
55
|
+
} else if (requestPath.startsWith("file://")) {
|
|
56
|
+
requestPath = fileURLToPath(requestPath);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (path.isAbsolute(requestPath) || requestPath.startsWith(".")) {
|
|
60
|
+
return normalizePath(requestPath);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return normalizePath(requestPath);
|
|
64
|
+
}
|