oidc-spa 7.2.1 → 7.2.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.
Files changed (257) hide show
  1. package/backend.js.map +1 -1
  2. package/core/AuthResponse.js.map +1 -1
  3. package/core/Oidc.js.map +1 -1
  4. package/core/OidcInitializationError.js.map +1 -1
  5. package/core/OidcMetadata.js.map +1 -1
  6. package/core/StateData.js.map +1 -1
  7. package/core/configId.js.map +1 -1
  8. package/core/createOidc.js +1 -1
  9. package/core/createOidc.js.map +1 -1
  10. package/core/diagnostic.js.map +1 -1
  11. package/core/evtIsUserActive.js.map +1 -1
  12. package/core/handleOidcCallback.js.map +1 -1
  13. package/core/iframeMessageProtection.js.map +1 -1
  14. package/core/index.js.map +1 -1
  15. package/core/initialLocationHref.js.map +1 -1
  16. package/core/isNewBrowserSession.js.map +1 -1
  17. package/core/loginOrGoToAuthServer.js.map +1 -1
  18. package/core/loginPropagationToOtherTabs.js.map +1 -1
  19. package/core/loginSilent.js.map +1 -1
  20. package/core/logoutPropagationToOtherTabs.js.map +1 -1
  21. package/core/oidcClientTsUserToTokens.js.map +1 -1
  22. package/core/ongoingLoginOrRefreshProcesses.js.map +1 -1
  23. package/core/persistedAuthState.js.map +1 -1
  24. package/entrypoint.js.map +1 -1
  25. package/esm/core/AuthResponse.js +2 -2
  26. package/esm/core/AuthResponse.js.map +1 -1
  27. package/esm/core/Oidc.d.ts +1 -1
  28. package/esm/core/Oidc.js.map +1 -1
  29. package/esm/core/OidcInitializationError.js.map +1 -1
  30. package/esm/core/OidcMetadata.js +2 -2
  31. package/esm/core/OidcMetadata.js.map +1 -1
  32. package/esm/core/StateData.js +3 -3
  33. package/esm/core/StateData.js.map +1 -1
  34. package/esm/core/configId.js.map +1 -1
  35. package/esm/core/createOidc.d.ts +2 -2
  36. package/esm/core/createOidc.js +33 -33
  37. package/esm/core/createOidc.js.map +1 -1
  38. package/esm/core/diagnostic.d.ts +1 -1
  39. package/esm/core/diagnostic.js +4 -4
  40. package/esm/core/diagnostic.js.map +1 -1
  41. package/esm/core/evtIsUserActive.d.ts +1 -1
  42. package/esm/core/evtIsUserActive.js +5 -5
  43. package/esm/core/evtIsUserActive.js.map +1 -1
  44. package/esm/core/handleOidcCallback.d.ts +2 -2
  45. package/esm/core/handleOidcCallback.js +5 -5
  46. package/esm/core/handleOidcCallback.js.map +1 -1
  47. package/esm/core/iframeMessageProtection.d.ts +1 -1
  48. package/esm/core/iframeMessageProtection.js +3 -3
  49. package/esm/core/iframeMessageProtection.js.map +1 -1
  50. package/esm/core/index.d.ts +4 -4
  51. package/esm/core/index.js +4 -4
  52. package/esm/core/index.js.map +1 -1
  53. package/esm/core/initialLocationHref.js.map +1 -1
  54. package/esm/core/isNewBrowserSession.d.ts +1 -1
  55. package/esm/core/isNewBrowserSession.js.map +1 -1
  56. package/esm/core/loginOrGoToAuthServer.d.ts +2 -2
  57. package/esm/core/loginOrGoToAuthServer.js +6 -6
  58. package/esm/core/loginOrGoToAuthServer.js.map +1 -1
  59. package/esm/core/loginPropagationToOtherTabs.js +3 -3
  60. package/esm/core/loginPropagationToOtherTabs.js.map +1 -1
  61. package/esm/core/loginSilent.d.ts +2 -2
  62. package/esm/core/loginSilent.js +8 -8
  63. package/esm/core/loginSilent.js.map +1 -1
  64. package/esm/core/logoutPropagationToOtherTabs.js +3 -3
  65. package/esm/core/logoutPropagationToOtherTabs.js.map +1 -1
  66. package/esm/core/oidcClientTsUserToTokens.d.ts +2 -2
  67. package/esm/core/oidcClientTsUserToTokens.js +4 -4
  68. package/esm/core/oidcClientTsUserToTokens.js.map +1 -1
  69. package/esm/core/ongoingLoginOrRefreshProcesses.js +3 -3
  70. package/esm/core/ongoingLoginOrRefreshProcesses.js.map +1 -1
  71. package/esm/core/persistedAuthState.js +2 -2
  72. package/esm/core/persistedAuthState.js.map +1 -1
  73. package/esm/entrypoint.js +3 -3
  74. package/esm/entrypoint.js.map +1 -1
  75. package/esm/index.d.ts +1 -1
  76. package/esm/index.js +2 -2
  77. package/esm/index.js.map +1 -1
  78. package/esm/keycloak/index.d.ts +3 -3
  79. package/esm/keycloak/index.js +3 -3
  80. package/esm/keycloak/index.js.map +1 -1
  81. package/esm/keycloak/isKeycloak.js.map +1 -1
  82. package/esm/keycloak/keycloak-js/Keycloak.d.ts +1 -1
  83. package/esm/keycloak/keycloak-js/Keycloak.js +9 -9
  84. package/esm/keycloak/keycloak-js/Keycloak.js.map +1 -1
  85. package/esm/keycloak/keycloak-js/index.d.ts +2 -2
  86. package/esm/keycloak/keycloak-js/index.js +2 -2
  87. package/esm/keycloak/keycloak-js/index.js.map +1 -1
  88. package/esm/keycloak/keycloak-js/types.js.map +1 -1
  89. package/esm/keycloak/keycloakIssuerUriParsed.js +3 -3
  90. package/esm/keycloak/keycloakIssuerUriParsed.js.map +1 -1
  91. package/esm/keycloak/keycloakUtils.d.ts +1 -1
  92. package/esm/keycloak/keycloakUtils.js +3 -3
  93. package/esm/keycloak/keycloakUtils.js.map +1 -1
  94. package/esm/keycloak-js.d.ts +1 -1
  95. package/esm/keycloak-js.js +2 -2
  96. package/esm/keycloak-js.js.map +1 -1
  97. package/esm/mock/index.d.ts +1 -1
  98. package/esm/mock/index.js +2 -2
  99. package/esm/mock/index.js.map +1 -1
  100. package/esm/mock/oidc.d.ts +1 -1
  101. package/esm/mock/oidc.js +6 -6
  102. package/esm/mock/oidc.js.map +1 -1
  103. package/esm/mock/react.d.ts +8 -8
  104. package/esm/mock/react.js +3 -3
  105. package/esm/mock/react.js.map +1 -1
  106. package/esm/react/index.d.ts +1 -1
  107. package/esm/react/index.js +2 -2
  108. package/esm/react/index.js.map +1 -1
  109. package/esm/react/react.d.ts +2 -2
  110. package/esm/react/react.js +6 -6
  111. package/esm/react/react.js.map +1 -1
  112. package/esm/tools/Deferred.js.map +1 -1
  113. package/esm/tools/EphemeralSessionStorage.js +2 -2
  114. package/esm/tools/EphemeralSessionStorage.js.map +1 -1
  115. package/esm/tools/Evt.js +3 -3
  116. package/esm/tools/Evt.js.map +1 -1
  117. package/esm/tools/StatefulEvt.js.map +1 -1
  118. package/esm/tools/ValueOrAsyncGetter.js.map +1 -1
  119. package/esm/tools/asymmetricEncryption.js.map +1 -1
  120. package/esm/tools/base64.js.map +1 -1
  121. package/esm/tools/createObjectThatThrowsIfAccessed.js.map +1 -1
  122. package/esm/tools/decodeJwt.js.map +1 -1
  123. package/esm/tools/generateUrlSafeRandom.js.map +1 -1
  124. package/esm/tools/getDownlinkAndRtt.js +2 -2
  125. package/esm/tools/getDownlinkAndRtt.js.map +1 -1
  126. package/esm/tools/getIsOnline.js +2 -2
  127. package/esm/tools/getIsOnline.js.map +1 -1
  128. package/esm/tools/getIsValidRemoteJson.js.map +1 -1
  129. package/esm/tools/getPrUserInteraction.js +2 -2
  130. package/esm/tools/getPrUserInteraction.js.map +1 -1
  131. package/esm/tools/getUserEnvironmentInfo.js.map +1 -1
  132. package/esm/tools/haveSharedParentDomain.js.map +1 -1
  133. package/esm/tools/isDev.js.map +1 -1
  134. package/esm/tools/parseKeycloakIssuerUri.js +2 -2
  135. package/esm/tools/parseKeycloakIssuerUri.js.map +1 -1
  136. package/esm/tools/readExpirationTimeInJwt.js +3 -3
  137. package/esm/tools/readExpirationTimeInJwt.js.map +1 -1
  138. package/esm/tools/startCountdown.js +2 -2
  139. package/esm/tools/startCountdown.js.map +1 -1
  140. package/esm/tools/subscribeToUserInteraction.js +2 -2
  141. package/esm/tools/subscribeToUserInteraction.js.map +1 -1
  142. package/esm/tools/toFullyQualifiedUrl.js.map +1 -1
  143. package/esm/tools/toHumanReadableDuration.js.map +1 -1
  144. package/esm/tools/urlSearchParams.js.map +1 -1
  145. package/esm/tools/workerTimers.js +2 -2
  146. package/esm/tools/workerTimers.js.map +1 -1
  147. package/index.js.map +1 -1
  148. package/keycloak/index.js.map +1 -1
  149. package/keycloak/isKeycloak.js.map +1 -1
  150. package/keycloak/keycloak-js/Keycloak.js.map +1 -1
  151. package/keycloak/keycloak-js/index.js.map +1 -1
  152. package/keycloak/keycloak-js/types.js.map +1 -1
  153. package/keycloak/keycloakIssuerUriParsed.js.map +1 -1
  154. package/keycloak/keycloakUtils.js.map +1 -1
  155. package/keycloak-js.js.map +1 -1
  156. package/mock/index.js.map +1 -1
  157. package/mock/oidc.js.map +1 -1
  158. package/mock/react.js.map +1 -1
  159. package/package.json +11 -33
  160. package/react/index.js.map +1 -1
  161. package/react/react.js.map +1 -1
  162. package/src/backend.ts +391 -0
  163. package/src/core/AuthResponse.ts +26 -0
  164. package/src/core/Oidc.ts +140 -0
  165. package/src/core/OidcInitializationError.ts +19 -0
  166. package/src/core/OidcMetadata.ts +271 -0
  167. package/src/core/StateData.ts +118 -0
  168. package/src/core/configId.ts +3 -0
  169. package/src/core/createOidc.ts +1576 -0
  170. package/src/core/diagnostic.ts +267 -0
  171. package/src/core/evtIsUserActive.ts +108 -0
  172. package/src/core/handleOidcCallback.ts +321 -0
  173. package/src/core/iframeMessageProtection.ts +100 -0
  174. package/src/core/index.ts +4 -0
  175. package/src/core/initialLocationHref.ts +5 -0
  176. package/src/core/isNewBrowserSession.ts +37 -0
  177. package/src/core/loginOrGoToAuthServer.ts +324 -0
  178. package/src/core/loginPropagationToOtherTabs.ts +51 -0
  179. package/src/core/loginSilent.ts +242 -0
  180. package/src/core/logoutPropagationToOtherTabs.ts +53 -0
  181. package/src/core/oidcClientTsUserToTokens.ts +229 -0
  182. package/src/core/ongoingLoginOrRefreshProcesses.ts +47 -0
  183. package/src/core/persistedAuthState.ts +122 -0
  184. package/src/entrypoint.ts +69 -0
  185. package/src/index.ts +1 -0
  186. package/src/keycloak/index.ts +8 -0
  187. package/src/keycloak/isKeycloak.ts +23 -0
  188. package/src/keycloak/keycloak-js/Keycloak.ts +1097 -0
  189. package/src/keycloak/keycloak-js/index.ts +2 -0
  190. package/src/keycloak/keycloak-js/types.ts +442 -0
  191. package/src/keycloak/keycloakIssuerUriParsed.ts +29 -0
  192. package/src/keycloak/keycloakUtils.ts +90 -0
  193. package/src/keycloak-js.ts +1 -0
  194. package/src/mock/index.ts +1 -0
  195. package/src/mock/oidc.ts +211 -0
  196. package/src/mock/react.tsx +11 -0
  197. package/src/react/index.ts +1 -0
  198. package/src/react/react.tsx +476 -0
  199. package/src/tools/Deferred.ts +33 -0
  200. package/src/tools/EphemeralSessionStorage.ts +223 -0
  201. package/src/tools/Evt.ts +56 -0
  202. package/src/tools/StatefulEvt.ts +38 -0
  203. package/src/tools/ValueOrAsyncGetter.ts +1 -0
  204. package/src/tools/asymmetricEncryption.ts +184 -0
  205. package/src/tools/base64.ts +7 -0
  206. package/src/tools/createObjectThatThrowsIfAccessed.ts +40 -0
  207. package/src/tools/decodeJwt.ts +95 -0
  208. package/src/tools/generateUrlSafeRandom.ts +26 -0
  209. package/src/tools/getDownlinkAndRtt.ts +22 -0
  210. package/src/tools/getIsOnline.ts +20 -0
  211. package/src/tools/getIsValidRemoteJson.ts +18 -0
  212. package/src/tools/getPrUserInteraction.ts +27 -0
  213. package/src/tools/getUserEnvironmentInfo.ts +42 -0
  214. package/src/tools/haveSharedParentDomain.ts +13 -0
  215. package/src/tools/isDev.ts +30 -0
  216. package/src/tools/parseKeycloakIssuerUri.ts +49 -0
  217. package/src/tools/readExpirationTimeInJwt.ts +16 -0
  218. package/src/tools/startCountdown.ts +36 -0
  219. package/src/tools/subscribeToUserInteraction.ts +33 -0
  220. package/src/tools/toFullyQualifiedUrl.ts +58 -0
  221. package/src/tools/toHumanReadableDuration.ts +21 -0
  222. package/src/tools/urlSearchParams.ts +130 -0
  223. package/src/tools/workerTimers.ts +57 -0
  224. package/src/vendor/backend/evt.ts +2 -0
  225. package/src/vendor/backend/jsonwebtoken.ts +1 -0
  226. package/src/vendor/backend/node-fetch.ts +2 -0
  227. package/src/vendor/backend/node-jose.ts +1 -0
  228. package/src/vendor/backend/tsafe.ts +5 -0
  229. package/src/vendor/backend/zod.ts +1 -0
  230. package/src/vendor/frontend/oidc-client-ts.ts +1 -0
  231. package/src/vendor/frontend/tsafe.ts +6 -0
  232. package/src/vendor/frontend/worker-timers.ts +2 -0
  233. package/tools/Deferred.js.map +1 -1
  234. package/tools/EphemeralSessionStorage.js.map +1 -1
  235. package/tools/Evt.js.map +1 -1
  236. package/tools/StatefulEvt.js.map +1 -1
  237. package/tools/ValueOrAsyncGetter.js.map +1 -1
  238. package/tools/asymmetricEncryption.js.map +1 -1
  239. package/tools/base64.js.map +1 -1
  240. package/tools/createObjectThatThrowsIfAccessed.js.map +1 -1
  241. package/tools/decodeJwt.js.map +1 -1
  242. package/tools/generateUrlSafeRandom.js.map +1 -1
  243. package/tools/getDownlinkAndRtt.js.map +1 -1
  244. package/tools/getIsOnline.js.map +1 -1
  245. package/tools/getIsValidRemoteJson.js.map +1 -1
  246. package/tools/getPrUserInteraction.js.map +1 -1
  247. package/tools/getUserEnvironmentInfo.js.map +1 -1
  248. package/tools/haveSharedParentDomain.js.map +1 -1
  249. package/tools/isDev.js.map +1 -1
  250. package/tools/parseKeycloakIssuerUri.js.map +1 -1
  251. package/tools/readExpirationTimeInJwt.js.map +1 -1
  252. package/tools/startCountdown.js.map +1 -1
  253. package/tools/subscribeToUserInteraction.js.map +1 -1
  254. package/tools/toFullyQualifiedUrl.js.map +1 -1
  255. package/tools/toHumanReadableDuration.js.map +1 -1
  256. package/tools/urlSearchParams.js.map +1 -1
  257. package/tools/workerTimers.js.map +1 -1
@@ -0,0 +1,211 @@
1
+ import type { Oidc } from "../core";
2
+ import { createObjectThatThrowsIfAccessed } from "../tools/createObjectThatThrowsIfAccessed";
3
+ import { id } from "../vendor/frontend/tsafe";
4
+ import { toFullyQualifiedUrl } from "../tools/toFullyQualifiedUrl";
5
+ import { getSearchParam, addOrUpdateSearchParam } from "../tools/urlSearchParams";
6
+ import { initialLocationHref } from "../core/initialLocationHref";
7
+
8
+ export type ParamsOfCreateMockOidc<
9
+ DecodedIdToken extends Record<string, unknown> = Record<string, unknown>,
10
+ AutoLogin extends boolean = false
11
+ > = {
12
+ mockedParams?: Partial<Oidc["params"]>;
13
+ mockedTokens?: Partial<Oidc.Tokens<DecodedIdToken>>;
14
+ /**
15
+ * The URL of the home page of your app.
16
+ * We need to know this so we know where to redirect when you call `logout({ redirectTo: "home"})`.
17
+ * In the majority of cases it should be `homeUrl: "/"` but it could aso be something like `homeUrl: "/dashboard"`
18
+ * if your web app isn't hosted at the root of the domain.
19
+ */
20
+ homeUrl: string;
21
+ autoLogin?: AutoLogin;
22
+ postLoginRedirectUrl?: string;
23
+ } & (AutoLogin extends true
24
+ ? { isUserInitiallyLoggedIn?: true }
25
+ : {
26
+ isUserInitiallyLoggedIn: boolean;
27
+ });
28
+
29
+ const URL_SEARCH_PARAM_NAME = "isUserLoggedIn";
30
+
31
+ export async function createMockOidc<
32
+ DecodedIdToken extends Record<string, unknown> = Oidc.Tokens.DecodedIdToken_base,
33
+ AutoLogin extends boolean = false
34
+ >(
35
+ params: ParamsOfCreateMockOidc<DecodedIdToken, AutoLogin>
36
+ ): Promise<AutoLogin extends true ? Oidc.LoggedIn<DecodedIdToken> : Oidc<DecodedIdToken>> {
37
+ const {
38
+ isUserInitiallyLoggedIn = true,
39
+ mockedParams = {},
40
+ mockedTokens = {},
41
+ homeUrl: homeUrl_params,
42
+ autoLogin = false,
43
+ postLoginRedirectUrl
44
+ } = params;
45
+
46
+ const isUserLoggedIn = (() => {
47
+ const { wasPresent, value } = getSearchParam({
48
+ url: initialLocationHref,
49
+ name: URL_SEARCH_PARAM_NAME
50
+ });
51
+
52
+ if (!wasPresent) {
53
+ return isUserInitiallyLoggedIn;
54
+ }
55
+
56
+ remove_from_url: {
57
+ const { wasPresent, url_withoutTheParam } = getSearchParam({
58
+ url: window.location.href,
59
+ name: URL_SEARCH_PARAM_NAME
60
+ });
61
+
62
+ if (!wasPresent) {
63
+ break remove_from_url;
64
+ }
65
+
66
+ window.history.replaceState({}, "", url_withoutTheParam);
67
+ }
68
+
69
+ return value === "true";
70
+ })();
71
+
72
+ const homeUrl = toFullyQualifiedUrl({
73
+ urlish: homeUrl_params,
74
+ doAssertNoQueryParams: true,
75
+ doOutputWithTrailingSlash: true
76
+ });
77
+
78
+ const common: Oidc.Common = {
79
+ params: {
80
+ clientId: mockedParams.clientId ?? "mymockclient",
81
+ issuerUri: mockedParams.issuerUri ?? "https://my-mock-oidc-server.net/realms/mymockrealm"
82
+ }
83
+ };
84
+
85
+ const loginOrGoToAuthServer = async (params: {
86
+ redirectUrl: string | undefined;
87
+ }): Promise<never> => {
88
+ const { redirectUrl: redirectUrl_params } = params;
89
+
90
+ const redirectUrl = addOrUpdateSearchParam({
91
+ url: (() => {
92
+ if (redirectUrl_params === undefined) {
93
+ return window.location.href;
94
+ }
95
+
96
+ return toFullyQualifiedUrl({
97
+ urlish: redirectUrl_params,
98
+ doAssertNoQueryParams: false
99
+ });
100
+ })(),
101
+ name: URL_SEARCH_PARAM_NAME,
102
+ value: "true",
103
+ encodeMethod: "www-form"
104
+ });
105
+
106
+ window.location.href = redirectUrl;
107
+
108
+ return new Promise<never>(() => {});
109
+ };
110
+
111
+ if (!isUserLoggedIn) {
112
+ const oidc = id<Oidc.NotLoggedIn>({
113
+ ...common,
114
+ isUserLoggedIn: false,
115
+ login: ({ redirectUrl }) => loginOrGoToAuthServer({ redirectUrl }),
116
+ initializationError: undefined
117
+ });
118
+ if (autoLogin) {
119
+ await oidc.login({
120
+ redirectUrl: postLoginRedirectUrl,
121
+ doesCurrentHrefRequiresAuth: true
122
+ });
123
+ // Never here
124
+ }
125
+ // @ts-expect-error: We know what we are doing
126
+ return oidc;
127
+ }
128
+
129
+ const oidc: Oidc.LoggedIn<DecodedIdToken> = {
130
+ ...common,
131
+ isUserLoggedIn: true,
132
+ renewTokens: async () => {},
133
+ ...(() => {
134
+ const tokens_common: Oidc.Tokens.Common<DecodedIdToken> = {
135
+ accessToken: mockedTokens.accessToken ?? "mocked-access-token",
136
+ accessTokenExpirationTime: mockedTokens.accessTokenExpirationTime ?? Infinity,
137
+ idToken: mockedTokens.idToken ?? "mocked-id-token",
138
+ decodedIdToken:
139
+ mockedTokens.decodedIdToken ??
140
+ createObjectThatThrowsIfAccessed<DecodedIdToken>({
141
+ debugMessage: [
142
+ "You haven't provided a mocked decodedIdToken",
143
+ "See https://docs.oidc-spa.dev/v/v7/mock"
144
+ ].join("\n")
145
+ }),
146
+ decodedIdToken_original:
147
+ mockedTokens.decodedIdToken_original ??
148
+ createObjectThatThrowsIfAccessed<Oidc.Tokens.DecodedIdToken_base>({
149
+ debugMessage: [
150
+ "You haven't provided a mocked decodedIdToken_original",
151
+ "See https://docs.oidc-spa.dev/v/v7/mock"
152
+ ].join("\n")
153
+ }),
154
+ issuedAtTime: Date.now()
155
+ };
156
+
157
+ const tokens: Oidc.Tokens<DecodedIdToken> =
158
+ mockedTokens.refreshToken !== undefined || mockedTokens.hasRefreshToken === true
159
+ ? id<Oidc.Tokens.WithRefreshToken<DecodedIdToken>>({
160
+ ...tokens_common,
161
+ hasRefreshToken: true,
162
+ refreshToken: mockedTokens.refreshToken ?? "mocked-refresh-token",
163
+ refreshTokenExpirationTime: mockedTokens.refreshTokenExpirationTime
164
+ })
165
+ : id<Oidc.Tokens.WithoutRefreshToken<DecodedIdToken>>({
166
+ ...tokens_common,
167
+ hasRefreshToken: false
168
+ });
169
+
170
+ return {
171
+ getTokens: () => Promise.resolve(tokens),
172
+ getDecodedIdToken: () => tokens_common.decodedIdToken
173
+ };
174
+ })(),
175
+ subscribeToTokensChange: () => ({
176
+ unsubscribe: () => {}
177
+ }),
178
+ logout: params => {
179
+ const redirectUrl = addOrUpdateSearchParam({
180
+ url: (() => {
181
+ switch (params.redirectTo) {
182
+ case "current page":
183
+ return window.location.href;
184
+ case "home":
185
+ return homeUrl;
186
+ case "specific url":
187
+ return toFullyQualifiedUrl({
188
+ urlish: params.url,
189
+ doAssertNoQueryParams: false
190
+ });
191
+ }
192
+ })(),
193
+ name: URL_SEARCH_PARAM_NAME,
194
+ value: "false",
195
+ encodeMethod: "www-form"
196
+ });
197
+
198
+ window.location.href = redirectUrl;
199
+
200
+ return new Promise<never>(() => {});
201
+ },
202
+ subscribeToAutoLogoutCountdown: () => ({
203
+ unsubscribeFromAutoLogoutCountdown: () => {}
204
+ }),
205
+ goToAuthServer: async ({ redirectUrl }) => loginOrGoToAuthServer({ redirectUrl }),
206
+ isNewBrowserSession: false,
207
+ backFromAuthServer: undefined
208
+ };
209
+
210
+ return oidc;
211
+ }
@@ -0,0 +1,11 @@
1
+ import { createOidcReactApi_dependencyInjection } from "../react/react";
2
+ import { createMockOidc, type ParamsOfCreateMockOidc } from "./oidc";
3
+ import type { ValueOrAsyncGetter } from "../tools/ValueOrAsyncGetter";
4
+
5
+ /** @see: https://docs.oidc-spa.dev/v/v7/mock */
6
+ export function createMockReactOidc<
7
+ DecodedIdToken extends Record<string, unknown> = Record<string, unknown>,
8
+ AutoLogin extends boolean = false
9
+ >(params: ValueOrAsyncGetter<ParamsOfCreateMockOidc<DecodedIdToken, AutoLogin>>) {
10
+ return createOidcReactApi_dependencyInjection(params, createMockOidc);
11
+ }
@@ -0,0 +1 @@
1
+ export { type OidcReact, createReactOidc } from "./react";
@@ -0,0 +1,476 @@
1
+ import {
2
+ useEffect,
3
+ useState,
4
+ createContext,
5
+ useContext,
6
+ type ReactNode,
7
+ type ComponentType,
8
+ type FC,
9
+ type JSX
10
+ } from "react";
11
+ import {
12
+ type Oidc,
13
+ createOidc,
14
+ type ParamsOfCreateOidc,
15
+ OidcInitializationError,
16
+ handleOidcCallback
17
+ } from "../core";
18
+ import { assert, type Equals, type Param0 } from "../vendor/frontend/tsafe";
19
+ import { id } from "../vendor/frontend/tsafe";
20
+ import type { ValueOrAsyncGetter } from "../tools/ValueOrAsyncGetter";
21
+ import { Deferred } from "../tools/Deferred";
22
+ import { toFullyQualifiedUrl } from "../tools/toFullyQualifiedUrl";
23
+
24
+ export type OidcReact<DecodedIdToken extends Record<string, unknown>> =
25
+ | OidcReact.NotLoggedIn
26
+ | OidcReact.LoggedIn<DecodedIdToken>;
27
+
28
+ export namespace OidcReact {
29
+ export type Common = Oidc.Common & {
30
+ useAutoLogoutWarningCountdown: (params: { warningDurationSeconds: number }) => {
31
+ secondsLeft: number | undefined;
32
+ };
33
+ };
34
+
35
+ export type NotLoggedIn = Common & {
36
+ isUserLoggedIn: false;
37
+ login: (params?: {
38
+ extraQueryParams?: Record<string, string | undefined>;
39
+ redirectUrl?: string;
40
+ transformUrlBeforeRedirect?: (url: string) => string;
41
+ doesCurrentHrefRequiresAuth?: boolean;
42
+ }) => Promise<never>;
43
+ initializationError: OidcInitializationError | undefined;
44
+
45
+ decodedIdToken?: never;
46
+ logout?: never;
47
+ renewTokens?: never;
48
+ goToAuthServer?: never;
49
+ backFromAuthServer?: never;
50
+ isNewBrowserSession?: never;
51
+ };
52
+
53
+ export type LoggedIn<DecodedIdToken extends Record<string, unknown>> = Common & {
54
+ isUserLoggedIn: true;
55
+ decodedIdToken: DecodedIdToken;
56
+ logout: Oidc.LoggedIn["logout"];
57
+ renewTokens: Oidc.LoggedIn["renewTokens"];
58
+ login?: never;
59
+ initializationError?: never;
60
+ goToAuthServer: (params: {
61
+ extraQueryParams?: Record<string, string>;
62
+ redirectUrl?: string;
63
+ transformUrlBeforeRedirect?: (url: string) => string;
64
+ }) => Promise<never>;
65
+
66
+ backFromAuthServer:
67
+ | {
68
+ extraQueryParams: Record<string, string>;
69
+ result: Record<string, string>;
70
+ }
71
+ | undefined;
72
+
73
+ isNewBrowserSession: boolean;
74
+ };
75
+ }
76
+ {
77
+ type Actual = Param0<OidcReact.NotLoggedIn["login"]>;
78
+ type Expected = Omit<Param0<Oidc.NotLoggedIn["login"]>, "doesCurrentHrefRequiresAuth"> & {
79
+ doesCurrentHrefRequiresAuth?: boolean;
80
+ };
81
+
82
+ assert<Equals<Actual, Expected>>();
83
+ }
84
+
85
+ type OidcReactApi<DecodedIdToken extends Record<string, unknown>, AutoLogin extends boolean> = {
86
+ OidcProvider: AutoLogin extends true
87
+ ? (props: {
88
+ fallback?: ReactNode;
89
+ ErrorFallback?: (props: { initializationError: OidcInitializationError }) => ReactNode;
90
+ children: ReactNode;
91
+ }) => JSX.Element
92
+ : (props: { fallback?: ReactNode; children: ReactNode }) => JSX.Element;
93
+ useOidc: AutoLogin extends true
94
+ ? {
95
+ (params?: { assert: "user logged in" }): OidcReact.LoggedIn<DecodedIdToken>;
96
+ }
97
+ : {
98
+ (params?: { assert?: undefined }): OidcReact<DecodedIdToken>;
99
+ (params: { assert: "user logged in" }): OidcReact.LoggedIn<DecodedIdToken>;
100
+ (params: { assert: "user not logged in" }): OidcReact.NotLoggedIn;
101
+ };
102
+ getOidc: () => Promise<
103
+ AutoLogin extends true ? Oidc.LoggedIn<DecodedIdToken> : Oidc<DecodedIdToken>
104
+ >;
105
+ } & (AutoLogin extends true
106
+ ? {}
107
+ : {
108
+ withLoginEnforced: <Props extends Record<string, unknown>>(
109
+ Component: ComponentType<Props>,
110
+ params?: {
111
+ onRedirecting: () => JSX.Element | null;
112
+ }
113
+ ) => FC<Props>;
114
+ enforceLogin: (loaderParams: {
115
+ request?: { url?: string };
116
+ cause?: "preload" | string;
117
+ location?: {
118
+ href?: string;
119
+ };
120
+ }) => Promise<void | never>;
121
+ });
122
+
123
+ export function createOidcReactApi_dependencyInjection<
124
+ DecodedIdToken extends Record<string, unknown>,
125
+ ParamsOfCreateOidc extends {
126
+ autoLogin?: boolean;
127
+ } & (
128
+ | {
129
+ decodedIdTokenSchema: { parse: (data: unknown) => DecodedIdToken } | undefined;
130
+ }
131
+ | {}
132
+ )
133
+ >(
134
+ paramsOrGetParams: ValueOrAsyncGetter<ParamsOfCreateOidc>,
135
+ createOidc: (params: ParamsOfCreateOidc) => Promise<Oidc<DecodedIdToken>>
136
+ ): OidcReactApi<
137
+ DecodedIdToken,
138
+ ParamsOfCreateOidc extends { autoLogin?: true | undefined } ? true : false
139
+ > {
140
+ const dReadyToCreate = new Deferred<void>();
141
+
142
+ const oidcContext = createContext<{ oidc: Oidc<DecodedIdToken>; fallback: ReactNode } | undefined>(
143
+ undefined
144
+ );
145
+
146
+ // NOTE: It can be InitializationError only if autoLogin is true
147
+ const prOidcOrInitializationError = (async () => {
148
+ // We're doing this here just for people that wouldn't have
149
+ // configured the early init in entrypoint.
150
+ {
151
+ const { isHandled } = handleOidcCallback();
152
+
153
+ if (isHandled) {
154
+ return new Promise<never>(() => {});
155
+ }
156
+ }
157
+
158
+ const params = await (async () => {
159
+ if (typeof paramsOrGetParams === "function") {
160
+ const getParams = paramsOrGetParams;
161
+
162
+ await dReadyToCreate.pr;
163
+
164
+ const params = await getParams();
165
+
166
+ return params;
167
+ }
168
+
169
+ const params = paramsOrGetParams;
170
+
171
+ return params;
172
+ })();
173
+
174
+ let oidc: Oidc<DecodedIdToken>;
175
+
176
+ try {
177
+ oidc = await createOidc(params);
178
+ } catch (error) {
179
+ if (!(error instanceof OidcInitializationError)) {
180
+ throw error;
181
+ }
182
+
183
+ return error;
184
+ }
185
+
186
+ return oidc;
187
+ })();
188
+
189
+ let prOidcOrInitializationError_resolvedValue:
190
+ | Oidc<DecodedIdToken>
191
+ | OidcInitializationError
192
+ | undefined = undefined;
193
+ prOidcOrInitializationError.then(value => (prOidcOrInitializationError_resolvedValue = value));
194
+
195
+ function OidcProvider(props: {
196
+ fallback?: ReactNode;
197
+ ErrorFallback?: (props: { initializationError: OidcInitializationError }) => ReactNode;
198
+ children: ReactNode;
199
+ }) {
200
+ const { fallback, ErrorFallback, children } = props;
201
+
202
+ const [oidcOrInitializationError, setOidcOrInitializationError] = useState<
203
+ Oidc<DecodedIdToken> | OidcInitializationError | undefined
204
+ >(prOidcOrInitializationError_resolvedValue);
205
+
206
+ useEffect(() => {
207
+ if (oidcOrInitializationError !== undefined) {
208
+ return;
209
+ }
210
+
211
+ dReadyToCreate.resolve();
212
+ prOidcOrInitializationError.then(setOidcOrInitializationError);
213
+ }, []);
214
+
215
+ if (oidcOrInitializationError === undefined) {
216
+ return <>{fallback === undefined ? null : fallback}</>;
217
+ }
218
+
219
+ if (oidcOrInitializationError instanceof OidcInitializationError) {
220
+ const initializationError = oidcOrInitializationError;
221
+
222
+ return (
223
+ <>
224
+ {ErrorFallback === undefined ? (
225
+ <h1 style={{ color: "red" }}>
226
+ An error occurred while initializing the OIDC client:&nbsp;
227
+ {initializationError.message}
228
+ </h1>
229
+ ) : (
230
+ <ErrorFallback initializationError={initializationError} />
231
+ )}
232
+ </>
233
+ );
234
+ }
235
+
236
+ const oidc = oidcOrInitializationError;
237
+
238
+ return (
239
+ <oidcContext.Provider value={{ oidc, fallback: fallback ?? null }}>
240
+ {children}
241
+ </oidcContext.Provider>
242
+ );
243
+ }
244
+
245
+ const useAutoLogoutWarningCountdown: OidcReact.LoggedIn<DecodedIdToken>["useAutoLogoutWarningCountdown"] =
246
+ ({ warningDurationSeconds }) => {
247
+ const contextValue = useContext(oidcContext);
248
+
249
+ assert(contextValue !== undefined);
250
+
251
+ const { oidc } = contextValue;
252
+
253
+ const [secondsLeft, setSecondsLeft] = useState<number | undefined>(undefined);
254
+
255
+ useEffect(() => {
256
+ if (!oidc.isUserLoggedIn) {
257
+ return;
258
+ }
259
+
260
+ const { unsubscribeFromAutoLogoutCountdown } = oidc.subscribeToAutoLogoutCountdown(
261
+ ({ secondsLeft }) =>
262
+ setSecondsLeft(
263
+ secondsLeft === undefined || secondsLeft > warningDurationSeconds
264
+ ? undefined
265
+ : secondsLeft
266
+ )
267
+ );
268
+
269
+ return () => {
270
+ unsubscribeFromAutoLogoutCountdown();
271
+ };
272
+ }, [warningDurationSeconds]);
273
+
274
+ return { secondsLeft };
275
+ };
276
+
277
+ function useOidc(params?: {
278
+ assert?: "user logged in" | "user not logged in";
279
+ }): OidcReact<DecodedIdToken> {
280
+ const { assert: assert_params } = params ?? {};
281
+
282
+ const contextValue = useContext(oidcContext);
283
+
284
+ assert(contextValue !== undefined, "You must use useOidc inside the corresponding OidcProvider");
285
+
286
+ const { oidc } = contextValue;
287
+
288
+ check_assertion: {
289
+ if (assert_params === undefined) {
290
+ break check_assertion;
291
+ }
292
+
293
+ const getMessage = (v: string) =>
294
+ [
295
+ "There is a logic error in the application.",
296
+ `If this component is mounted the user is supposed ${v}.`,
297
+ "An explicit assertion was made in this sense."
298
+ ].join(" ");
299
+
300
+ switch (assert_params) {
301
+ case "user logged in":
302
+ if (!oidc.isUserLoggedIn) {
303
+ throw new Error(getMessage("to be logged in but currently they arn't"));
304
+ }
305
+ break;
306
+ case "user not logged in":
307
+ if (oidc.isUserLoggedIn) {
308
+ throw new Error(getMessage("not to be logged in but currently they are"));
309
+ }
310
+ break;
311
+ default:
312
+ assert<Equals<typeof assert_params, never>>(false);
313
+ }
314
+ }
315
+
316
+ const [, reRenderIfDecodedIdTokenChanged] = useState(
317
+ !oidc.isUserLoggedIn ? undefined : oidc.getDecodedIdToken()
318
+ );
319
+
320
+ useEffect(() => {
321
+ if (!oidc.isUserLoggedIn) {
322
+ return;
323
+ }
324
+
325
+ const { unsubscribe } = oidc.subscribeToTokensChange(() =>
326
+ reRenderIfDecodedIdTokenChanged(oidc.getDecodedIdToken())
327
+ );
328
+
329
+ reRenderIfDecodedIdTokenChanged(oidc.getDecodedIdToken());
330
+
331
+ return unsubscribe;
332
+ }, []);
333
+
334
+ const common: OidcReact.Common = {
335
+ params: oidc.params,
336
+ useAutoLogoutWarningCountdown
337
+ };
338
+
339
+ if (!oidc.isUserLoggedIn) {
340
+ return id<OidcReact.NotLoggedIn>({
341
+ ...common,
342
+ isUserLoggedIn: false,
343
+ login: ({ doesCurrentHrefRequiresAuth = false, ...rest } = {}) =>
344
+ oidc.login({ doesCurrentHrefRequiresAuth, ...rest }),
345
+ initializationError: oidc.initializationError
346
+ });
347
+ }
348
+
349
+ const oidcReact: OidcReact.LoggedIn<DecodedIdToken> = {
350
+ ...common,
351
+ isUserLoggedIn: true,
352
+ decodedIdToken: oidc.getDecodedIdToken(),
353
+ logout: oidc.logout,
354
+ renewTokens: oidc.renewTokens,
355
+ goToAuthServer: oidc.goToAuthServer,
356
+ isNewBrowserSession: oidc.isNewBrowserSession,
357
+ backFromAuthServer: oidc.backFromAuthServer
358
+ };
359
+
360
+ return oidcReact;
361
+ }
362
+
363
+ function withLoginEnforced<Props extends Record<string, unknown>>(
364
+ Component: ComponentType<Props>,
365
+ params?: {
366
+ onRedirecting?: () => JSX.Element | null;
367
+ }
368
+ ): FC<Props> {
369
+ const { onRedirecting } = params ?? {};
370
+
371
+ function ComponentWithLoginEnforced(props: Props) {
372
+ const contextValue = useContext(oidcContext);
373
+
374
+ assert(contextValue !== undefined, "094283");
375
+
376
+ const { oidc, fallback } = contextValue;
377
+
378
+ useEffect(() => {
379
+ if (oidc.isUserLoggedIn) {
380
+ return;
381
+ }
382
+
383
+ oidc.login({ doesCurrentHrefRequiresAuth: true });
384
+ }, []);
385
+
386
+ if (!oidc.isUserLoggedIn) {
387
+ return onRedirecting === undefined ? fallback : onRedirecting();
388
+ }
389
+
390
+ return <Component {...props} />;
391
+ }
392
+
393
+ ComponentWithLoginEnforced.displayName = `${
394
+ Component.displayName ?? Component.name ?? "Component"
395
+ }WithLoginEnforced`;
396
+
397
+ return ComponentWithLoginEnforced;
398
+ }
399
+
400
+ async function enforceLogin(loaderParams: {
401
+ request?: { url?: string };
402
+ cause?: "preload" | string;
403
+ location?: { href?: string };
404
+ }): Promise<void | never> {
405
+ const { cause } = loaderParams;
406
+
407
+ const redirectUrl = (() => {
408
+ if (loaderParams.request?.url !== undefined) {
409
+ return toFullyQualifiedUrl({
410
+ urlish: loaderParams.request.url,
411
+ doAssertNoQueryParams: false
412
+ });
413
+ }
414
+
415
+ if (loaderParams.location?.href !== undefined) {
416
+ return toFullyQualifiedUrl({
417
+ urlish: loaderParams.location.href,
418
+ doAssertNoQueryParams: false
419
+ });
420
+ }
421
+
422
+ return location.href;
423
+ })();
424
+
425
+ const oidc = await getOidc();
426
+
427
+ if (!oidc.isUserLoggedIn) {
428
+ if (cause === "preload") {
429
+ throw new Error(
430
+ "oidc-spa: User is not yet logged in. This is an expected error, nothing to be addressed."
431
+ );
432
+ }
433
+ const doesCurrentHrefRequiresAuth =
434
+ location.href.replace(/\/$/, "") === redirectUrl.replace(/\/$/, "");
435
+
436
+ await oidc.login({
437
+ redirectUrl,
438
+ doesCurrentHrefRequiresAuth
439
+ });
440
+ }
441
+ }
442
+
443
+ async function getOidc(): Promise<Oidc<DecodedIdToken>> {
444
+ dReadyToCreate.resolve();
445
+
446
+ const oidcOrInitializationError = await prOidcOrInitializationError;
447
+
448
+ if (oidcOrInitializationError instanceof OidcInitializationError) {
449
+ const error = oidcOrInitializationError;
450
+ throw error;
451
+ }
452
+
453
+ const oidc = oidcOrInitializationError;
454
+
455
+ return oidc;
456
+ }
457
+
458
+ const oidcReact: OidcReactApi<DecodedIdToken, false> = {
459
+ OidcProvider,
460
+ useOidc: useOidc as any,
461
+ getOidc,
462
+ withLoginEnforced,
463
+ enforceLogin
464
+ };
465
+
466
+ // @ts-expect-error: We know what we are doing
467
+ return oidcReact;
468
+ }
469
+
470
+ /** @see: https://docs.oidc-spa.dev/v/v7/usage#react-api */
471
+ export function createReactOidc<
472
+ DecodedIdToken extends Record<string, unknown> = Oidc.Tokens.DecodedIdToken_base,
473
+ AutoLogin extends boolean = false
474
+ >(params: ValueOrAsyncGetter<ParamsOfCreateOidc<DecodedIdToken, AutoLogin>>) {
475
+ return createOidcReactApi_dependencyInjection(params, createOidc);
476
+ }