oidc-spa 8.3.6 → 8.3.8

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.
Files changed (75) hide show
  1. package/core/StateDataCookie.d.ts +34 -0
  2. package/core/StateDataCookie.js +142 -0
  3. package/core/StateDataCookie.js.map +1 -0
  4. package/core/createOidc.js +25 -9
  5. package/core/createOidc.js.map +1 -1
  6. package/core/earlyInit.d.ts +0 -1
  7. package/core/earlyInit.js +4 -12
  8. package/core/earlyInit.js.map +1 -1
  9. package/core/loginOrGoToAuthServer.d.ts +1 -0
  10. package/core/loginOrGoToAuthServer.js +20 -9
  11. package/core/loginOrGoToAuthServer.js.map +1 -1
  12. package/entrypoint.d.ts +0 -1
  13. package/entrypoint.js +1 -3
  14. package/entrypoint.js.map +1 -1
  15. package/esm/core/StateDataCookie.d.ts +34 -0
  16. package/esm/core/StateDataCookie.js +135 -0
  17. package/esm/core/StateDataCookie.js.map +1 -0
  18. package/esm/core/createOidc.js +25 -9
  19. package/esm/core/createOidc.js.map +1 -1
  20. package/esm/core/earlyInit.d.ts +0 -1
  21. package/esm/core/earlyInit.js +4 -12
  22. package/esm/core/earlyInit.js.map +1 -1
  23. package/esm/core/loginOrGoToAuthServer.d.ts +1 -0
  24. package/esm/core/loginOrGoToAuthServer.js +20 -9
  25. package/esm/core/loginOrGoToAuthServer.js.map +1 -1
  26. package/esm/entrypoint.d.ts +0 -1
  27. package/esm/entrypoint.js +0 -1
  28. package/esm/entrypoint.js.map +1 -1
  29. package/esm/tanstack-start/react/createOidcSpaApi.js +2 -0
  30. package/esm/tanstack-start/react/createOidcSpaApi.js.map +1 -1
  31. package/esm/tanstack-start/react/index.d.ts +1 -1
  32. package/esm/tanstack-start/react/index.js +1 -1
  33. package/esm/tanstack-start/react/index.js.map +1 -1
  34. package/esm/tanstack-start/react/withOidcSpaServerEntry.d.ts +5 -0
  35. package/esm/tanstack-start/react/withOidcSpaServerEntry.js +38 -0
  36. package/esm/tanstack-start/react/withOidcSpaServerEntry.js.map +1 -0
  37. package/package.json +1 -1
  38. package/src/core/StateDataCookie.ts +217 -0
  39. package/src/core/createOidc.ts +34 -8
  40. package/src/core/earlyInit.ts +3 -15
  41. package/src/core/loginOrGoToAuthServer.ts +25 -9
  42. package/src/entrypoint.ts +0 -1
  43. package/src/tanstack-start/react/createOidcSpaApi.tsx +3 -0
  44. package/src/tanstack-start/react/index.ts +1 -1
  45. package/src/tanstack-start/react/withOidcSpaServerEntry.ts +60 -0
  46. package/src/vite-plugin/handleClientEntrypoint.ts +10 -67
  47. package/src/vite-plugin/handleServerEntrypoint.ts +129 -0
  48. package/src/vite-plugin/transformTanstackRouterCreateFileRoute.ts +0 -64
  49. package/src/vite-plugin/utils.ts +64 -0
  50. package/src/vite-plugin/vite-plugin.ts +31 -10
  51. package/vite-plugin/handleClientEntrypoint.d.ts +7 -5
  52. package/vite-plugin/handleClientEntrypoint.js +16 -62
  53. package/vite-plugin/handleClientEntrypoint.js.map +1 -1
  54. package/vite-plugin/handleServerEntrypoint.d.ts +12 -0
  55. package/vite-plugin/handleServerEntrypoint.js +113 -0
  56. package/vite-plugin/handleServerEntrypoint.js.map +1 -0
  57. package/vite-plugin/transformTanstackRouterCreateFileRoute.js +0 -39
  58. package/vite-plugin/transformTanstackRouterCreateFileRoute.js.map +1 -1
  59. package/vite-plugin/utils.d.ts +12 -0
  60. package/vite-plugin/utils.js +88 -0
  61. package/vite-plugin/utils.js.map +1 -0
  62. package/vite-plugin/vite-plugin.d.ts +1 -1
  63. package/vite-plugin/vite-plugin.js +21 -5
  64. package/vite-plugin/vite-plugin.js.map +1 -1
  65. package/core/requiredPostHydrationReplaceNavigationUrl.d.ts +0 -6
  66. package/core/requiredPostHydrationReplaceNavigationUrl.js +0 -12
  67. package/core/requiredPostHydrationReplaceNavigationUrl.js.map +0 -1
  68. package/esm/core/requiredPostHydrationReplaceNavigationUrl.d.ts +0 -6
  69. package/esm/core/requiredPostHydrationReplaceNavigationUrl.js +0 -8
  70. package/esm/core/requiredPostHydrationReplaceNavigationUrl.js.map +0 -1
  71. package/esm/tanstack-start/react/withHandlingOidcPostLoginNavigation.d.ts +0 -2
  72. package/esm/tanstack-start/react/withHandlingOidcPostLoginNavigation.js +0 -36
  73. package/esm/tanstack-start/react/withHandlingOidcPostLoginNavigation.js.map +0 -1
  74. package/src/core/requiredPostHydrationReplaceNavigationUrl.ts +0 -11
  75. package/src/tanstack-start/react/withHandlingOidcPostLoginNavigation.tsx +0 -46
@@ -53,6 +53,11 @@ import {
53
53
  import { getDesiredPostLoginRedirectUrl } from "./desiredPostLoginRedirectUrl";
54
54
  import { getHomeAndRedirectUri } from "./homeAndRedirectUri";
55
55
  import { ensureNonBlankPaint } from "../tools/ensureNonBlankPaint";
56
+ import {
57
+ setStateDataCookieIfEnabled,
58
+ clearStateDataCookie,
59
+ getIsStateDataCookieEnabled
60
+ } from "./StateDataCookie";
56
61
 
57
62
  // NOTE: Replaced at build time
58
63
  const VERSION = "{{OIDC_SPA_VERSION}}";
@@ -373,10 +378,11 @@ export async function createOidc_nonMemoized<
373
378
  __unsafe_clientSecret,
374
379
  __unsafe_useIdTokenAsAccessToken = false,
375
380
  __metadata,
376
- scopes = ["openid", "profile"],
377
381
  sessionRestorationMethod = params.autoLogin === true ? "full page redirect" : "auto"
378
382
  } = params;
379
383
 
384
+ const scopes = Array.from(new Set(["openid", ...(params.scopes ?? ["profile"])]));
385
+
380
386
  const BASE_URL_params = params.BASE_URL ?? params.homeUrl;
381
387
 
382
388
  const { issuerUri, clientId, configId, log } = preProcessedParams;
@@ -607,9 +613,10 @@ export async function createOidc_nonMemoized<
607
613
  redirect_uri: homeUrlAndRedirectUri,
608
614
  silent_redirect_uri: homeUrlAndRedirectUri,
609
615
  post_logout_redirect_uri: homeUrlAndRedirectUri,
610
- response_mode: isKeycloak({ issuerUri }) ? "fragment" : "query",
616
+ response_mode:
617
+ isKeycloak({ issuerUri }) && !getIsStateDataCookieEnabled() ? "fragment" : "query",
611
618
  response_type: "code",
612
- scope: Array.from(new Set(["openid", ...scopes])).join(" "),
619
+ scope: scopes.join(" "),
613
620
  automaticSilentRenew: false,
614
621
  userStore: new WebStorageStateStore({
615
622
  store: (() => {
@@ -647,6 +654,7 @@ export async function createOidc_nonMemoized<
647
654
  getExtraQueryParams,
648
655
  getExtraTokenParams,
649
656
  homeUrl: homeUrlAndRedirectUri,
657
+ stateUrlParamValue_instance,
650
658
  evtInitializationOutcomeUserNotLoggedIn,
651
659
  log
652
660
  });
@@ -741,6 +749,8 @@ export async function createOidc_nonMemoized<
741
749
  break handle_redirect_auth_response;
742
750
  }
743
751
 
752
+ // TODO: Delete cookie if exist
753
+
744
754
  const { stateData, authResponse } = stateDataAndAuthResponse;
745
755
 
746
756
  switch (stateData.action) {
@@ -763,6 +773,8 @@ export async function createOidc_nonMemoized<
763
773
 
764
774
  const authResponseUrl = authResponseToUrl(authResponse);
765
775
 
776
+ clearStateDataCookie({ stateUrlParamValue: authResponse.state });
777
+
766
778
  let oidcClientTsUser: OidcClientTsUser | undefined = undefined;
767
779
 
768
780
  try {
@@ -786,7 +798,10 @@ export async function createOidc_nonMemoized<
786
798
 
787
799
  if (authResponse_error !== undefined) {
788
800
  log?.(
789
- `The auth server responded with: ${authResponse_error}, trying to restore from the http only cookie`
801
+ [
802
+ `The auth server responded with: ${authResponse_error},`,
803
+ `trying to restore session as if we didn't had a auth response.`
804
+ ].join(" ")
790
805
  );
791
806
  break handle_redirect_auth_response;
792
807
  }
@@ -943,6 +958,8 @@ export async function createOidc_nonMemoized<
943
958
  )}`
944
959
  );
945
960
 
961
+ clearStateDataCookie({ stateUrlParamValue: authResponse.state });
962
+
946
963
  authResponse_error = authResponse.error;
947
964
 
948
965
  try {
@@ -1006,9 +1023,7 @@ export async function createOidc_nonMemoized<
1006
1023
 
1007
1024
  return getDesiredPostLoginRedirectUrl() ?? window.location.href;
1008
1025
  })(),
1009
- // NOTE: Wether or not it's the preferred behavior, pushing to history
1010
- // only works on user interaction so it have to be false
1011
- doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack: false,
1026
+ doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack: true,
1012
1027
  extraQueryParams_local: undefined,
1013
1028
  transformUrlBeforeRedirect_local: undefined,
1014
1029
  interaction: (() => {
@@ -1364,6 +1379,15 @@ export async function createOidc_nonMemoized<
1364
1379
  location.reload();
1365
1380
  });
1366
1381
 
1382
+ setStateDataCookieIfEnabled({
1383
+ homeUrl: homeUrlAndRedirectUri,
1384
+ stateUrlParamValue_instance,
1385
+ stateDataCookie: {
1386
+ action: "logout",
1387
+ rootRelativeRedirectUrl: rootRelativePostLogoutRedirectUrl
1388
+ }
1389
+ });
1390
+
1367
1391
  try {
1368
1392
  await oidcClientTsUserManager.signoutRedirect({
1369
1393
  state: id<StateData.Redirect>({
@@ -1401,7 +1425,7 @@ export async function createOidc_nonMemoized<
1401
1425
  doForceReloadOnBfCache: true,
1402
1426
  extraQueryParams_local: undefined,
1403
1427
  transformUrlBeforeRedirect_local: undefined,
1404
- doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack: false,
1428
+ doNavigateBackToLastPublicUrlIfTheTheUserNavigateBack: true,
1405
1429
  interaction: "directly redirect if active session show login otherwise",
1406
1430
  preRedirectHook: undefined
1407
1431
  });
@@ -1465,6 +1489,8 @@ export async function createOidc_nonMemoized<
1465
1489
 
1466
1490
  log?.("Tokens refresh using iframe", authResponse);
1467
1491
 
1492
+ clearStateDataCookie({ stateUrlParamValue: authResponse.state });
1493
+
1468
1494
  const authResponse_error = authResponse.error;
1469
1495
 
1470
1496
  let oidcClientTsUser_scope: OidcClientTsUser | undefined = undefined;
@@ -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({ isPostLoginRedirectManual });
40
+ const { shouldLoadApp } = handleOidcCallback();
44
41
 
45
42
  if (shouldLoadApp) {
46
43
  const createWriteError = (target: string) =>
@@ -285,11 +282,9 @@ export function getRootRelativeOriginalLocationHref() {
285
282
  return rootRelativeOriginalLocationHref;
286
283
  }
287
284
 
288
- function handleOidcCallback(params: { isPostLoginRedirectManual?: boolean }): {
285
+ function handleOidcCallback(): {
289
286
  shouldLoadApp: boolean;
290
287
  } {
291
- const { isPostLoginRedirectManual } = params;
292
-
293
288
  const location_urlObj = new URL(window.location.href);
294
289
 
295
290
  const locationHrefAssessment = (() => {
@@ -390,7 +385,6 @@ function handleOidcCallback(params: { isPostLoginRedirectManual?: boolean }): {
390
385
  return { shouldLoadApp: false };
391
386
  case "redirect": {
392
387
  redirectAuthResponse = authResponse;
393
-
394
388
  const rootRelativeRedirectUrl = (() => {
395
389
  if (stateData.action === "login" && authResponse.error === "consent_required") {
396
390
  return stateData.rootRelativeRedirectUrl_consentRequiredCase;
@@ -398,13 +392,7 @@ function handleOidcCallback(params: { isPostLoginRedirectManual?: boolean }): {
398
392
  return stateData.rootRelativeRedirectUrl;
399
393
  })();
400
394
 
401
- if (isPostLoginRedirectManual) {
402
- setOidcRequiredPostHydrationReplaceNavigationUrl({ rootRelativeRedirectUrl });
403
- history.replaceState({}, "", rootRelativeOriginalLocationHref);
404
- } else {
405
- history.replaceState({}, "", rootRelativeRedirectUrl);
406
- }
407
-
395
+ history.replaceState({}, "", rootRelativeRedirectUrl);
408
396
  return { shouldLoadApp: true };
409
397
  }
410
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 stateData: StateData = {
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
@@ -1,2 +1 @@
1
1
  export { oidcEarlyInit } from "./core/earlyInit";
2
- export { getOidcRequiredPostHydrationReplaceNavigationUrl } from "./core/requiredPostHydrationReplaceNavigationUrl";
@@ -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 createLoadHandleEntrypoint(params: {
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 loadHandleEntrypoint(params: {
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 loadHandleEntrypoint;
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
+ }