@alepha/react 0.11.10 → 0.11.12

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 (128) hide show
  1. package/README.md +1 -183
  2. package/dist/auth/index.browser.js +1460 -0
  3. package/dist/auth/index.browser.js.map +1 -0
  4. package/dist/auth/index.cjs +3647 -0
  5. package/dist/auth/index.cjs.map +1 -0
  6. package/dist/auth/index.d.cts +564 -0
  7. package/dist/auth/index.d.cts.map +1 -0
  8. package/dist/auth/index.d.ts +564 -0
  9. package/dist/auth/index.d.ts.map +1 -0
  10. package/dist/auth/index.js +3615 -0
  11. package/dist/auth/index.js.map +1 -0
  12. package/dist/{index.browser.js → core/index.browser.js} +36 -35
  13. package/dist/core/index.browser.js.map +1 -0
  14. package/dist/{index.cjs → core/index.cjs} +141 -140
  15. package/dist/core/index.cjs.map +1 -0
  16. package/dist/{index.d.cts → core/index.d.cts} +68 -68
  17. package/dist/core/index.d.cts.map +1 -0
  18. package/dist/{index.d.ts → core/index.d.ts} +68 -68
  19. package/dist/core/index.d.ts.map +1 -0
  20. package/dist/{index.js → core/index.js} +39 -38
  21. package/dist/core/index.js.map +1 -0
  22. package/dist/form/index.cjs +2054 -0
  23. package/dist/form/index.cjs.map +1 -0
  24. package/dist/form/index.d.cts +211 -0
  25. package/dist/form/index.d.cts.map +1 -0
  26. package/dist/form/index.d.ts +211 -0
  27. package/dist/form/index.d.ts.map +1 -0
  28. package/dist/form/index.js +2026 -0
  29. package/dist/form/index.js.map +1 -0
  30. package/dist/head/index.browser.js +1503 -0
  31. package/dist/head/index.browser.js.map +1 -0
  32. package/dist/head/index.cjs +1908 -0
  33. package/dist/head/index.cjs.map +1 -0
  34. package/dist/head/index.d.cts +595 -0
  35. package/dist/head/index.d.cts.map +1 -0
  36. package/dist/head/index.d.ts +601 -0
  37. package/dist/head/index.d.ts.map +1 -0
  38. package/dist/head/index.js +1880 -0
  39. package/dist/head/index.js.map +1 -0
  40. package/dist/i18n/index.cjs +1886 -0
  41. package/dist/i18n/index.cjs.map +1 -0
  42. package/dist/i18n/index.d.cts +168 -0
  43. package/dist/i18n/index.d.cts.map +1 -0
  44. package/dist/i18n/index.d.ts +168 -0
  45. package/dist/i18n/index.d.ts.map +1 -0
  46. package/dist/i18n/index.js +1857 -0
  47. package/dist/i18n/index.js.map +1 -0
  48. package/dist/websocket/index.cjs +1774 -0
  49. package/dist/websocket/index.cjs.map +1 -0
  50. package/dist/websocket/index.d.cts +118 -0
  51. package/dist/websocket/index.d.cts.map +1 -0
  52. package/dist/websocket/index.d.ts +118 -0
  53. package/dist/websocket/index.d.ts.map +1 -0
  54. package/dist/websocket/index.js +1750 -0
  55. package/dist/websocket/index.js.map +1 -0
  56. package/package.json +89 -67
  57. package/src/auth/descriptors/$auth.ts +436 -0
  58. package/src/auth/descriptors/$authApple.ts +8 -0
  59. package/src/auth/descriptors/$authGithub.ts +81 -0
  60. package/src/auth/descriptors/$authGoogle.ts +38 -0
  61. package/src/auth/errors/SessionExpiredError.ts +6 -0
  62. package/src/auth/hooks/useAuth.ts +31 -0
  63. package/src/auth/index.browser.ts +16 -0
  64. package/src/auth/index.shared.ts +3 -0
  65. package/src/auth/index.ts +47 -0
  66. package/src/auth/providers/ReactAuthProvider.ts +629 -0
  67. package/src/auth/schemas/tokenResponseSchema.ts +11 -0
  68. package/src/auth/schemas/tokensSchema.ts +21 -0
  69. package/src/auth/schemas/userinfoResponseSchema.ts +10 -0
  70. package/src/auth/services/ReactAuth.ts +124 -0
  71. package/src/{components → core/components}/ErrorViewer.tsx +3 -2
  72. package/src/{components → core/components}/NestedView.tsx +1 -1
  73. package/src/{contexts → core/contexts}/AlephaContext.ts +1 -1
  74. package/src/{descriptors → core/descriptors}/$page.ts +4 -4
  75. package/src/{hooks → core/hooks}/useAction.ts +1 -1
  76. package/src/{hooks → core/hooks}/useAlepha.ts +1 -1
  77. package/src/{hooks → core/hooks}/useClient.ts +1 -1
  78. package/src/{hooks → core/hooks}/useEvents.ts +1 -1
  79. package/src/{hooks → core/hooks}/useInject.ts +1 -1
  80. package/src/{hooks → core/hooks}/useQueryParams.ts +1 -1
  81. package/src/{hooks → core/hooks}/useRouterState.ts +1 -1
  82. package/src/{hooks → core/hooks}/useSchema.ts +3 -3
  83. package/src/{hooks → core/hooks}/useStore.ts +2 -2
  84. package/src/{index.browser.ts → core/index.browser.ts} +4 -4
  85. package/src/{index.ts → core/index.ts} +6 -6
  86. package/src/{providers → core/providers}/ReactBrowserProvider.ts +6 -6
  87. package/src/{providers → core/providers}/ReactBrowserRendererProvider.ts +2 -2
  88. package/src/{providers → core/providers}/ReactBrowserRouterProvider.ts +3 -3
  89. package/src/{providers → core/providers}/ReactPageProvider.ts +3 -3
  90. package/src/{providers → core/providers}/ReactServerProvider.ts +7 -7
  91. package/src/{services → core/services}/ReactPageServerService.ts +2 -2
  92. package/src/{services → core/services}/ReactPageService.ts +1 -1
  93. package/src/{services → core/services}/ReactRouter.ts +1 -1
  94. package/src/form/components/FormState.tsx +17 -0
  95. package/src/form/hooks/useForm.ts +47 -0
  96. package/src/form/hooks/useFormState.ts +130 -0
  97. package/src/form/index.ts +38 -0
  98. package/src/form/services/FormModel.ts +548 -0
  99. package/src/head/descriptors/$head.ts +25 -0
  100. package/src/head/hooks/useHead.ts +62 -0
  101. package/src/head/index.browser.ts +25 -0
  102. package/src/head/index.ts +47 -0
  103. package/src/head/interfaces/Head.ts +46 -0
  104. package/src/head/providers/BrowserHeadProvider.ts +105 -0
  105. package/src/head/providers/HeadProvider.ts +73 -0
  106. package/src/head/providers/ServerHeadProvider.ts +109 -0
  107. package/src/i18n/README.md +76 -0
  108. package/src/i18n/components/Localize.tsx +35 -0
  109. package/src/i18n/descriptors/$dictionary.ts +65 -0
  110. package/src/i18n/hooks/useI18n.ts +18 -0
  111. package/src/i18n/index.ts +34 -0
  112. package/src/i18n/providers/I18nProvider.ts +277 -0
  113. package/src/websocket/hooks/useRoom.tsx +223 -0
  114. package/src/websocket/index.ts +7 -0
  115. package/dist/index.browser.js.map +0 -1
  116. package/dist/index.cjs.map +0 -1
  117. package/dist/index.d.cts.map +0 -1
  118. package/dist/index.d.ts.map +0 -1
  119. package/dist/index.js.map +0 -1
  120. /package/src/{components → core/components}/ClientOnly.tsx +0 -0
  121. /package/src/{components → core/components}/ErrorBoundary.tsx +0 -0
  122. /package/src/{components → core/components}/Link.tsx +0 -0
  123. /package/src/{components → core/components}/NotFound.tsx +0 -0
  124. /package/src/{contexts → core/contexts}/RouterLayerContext.ts +0 -0
  125. /package/src/{errors → core/errors}/Redirection.ts +0 -0
  126. /package/src/{hooks → core/hooks}/useActive.ts +0 -0
  127. /package/src/{hooks → core/hooks}/useRouter.ts +0 -0
  128. /package/src/{index.shared.ts → core/index.shared.ts} +0 -0
@@ -0,0 +1,3647 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ let alepha = require("alepha");
25
+ let alepha_datetime = require("alepha/datetime");
26
+ let alepha_server = require("alepha/server");
27
+ let alepha_server_cache = require("alepha/server/cache");
28
+ let alepha_server_links = require("alepha/server/links");
29
+ let alepha_logger = require("alepha/logger");
30
+ let react = require("react");
31
+ react = __toESM(react);
32
+ let react_jsx_runtime = require("react/jsx-runtime");
33
+ let node_fs = require("node:fs");
34
+ let node_path = require("node:path");
35
+ let alepha_server_static = require("alepha/server/static");
36
+ let react_dom_server = require("react-dom/server");
37
+ let alepha_router = require("alepha/router");
38
+ let alepha_server_cookies = require("alepha/server/cookies");
39
+ let alepha_security = require("alepha/security");
40
+
41
+ //#region src/core/services/ReactPageService.ts
42
+ var ReactPageService = class {
43
+ fetch(pathname, options = {}) {
44
+ throw new alepha.AlephaError("Fetch is not available for this environment.");
45
+ }
46
+ render(name, options = {}) {
47
+ throw new alepha.AlephaError("Render is not available for this environment.");
48
+ }
49
+ };
50
+
51
+ //#endregion
52
+ //#region src/core/descriptors/$page.ts
53
+ /**
54
+ * Main descriptor for defining a React route in the application.
55
+ *
56
+ * The $page descriptor is the core building block for creating type-safe, SSR-enabled React routes.
57
+ * It provides a declarative way to define pages with powerful features:
58
+ *
59
+ * **Routing & Navigation**
60
+ * - URL pattern matching with parameters (e.g., `/users/:id`)
61
+ * - Nested routing with parent-child relationships
62
+ * - Type-safe URL parameter and query string validation
63
+ *
64
+ * **Data Loading**
65
+ * - Server-side data fetching with the `resolve` function
66
+ * - Automatic serialization and hydration for SSR
67
+ * - Access to request context, URL params, and parent data
68
+ *
69
+ * **Component Loading**
70
+ * - Direct component rendering or lazy loading for code splitting
71
+ * - Client-only rendering when browser APIs are needed
72
+ * - Automatic fallback handling during hydration
73
+ *
74
+ * **Performance Optimization**
75
+ * - Static generation for pre-rendered pages at build time
76
+ * - Server-side caching with configurable TTL and providers
77
+ * - Code splitting through lazy component loading
78
+ *
79
+ * **Error Handling**
80
+ * - Custom error handlers with support for redirects
81
+ * - Hierarchical error handling (child → parent)
82
+ * - HTTP status code handling (404, 401, etc.)
83
+ *
84
+ * **Page Animations**
85
+ * - CSS-based enter/exit animations
86
+ * - Dynamic animations based on page state
87
+ * - Custom timing and easing functions
88
+ *
89
+ * **Lifecycle Management**
90
+ * - Server response hooks for headers and status codes
91
+ * - Page leave handlers for cleanup (browser only)
92
+ * - Permission-based access control
93
+ *
94
+ * @example Simple page with data fetching
95
+ * ```typescript
96
+ * const userProfile = $page({
97
+ * path: "/users/:id",
98
+ * schema: {
99
+ * params: t.object({ id: t.int() }),
100
+ * query: t.object({ tab: t.optional(t.text()) })
101
+ * },
102
+ * resolve: async ({ params }) => {
103
+ * const user = await userApi.getUser(params.id);
104
+ * return { user };
105
+ * },
106
+ * lazy: () => import("./UserProfile.tsx")
107
+ * });
108
+ * ```
109
+ *
110
+ * @example Nested routing with error handling
111
+ * ```typescript
112
+ * const projectSection = $page({
113
+ * path: "/projects/:id",
114
+ * children: () => [projectBoard, projectSettings],
115
+ * resolve: async ({ params }) => {
116
+ * const project = await projectApi.get(params.id);
117
+ * return { project };
118
+ * },
119
+ * errorHandler: (error) => {
120
+ * if (HttpError.is(error, 404)) {
121
+ * return <ProjectNotFound />;
122
+ * }
123
+ * }
124
+ * });
125
+ * ```
126
+ *
127
+ * @example Static generation with caching
128
+ * ```typescript
129
+ * const blogPost = $page({
130
+ * path: "/blog/:slug",
131
+ * static: {
132
+ * entries: posts.map(p => ({ params: { slug: p.slug } }))
133
+ * },
134
+ * resolve: async ({ params }) => {
135
+ * const post = await loadPost(params.slug);
136
+ * return { post };
137
+ * }
138
+ * });
139
+ * ```
140
+ */
141
+ const $page = (options) => {
142
+ return (0, alepha.createDescriptor)(PageDescriptor, options);
143
+ };
144
+ var PageDescriptor = class extends alepha.Descriptor {
145
+ reactPageService = (0, alepha.$inject)(ReactPageService);
146
+ onInit() {
147
+ if (this.options.static) this.options.cache ??= { store: {
148
+ provider: "memory",
149
+ ttl: [1, "week"]
150
+ } };
151
+ }
152
+ get name() {
153
+ return this.options.name ?? this.config.propertyKey;
154
+ }
155
+ /**
156
+ * For testing or build purposes.
157
+ *
158
+ * This will render the page (HTML layout included or not) and return the HTML + context.
159
+ * Only valid for server-side rendering, it will throw an error if called on the client-side.
160
+ */
161
+ async render(options) {
162
+ return this.reactPageService.render(this.name, options);
163
+ }
164
+ async fetch(options) {
165
+ return this.reactPageService.fetch(this.options.path || "", options);
166
+ }
167
+ match(url) {
168
+ return false;
169
+ }
170
+ pathname(config) {
171
+ return this.options.path || "";
172
+ }
173
+ };
174
+ $page[alepha.KIND] = PageDescriptor;
175
+
176
+ //#endregion
177
+ //#region src/core/components/ClientOnly.tsx
178
+ /**
179
+ * A small utility component that renders its children only on the client side.
180
+ *
181
+ * Optionally, you can provide a fallback React node that will be rendered.
182
+ *
183
+ * You should use this component when
184
+ * - you have code that relies on browser-specific APIs
185
+ * - you want to avoid server-side rendering for a specific part of your application
186
+ * - you want to prevent pre-rendering of a component
187
+ */
188
+ const ClientOnly = (props$1) => {
189
+ const [mounted, setMounted] = (0, react.useState)(false);
190
+ (0, react.useEffect)(() => setMounted(true), []);
191
+ if (props$1.disabled) return props$1.children;
192
+ return mounted ? props$1.children : props$1.fallback;
193
+ };
194
+ var ClientOnly_default = ClientOnly;
195
+
196
+ //#endregion
197
+ //#region src/core/components/ErrorViewer.tsx
198
+ const ErrorViewer = ({ error, alepha: alepha$1 }) => {
199
+ const [expanded, setExpanded] = (0, react.useState)(false);
200
+ if (alepha$1.isProduction()) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorViewerProduction, {});
201
+ const stackLines = error.stack?.split("\n") ?? [];
202
+ const previewLines = stackLines.slice(0, 5);
203
+ const hiddenLineCount = stackLines.length - previewLines.length;
204
+ const copyToClipboard = (text) => {
205
+ navigator.clipboard.writeText(text).catch((err) => {
206
+ console.error("Clipboard error:", err);
207
+ });
208
+ };
209
+ const styles = {
210
+ container: {
211
+ padding: "24px",
212
+ backgroundColor: "#FEF2F2",
213
+ color: "#7F1D1D",
214
+ border: "1px solid #FECACA",
215
+ borderRadius: "16px",
216
+ boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
217
+ fontFamily: "monospace",
218
+ maxWidth: "768px",
219
+ margin: "40px auto"
220
+ },
221
+ heading: {
222
+ fontSize: "20px",
223
+ fontWeight: "bold",
224
+ marginBottom: "10px"
225
+ },
226
+ name: {
227
+ fontSize: "16px",
228
+ fontWeight: 600
229
+ },
230
+ message: {
231
+ fontSize: "14px",
232
+ marginBottom: "16px"
233
+ },
234
+ sectionHeader: {
235
+ display: "flex",
236
+ justifyContent: "space-between",
237
+ alignItems: "center",
238
+ fontSize: "12px",
239
+ marginBottom: "4px",
240
+ color: "#991B1B"
241
+ },
242
+ copyButton: {
243
+ fontSize: "12px",
244
+ color: "#DC2626",
245
+ background: "none",
246
+ border: "none",
247
+ cursor: "pointer",
248
+ textDecoration: "underline"
249
+ },
250
+ stackContainer: {
251
+ backgroundColor: "#FEE2E2",
252
+ padding: "12px",
253
+ borderRadius: "8px",
254
+ fontSize: "13px",
255
+ lineHeight: "1.4",
256
+ overflowX: "auto",
257
+ whiteSpace: "pre-wrap"
258
+ },
259
+ expandLine: {
260
+ color: "#F87171",
261
+ cursor: "pointer",
262
+ marginTop: "8px"
263
+ }
264
+ };
265
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
266
+ style: styles.container,
267
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
268
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
269
+ style: styles.heading,
270
+ children: "🔥 Error"
271
+ }),
272
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
273
+ style: styles.name,
274
+ children: error.name
275
+ }),
276
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
277
+ style: styles.message,
278
+ children: error.message
279
+ })
280
+ ] }), stackLines.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
281
+ style: styles.sectionHeader,
282
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Stack trace" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
283
+ type: "button",
284
+ onClick: () => copyToClipboard(error.stack),
285
+ style: styles.copyButton,
286
+ children: "Copy all"
287
+ })]
288
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("pre", {
289
+ style: styles.stackContainer,
290
+ children: [(expanded ? stackLines : previewLines).map((line, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: line }, i)), !expanded && hiddenLineCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
291
+ style: styles.expandLine,
292
+ onClick: () => setExpanded(true),
293
+ children: [
294
+ "+ ",
295
+ hiddenLineCount,
296
+ " more lines..."
297
+ ]
298
+ })]
299
+ })] })]
300
+ });
301
+ };
302
+ var ErrorViewer_default = ErrorViewer;
303
+ const ErrorViewerProduction = () => {
304
+ const styles = {
305
+ container: {
306
+ padding: "24px",
307
+ backgroundColor: "#FEF2F2",
308
+ color: "#7F1D1D",
309
+ border: "1px solid #FECACA",
310
+ borderRadius: "16px",
311
+ boxShadow: "0 8px 24px rgba(0,0,0,0.05)",
312
+ fontFamily: "monospace",
313
+ maxWidth: "768px",
314
+ margin: "40px auto",
315
+ textAlign: "center"
316
+ },
317
+ heading: {
318
+ fontSize: "20px",
319
+ fontWeight: "bold",
320
+ marginBottom: "8px"
321
+ },
322
+ name: {
323
+ fontSize: "16px",
324
+ fontWeight: 600,
325
+ marginBottom: "4px"
326
+ },
327
+ message: {
328
+ fontSize: "14px",
329
+ opacity: .85
330
+ }
331
+ };
332
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
333
+ style: styles.container,
334
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
335
+ style: styles.heading,
336
+ children: "🚨 An error occurred"
337
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
338
+ style: styles.message,
339
+ children: "Something went wrong. Please try again later."
340
+ })]
341
+ });
342
+ };
343
+
344
+ //#endregion
345
+ //#region src/core/contexts/RouterLayerContext.ts
346
+ const RouterLayerContext = (0, react.createContext)(void 0);
347
+
348
+ //#endregion
349
+ //#region src/core/errors/Redirection.ts
350
+ /**
351
+ * Used for Redirection during the page loading.
352
+ *
353
+ * Depends on the context, it can be thrown or just returned.
354
+ */
355
+ var Redirection = class extends Error {
356
+ redirect;
357
+ constructor(redirect) {
358
+ super("Redirection");
359
+ this.redirect = redirect;
360
+ }
361
+ };
362
+
363
+ //#endregion
364
+ //#region src/core/contexts/AlephaContext.ts
365
+ const AlephaContext = (0, react.createContext)(void 0);
366
+
367
+ //#endregion
368
+ //#region src/core/hooks/useAlepha.ts
369
+ /**
370
+ * Main Alepha hook.
371
+ *
372
+ * It provides access to the Alepha instance within a React component.
373
+ *
374
+ * With Alepha, you can access the core functionalities of the framework:
375
+ *
376
+ * - alepha.state() for state management
377
+ * - alepha.inject() for dependency injection
378
+ * - alepha.events.emit() for event handling
379
+ * etc...
380
+ */
381
+ const useAlepha = () => {
382
+ const alepha$1 = (0, react.useContext)(AlephaContext);
383
+ if (!alepha$1) throw new alepha.AlephaError("Hook 'useAlepha()' must be used within an AlephaContext.Provider");
384
+ return alepha$1;
385
+ };
386
+
387
+ //#endregion
388
+ //#region src/core/hooks/useEvents.ts
389
+ /**
390
+ * Allow subscribing to multiple Alepha events. See {@link Hooks} for available events.
391
+ *
392
+ * useEvents is fully typed to ensure correct event callback signatures.
393
+ *
394
+ * @example
395
+ * ```tsx
396
+ * useEvents(
397
+ * {
398
+ * "react:transition:begin": (ev) => {
399
+ * console.log("Transition began to:", ev.to);
400
+ * },
401
+ * "react:transition:error": {
402
+ * priority: "first",
403
+ * callback: (ev) => {
404
+ * console.error("Transition error:", ev.error);
405
+ * },
406
+ * },
407
+ * },
408
+ * [],
409
+ * );
410
+ * ```
411
+ */
412
+ const useEvents = (opts, deps) => {
413
+ const alepha$1 = useAlepha();
414
+ (0, react.useEffect)(() => {
415
+ if (!alepha$1.isBrowser()) return;
416
+ const subs = [];
417
+ for (const [name, hook] of Object.entries(opts)) subs.push(alepha$1.events.on(name, hook));
418
+ return () => {
419
+ for (const clear of subs) clear();
420
+ };
421
+ }, deps);
422
+ };
423
+
424
+ //#endregion
425
+ //#region src/core/hooks/useStore.ts
426
+ function useStore(target, defaultValue) {
427
+ const alepha$1 = useAlepha();
428
+ (0, react.useMemo)(() => {
429
+ if (defaultValue != null && alepha$1.state.get(target) == null) alepha$1.state.set(target, defaultValue);
430
+ }, [defaultValue]);
431
+ const [state, setState] = (0, react.useState)(alepha$1.state.get(target));
432
+ (0, react.useEffect)(() => {
433
+ if (!alepha$1.isBrowser()) return;
434
+ const key = target instanceof alepha.Atom ? target.key : target;
435
+ return alepha$1.events.on("state:mutate", (ev) => {
436
+ if (ev.key === key) setState(ev.value);
437
+ });
438
+ }, []);
439
+ return [state, (value) => {
440
+ alepha$1.state.set(target, value);
441
+ }];
442
+ }
443
+
444
+ //#endregion
445
+ //#region src/core/hooks/useRouterState.ts
446
+ const useRouterState = () => {
447
+ const [state] = useStore("alepha.react.router.state");
448
+ if (!state) throw new alepha.AlephaError("Missing react router state");
449
+ return state;
450
+ };
451
+
452
+ //#endregion
453
+ //#region src/core/components/ErrorBoundary.tsx
454
+ /**
455
+ * A reusable error boundary for catching rendering errors
456
+ * in any part of the React component tree.
457
+ */
458
+ var ErrorBoundary = class extends react.default.Component {
459
+ constructor(props$1) {
460
+ super(props$1);
461
+ this.state = {};
462
+ }
463
+ /**
464
+ * Update state so the next render shows the fallback UI.
465
+ */
466
+ static getDerivedStateFromError(error) {
467
+ return { error };
468
+ }
469
+ /**
470
+ * Lifecycle method called when an error is caught.
471
+ * You can log the error or perform side effects here.
472
+ */
473
+ componentDidCatch(error, info) {
474
+ if (this.props.onError) this.props.onError(error, info);
475
+ }
476
+ render() {
477
+ if (this.state.error) return this.props.fallback(this.state.error);
478
+ return this.props.children;
479
+ }
480
+ };
481
+ var ErrorBoundary_default = ErrorBoundary;
482
+
483
+ //#endregion
484
+ //#region src/core/components/NestedView.tsx
485
+ /**
486
+ * A component that renders the current view of the nested router layer.
487
+ *
488
+ * To be simple, it renders the `element` of the current child page of a parent page.
489
+ *
490
+ * @example
491
+ * ```tsx
492
+ * import { NestedView } from "@alepha/react";
493
+ *
494
+ * class App {
495
+ * parent = $page({
496
+ * component: () => <NestedView />,
497
+ * });
498
+ *
499
+ * child = $page({
500
+ * parent: this.root,
501
+ * component: () => <div>Child Page</div>,
502
+ * });
503
+ * }
504
+ * ```
505
+ */
506
+ const NestedView = (props$1) => {
507
+ const index = (0, react.use)(RouterLayerContext)?.index ?? 0;
508
+ const state = useRouterState();
509
+ const [view, setView] = (0, react.useState)(state.layers[index]?.element);
510
+ const [animation, setAnimation] = (0, react.useState)("");
511
+ const animationExitDuration = (0, react.useRef)(0);
512
+ const animationExitNow = (0, react.useRef)(0);
513
+ useEvents({
514
+ "react:transition:begin": async ({ previous, state: state$1 }) => {
515
+ const layer = previous.layers[index];
516
+ if (`${state$1.url.pathname}/`.startsWith(`${layer?.path}/`)) return;
517
+ const animationExit = parseAnimation(layer.route?.animation, state$1, "exit");
518
+ if (animationExit) {
519
+ const duration = animationExit.duration || 200;
520
+ animationExitNow.current = Date.now();
521
+ animationExitDuration.current = duration;
522
+ setAnimation(animationExit.animation);
523
+ } else {
524
+ animationExitNow.current = 0;
525
+ animationExitDuration.current = 0;
526
+ setAnimation("");
527
+ }
528
+ },
529
+ "react:transition:end": async ({ state: state$1 }) => {
530
+ const layer = state$1.layers[index];
531
+ if (animationExitNow.current) {
532
+ const duration = animationExitDuration.current;
533
+ const diff = Date.now() - animationExitNow.current;
534
+ if (diff < duration) await new Promise((resolve) => setTimeout(resolve, duration - diff));
535
+ }
536
+ if (!layer?.cache) {
537
+ setView(layer?.element);
538
+ const animationEnter = parseAnimation(layer?.route?.animation, state$1, "enter");
539
+ if (animationEnter) setAnimation(animationEnter.animation);
540
+ else setAnimation("");
541
+ }
542
+ }
543
+ }, []);
544
+ let element = view ?? props$1.children ?? null;
545
+ if (animation) element = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
546
+ style: {
547
+ display: "flex",
548
+ flex: 1,
549
+ height: "100%",
550
+ width: "100%",
551
+ position: "relative",
552
+ overflow: "hidden"
553
+ },
554
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
555
+ style: {
556
+ height: "100%",
557
+ width: "100%",
558
+ display: "flex",
559
+ animation
560
+ },
561
+ children: element
562
+ })
563
+ });
564
+ if (props$1.errorBoundary === false) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: element });
565
+ if (props$1.errorBoundary) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBoundary_default, {
566
+ fallback: props$1.errorBoundary,
567
+ children: element
568
+ });
569
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBoundary_default, {
570
+ fallback: (error) => {
571
+ const result = state.onError(error, state);
572
+ if (result instanceof Redirection) return "Redirection inside ErrorBoundary is not allowed.";
573
+ return result;
574
+ },
575
+ children: element
576
+ });
577
+ };
578
+ var NestedView_default = (0, react.memo)(NestedView);
579
+ function parseAnimation(animationLike, state, type = "enter") {
580
+ if (!animationLike) return;
581
+ const DEFAULT_DURATION = 300;
582
+ const animation = typeof animationLike === "function" ? animationLike(state) : animationLike;
583
+ if (typeof animation === "string") {
584
+ if (type === "exit") return;
585
+ return {
586
+ duration: DEFAULT_DURATION,
587
+ animation: `${DEFAULT_DURATION}ms ${animation}`
588
+ };
589
+ }
590
+ if (typeof animation === "object") {
591
+ const anim = animation[type];
592
+ const duration = typeof anim === "object" ? anim.duration ?? DEFAULT_DURATION : DEFAULT_DURATION;
593
+ const name = typeof anim === "object" ? anim.name : anim;
594
+ if (type === "exit") return {
595
+ duration,
596
+ animation: `${duration}ms ${typeof anim === "object" ? anim.timing ?? "" : ""} ${name}`
597
+ };
598
+ return {
599
+ duration,
600
+ animation: `${duration}ms ${typeof anim === "object" ? anim.timing ?? "" : ""} ${name}`
601
+ };
602
+ }
603
+ }
604
+
605
+ //#endregion
606
+ //#region src/core/components/NotFound.tsx
607
+ function NotFoundPage(props$1) {
608
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
609
+ style: {
610
+ height: "100vh",
611
+ display: "flex",
612
+ flexDirection: "column",
613
+ justifyContent: "center",
614
+ alignItems: "center",
615
+ textAlign: "center",
616
+ fontFamily: "sans-serif",
617
+ padding: "1rem",
618
+ ...props$1.style
619
+ },
620
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
621
+ style: {
622
+ fontSize: "1rem",
623
+ marginBottom: "0.5rem"
624
+ },
625
+ children: "404 - This page does not exist"
626
+ })
627
+ });
628
+ }
629
+
630
+ //#endregion
631
+ //#region src/core/providers/ReactPageProvider.ts
632
+ const envSchema$2 = alepha.t.object({ REACT_STRICT_MODE: alepha.t.boolean({ default: true }) });
633
+ var ReactPageProvider = class {
634
+ log = (0, alepha_logger.$logger)();
635
+ env = (0, alepha.$env)(envSchema$2);
636
+ alepha = (0, alepha.$inject)(alepha.Alepha);
637
+ pages = [];
638
+ getPages() {
639
+ return this.pages;
640
+ }
641
+ getConcretePages() {
642
+ const pages = [];
643
+ for (const page of this.pages) {
644
+ if (page.children && page.children.length > 0) continue;
645
+ const fullPath = this.pathname(page.name);
646
+ if (fullPath.includes(":") || fullPath.includes("*")) {
647
+ if (typeof page.static === "object") {
648
+ const entries = page.static.entries;
649
+ if (entries && entries.length > 0) for (const entry of entries) {
650
+ const params = entry.params;
651
+ const path = this.compile(page.path ?? "", params);
652
+ if (!path.includes(":") && !path.includes("*")) pages.push({
653
+ ...page,
654
+ name: params[Object.keys(params)[0]],
655
+ path,
656
+ ...entry
657
+ });
658
+ }
659
+ }
660
+ continue;
661
+ }
662
+ pages.push(page);
663
+ }
664
+ return pages;
665
+ }
666
+ page(name) {
667
+ for (const page of this.pages) if (page.name === name) return page;
668
+ throw new alepha.AlephaError(`Page '${name}' not found`);
669
+ }
670
+ pathname(name, options = {}) {
671
+ const page = this.page(name);
672
+ if (!page) throw new Error(`Page ${name} not found`);
673
+ let url = page.path ?? "";
674
+ let parent = page.parent;
675
+ while (parent) {
676
+ url = `${parent.path ?? ""}/${url}`;
677
+ parent = parent.parent;
678
+ }
679
+ url = this.compile(url, options.params ?? {});
680
+ if (options.query) {
681
+ const query = new URLSearchParams(options.query);
682
+ if (query.toString()) url += `?${query.toString()}`;
683
+ }
684
+ return url.replace(/\/\/+/g, "/") || "/";
685
+ }
686
+ url(name, options = {}) {
687
+ return new URL(this.pathname(name, options), options.host ?? `http://localhost`);
688
+ }
689
+ root(state) {
690
+ const root = (0, react.createElement)(AlephaContext.Provider, { value: this.alepha }, (0, react.createElement)(NestedView_default, {}, state.layers[0]?.element));
691
+ if (this.env.REACT_STRICT_MODE) return (0, react.createElement)(react.StrictMode, {}, root);
692
+ return root;
693
+ }
694
+ convertStringObjectToObject = (schema, value) => {
695
+ if (alepha.t.schema.isObject(schema) && typeof value === "object") {
696
+ for (const key in schema.properties) if (alepha.t.schema.isObject(schema.properties[key]) && typeof value[key] === "string") try {
697
+ value[key] = this.alepha.codec.decode(schema.properties[key], decodeURIComponent(value[key]));
698
+ } catch (e$1) {}
699
+ }
700
+ return value;
701
+ };
702
+ /**
703
+ * Create a new RouterState based on a given route and request.
704
+ * This method resolves the layers for the route, applying any query and params schemas defined in the route.
705
+ * It also handles errors and redirects.
706
+ */
707
+ async createLayers(route, state, previous = []) {
708
+ let context = {};
709
+ const stack = [{ route }];
710
+ let parent = route.parent;
711
+ while (parent) {
712
+ stack.unshift({ route: parent });
713
+ parent = parent.parent;
714
+ }
715
+ let forceRefresh = false;
716
+ for (let i = 0; i < stack.length; i++) {
717
+ const it = stack[i];
718
+ const route$1 = it.route;
719
+ const config = {};
720
+ try {
721
+ this.convertStringObjectToObject(route$1.schema?.query, state.query);
722
+ config.query = route$1.schema?.query ? this.alepha.codec.decode(route$1.schema.query, state.query) : {};
723
+ } catch (e$1) {
724
+ it.error = e$1;
725
+ break;
726
+ }
727
+ try {
728
+ config.params = route$1.schema?.params ? this.alepha.codec.decode(route$1.schema.params, state.params) : {};
729
+ } catch (e$1) {
730
+ it.error = e$1;
731
+ break;
732
+ }
733
+ it.config = { ...config };
734
+ if (previous?.[i] && !forceRefresh && previous[i].name === route$1.name) {
735
+ const url = (str) => str ? str.replace(/\/\/+/g, "/") : "/";
736
+ if (JSON.stringify({
737
+ part: url(previous[i].part),
738
+ params: previous[i].config?.params ?? {}
739
+ }) === JSON.stringify({
740
+ part: url(route$1.path),
741
+ params: config.params ?? {}
742
+ })) {
743
+ it.props = previous[i].props;
744
+ it.error = previous[i].error;
745
+ it.cache = true;
746
+ context = {
747
+ ...context,
748
+ ...it.props
749
+ };
750
+ continue;
751
+ }
752
+ forceRefresh = true;
753
+ }
754
+ if (!route$1.resolve) continue;
755
+ try {
756
+ const args = Object.create(state);
757
+ Object.assign(args, config, context);
758
+ const props$1 = await route$1.resolve?.(args) ?? {};
759
+ it.props = { ...props$1 };
760
+ context = {
761
+ ...context,
762
+ ...props$1
763
+ };
764
+ } catch (e$1) {
765
+ if (e$1 instanceof Redirection) return { redirect: e$1.redirect };
766
+ this.log.error("Page resolver has failed", e$1);
767
+ it.error = e$1;
768
+ break;
769
+ }
770
+ }
771
+ let acc = "";
772
+ for (let i = 0; i < stack.length; i++) {
773
+ const it = stack[i];
774
+ const props$1 = it.props ?? {};
775
+ const params = { ...it.config?.params };
776
+ for (const key of Object.keys(params)) params[key] = String(params[key]);
777
+ acc += "/";
778
+ acc += it.route.path ? this.compile(it.route.path, params) : "";
779
+ const path = acc.replace(/\/+/, "/");
780
+ const localErrorHandler = this.getErrorHandler(it.route);
781
+ if (localErrorHandler) {
782
+ const onErrorParent = state.onError;
783
+ state.onError = (error, context$1) => {
784
+ const result = localErrorHandler(error, context$1);
785
+ if (result === void 0) return onErrorParent(error, context$1);
786
+ return result;
787
+ };
788
+ }
789
+ if (!it.error) try {
790
+ const element = await this.createElement(it.route, {
791
+ ...props$1,
792
+ ...context
793
+ });
794
+ state.layers.push({
795
+ name: it.route.name,
796
+ props: props$1,
797
+ part: it.route.path,
798
+ config: it.config,
799
+ element: this.renderView(i + 1, path, element, it.route),
800
+ index: i + 1,
801
+ path,
802
+ route: it.route,
803
+ cache: it.cache
804
+ });
805
+ } catch (e$1) {
806
+ it.error = e$1;
807
+ }
808
+ if (it.error) try {
809
+ let element = await state.onError(it.error, state);
810
+ if (element === void 0) throw it.error;
811
+ if (element instanceof Redirection) return { redirect: element.redirect };
812
+ if (element === null) element = this.renderError(it.error);
813
+ state.layers.push({
814
+ props: props$1,
815
+ error: it.error,
816
+ name: it.route.name,
817
+ part: it.route.path,
818
+ config: it.config,
819
+ element: this.renderView(i + 1, path, element, it.route),
820
+ index: i + 1,
821
+ path,
822
+ route: it.route
823
+ });
824
+ break;
825
+ } catch (e$1) {
826
+ if (e$1 instanceof Redirection) return { redirect: e$1.redirect };
827
+ throw e$1;
828
+ }
829
+ }
830
+ return { state };
831
+ }
832
+ createRedirectionLayer(redirect) {
833
+ return { redirect };
834
+ }
835
+ getErrorHandler(route) {
836
+ if (route.errorHandler) return route.errorHandler;
837
+ let parent = route.parent;
838
+ while (parent) {
839
+ if (parent.errorHandler) return parent.errorHandler;
840
+ parent = parent.parent;
841
+ }
842
+ }
843
+ async createElement(page, props$1) {
844
+ if (page.lazy && page.component) this.log.warn(`Page ${page.name} has both lazy and component options, lazy will be used`);
845
+ if (page.lazy) return (0, react.createElement)((await page.lazy()).default, props$1);
846
+ if (page.component) return (0, react.createElement)(page.component, props$1);
847
+ }
848
+ renderError(error) {
849
+ return (0, react.createElement)(ErrorViewer_default, {
850
+ error,
851
+ alepha: this.alepha
852
+ });
853
+ }
854
+ renderEmptyView() {
855
+ return (0, react.createElement)(NestedView_default, {});
856
+ }
857
+ href(page, params = {}) {
858
+ const found = this.pages.find((it) => it.name === page.options.name);
859
+ if (!found) throw new Error(`Page ${page.options.name} not found`);
860
+ let url = found.path ?? "";
861
+ let parent = found.parent;
862
+ while (parent) {
863
+ url = `${parent.path ?? ""}/${url}`;
864
+ parent = parent.parent;
865
+ }
866
+ url = this.compile(url, params);
867
+ return url.replace(/\/\/+/g, "/") || "/";
868
+ }
869
+ compile(path, params = {}) {
870
+ for (const [key, value] of Object.entries(params)) path = path.replace(`:${key}`, value);
871
+ return path;
872
+ }
873
+ renderView(index, path, view, page) {
874
+ view ??= this.renderEmptyView();
875
+ const element = page.client ? (0, react.createElement)(ClientOnly_default, typeof page.client === "object" ? page.client : {}, view) : view;
876
+ return (0, react.createElement)(RouterLayerContext.Provider, { value: {
877
+ index,
878
+ path
879
+ } }, element);
880
+ }
881
+ configure = (0, alepha.$hook)({
882
+ on: "configure",
883
+ handler: () => {
884
+ let hasNotFoundHandler = false;
885
+ const pages = this.alepha.descriptors($page);
886
+ const hasParent = (it) => {
887
+ if (it.options.parent) return true;
888
+ for (const page of pages) if ((page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : []).includes(it)) return true;
889
+ };
890
+ for (const page of pages) {
891
+ if (page.options.path === "/*") hasNotFoundHandler = true;
892
+ if (hasParent(page)) continue;
893
+ this.add(this.map(pages, page));
894
+ }
895
+ if (!hasNotFoundHandler && pages.length > 0) this.add({
896
+ path: "/*",
897
+ name: "notFound",
898
+ cache: true,
899
+ component: NotFoundPage,
900
+ onServerResponse: ({ reply }) => {
901
+ reply.status = 404;
902
+ }
903
+ });
904
+ }
905
+ });
906
+ map(pages, target) {
907
+ const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
908
+ const getChildrenFromParent = (it) => {
909
+ const children$1 = [];
910
+ for (const page of pages) if (page.options.parent === it) children$1.push(page);
911
+ return children$1;
912
+ };
913
+ children.push(...getChildrenFromParent(target));
914
+ return {
915
+ ...target.options,
916
+ name: target.name,
917
+ parent: void 0,
918
+ children: children.map((it) => this.map(pages, it))
919
+ };
920
+ }
921
+ add(entry) {
922
+ if (this.alepha.isReady()) throw new alepha.AlephaError("Router is already initialized");
923
+ entry.name ??= this.nextId();
924
+ const page = entry;
925
+ page.match = this.createMatch(page);
926
+ this.pages.push(page);
927
+ if (page.children) for (const child of page.children) {
928
+ child.parent = page;
929
+ this.add(child);
930
+ }
931
+ }
932
+ createMatch(page) {
933
+ let url = page.path ?? "/";
934
+ let target = page.parent;
935
+ while (target) {
936
+ url = `${target.path ?? ""}/${url}`;
937
+ target = target.parent;
938
+ }
939
+ let path = url.replace(/\/\/+/g, "/");
940
+ if (path.endsWith("/") && path !== "/") path = path.slice(0, -1);
941
+ return path;
942
+ }
943
+ _next = 0;
944
+ nextId() {
945
+ this._next += 1;
946
+ return `P${this._next}`;
947
+ }
948
+ };
949
+ const isPageRoute = (it) => {
950
+ return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
951
+ };
952
+
953
+ //#endregion
954
+ //#region src/core/providers/ReactServerProvider.ts
955
+ const envSchema$1 = alepha.t.object({
956
+ REACT_SSR_ENABLED: alepha.t.optional(alepha.t.boolean()),
957
+ REACT_ROOT_ID: alepha.t.text({ default: "root" }),
958
+ REACT_SERVER_TEMPLATE: alepha.t.optional(alepha.t.text({ size: "rich" }))
959
+ });
960
+ /**
961
+ * React server provider configuration atom
962
+ */
963
+ const reactServerOptions = (0, alepha.$atom)({
964
+ name: "alepha.react.server.options",
965
+ schema: alepha.t.object({
966
+ publicDir: alepha.t.string(),
967
+ staticServer: alepha.t.object({
968
+ disabled: alepha.t.boolean(),
969
+ path: alepha.t.string({ description: "URL path where static files will be served." })
970
+ })
971
+ }),
972
+ default: {
973
+ publicDir: "public",
974
+ staticServer: {
975
+ disabled: false,
976
+ path: "/"
977
+ }
978
+ }
979
+ });
980
+ var ReactServerProvider = class {
981
+ log = (0, alepha_logger.$logger)();
982
+ alepha = (0, alepha.$inject)(alepha.Alepha);
983
+ env = (0, alepha.$env)(envSchema$1);
984
+ pageApi = (0, alepha.$inject)(ReactPageProvider);
985
+ serverProvider = (0, alepha.$inject)(alepha_server.ServerProvider);
986
+ serverStaticProvider = (0, alepha.$inject)(alepha_server_static.ServerStaticProvider);
987
+ serverRouterProvider = (0, alepha.$inject)(alepha_server.ServerRouterProvider);
988
+ serverTimingProvider = (0, alepha.$inject)(alepha_server.ServerTimingProvider);
989
+ ROOT_DIV_REGEX = new RegExp(`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`, "is");
990
+ preprocessedTemplate = null;
991
+ options = (0, alepha.$use)(reactServerOptions);
992
+ /**
993
+ * Configure the React server provider.
994
+ */
995
+ onConfigure = (0, alepha.$hook)({
996
+ on: "configure",
997
+ handler: async () => {
998
+ const ssrEnabled = this.alepha.descriptors($page).length > 0 && this.env.REACT_SSR_ENABLED !== false;
999
+ this.alepha.state.set("alepha.react.server.ssr", ssrEnabled);
1000
+ if (this.alepha.isViteDev()) {
1001
+ await this.configureVite(ssrEnabled);
1002
+ return;
1003
+ }
1004
+ let root = "";
1005
+ if (!this.alepha.isServerless()) {
1006
+ root = this.getPublicDirectory();
1007
+ if (!root) this.log.warn("Missing static files, static file server will be disabled");
1008
+ else {
1009
+ this.log.debug(`Using static files from: ${root}`);
1010
+ await this.configureStaticServer(root);
1011
+ }
1012
+ }
1013
+ if (ssrEnabled) {
1014
+ await this.registerPages(async () => this.template);
1015
+ this.log.info("SSR OK");
1016
+ return;
1017
+ }
1018
+ this.log.info("SSR is disabled, use History API fallback");
1019
+ this.serverRouterProvider.createRoute({
1020
+ path: "*",
1021
+ handler: async ({ url, reply }) => {
1022
+ if (url.pathname.includes(".")) {
1023
+ reply.headers["content-type"] = "text/plain";
1024
+ reply.body = "Not Found";
1025
+ reply.status = 404;
1026
+ return;
1027
+ }
1028
+ reply.headers["content-type"] = "text/html";
1029
+ return this.template;
1030
+ }
1031
+ });
1032
+ }
1033
+ });
1034
+ get template() {
1035
+ return this.alepha.env.REACT_SERVER_TEMPLATE ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
1036
+ }
1037
+ async registerPages(templateLoader) {
1038
+ const template = await templateLoader();
1039
+ if (template) this.preprocessedTemplate = this.preprocessTemplate(template);
1040
+ for (const page of this.pageApi.getPages()) {
1041
+ if (page.children?.length) continue;
1042
+ this.log.debug(`+ ${page.match} -> ${page.name}`);
1043
+ this.serverRouterProvider.createRoute({
1044
+ ...page,
1045
+ schema: void 0,
1046
+ method: "GET",
1047
+ path: page.match,
1048
+ handler: this.createHandler(page, templateLoader)
1049
+ });
1050
+ }
1051
+ }
1052
+ /**
1053
+ * Get the public directory path where static files are located.
1054
+ */
1055
+ getPublicDirectory() {
1056
+ const maybe = [(0, node_path.join)(process.cwd(), `dist/${this.options.publicDir}`), (0, node_path.join)(process.cwd(), this.options.publicDir)];
1057
+ for (const it of maybe) if ((0, node_fs.existsSync)(it)) return it;
1058
+ return "";
1059
+ }
1060
+ /**
1061
+ * Configure the static file server to serve files from the given root directory.
1062
+ */
1063
+ async configureStaticServer(root) {
1064
+ await this.serverStaticProvider.createStaticServer({
1065
+ root,
1066
+ cacheControl: {
1067
+ maxAge: 3600,
1068
+ immutable: true
1069
+ },
1070
+ ...this.options.staticServer
1071
+ });
1072
+ }
1073
+ /**
1074
+ * Configure Vite for SSR.
1075
+ */
1076
+ async configureVite(ssrEnabled) {
1077
+ if (!ssrEnabled) return;
1078
+ this.log.info("SSR (dev) OK");
1079
+ const url = `http://${process.env.SERVER_HOST}:${process.env.SERVER_PORT}`;
1080
+ await this.registerPages(() => fetch(`${url}/index.html`).then((it) => it.text()).catch(() => void 0));
1081
+ }
1082
+ /**
1083
+ * For testing purposes, creates a render function that can be used.
1084
+ */
1085
+ async render(name, options = {}) {
1086
+ const page = this.pageApi.page(name);
1087
+ const url = new URL(this.pageApi.url(name, options));
1088
+ const state = {
1089
+ url,
1090
+ params: options.params ?? {},
1091
+ query: options.query ?? {},
1092
+ onError: () => null,
1093
+ layers: [],
1094
+ meta: {}
1095
+ };
1096
+ this.log.trace("Rendering", { url });
1097
+ await this.alepha.events.emit("react:server:render:begin", { state });
1098
+ const { redirect } = await this.pageApi.createLayers(page, state);
1099
+ if (redirect) return {
1100
+ state,
1101
+ html: "",
1102
+ redirect
1103
+ };
1104
+ if (!options.html) {
1105
+ this.alepha.state.set("alepha.react.router.state", state);
1106
+ return {
1107
+ state,
1108
+ html: (0, react_dom_server.renderToString)(this.pageApi.root(state))
1109
+ };
1110
+ }
1111
+ const template = this.template ?? "";
1112
+ const html = this.renderToHtml(template, state, options.hydration);
1113
+ if (html instanceof Redirection) return {
1114
+ state,
1115
+ html: "",
1116
+ redirect
1117
+ };
1118
+ const result = {
1119
+ state,
1120
+ html
1121
+ };
1122
+ await this.alepha.events.emit("react:server:render:end", result);
1123
+ return result;
1124
+ }
1125
+ createHandler(route, templateLoader) {
1126
+ return async (serverRequest) => {
1127
+ const { url, reply, query, params } = serverRequest;
1128
+ const template = await templateLoader();
1129
+ if (!template) throw new alepha.AlephaError("Missing template for SSR rendering");
1130
+ this.log.trace("Rendering page", { name: route.name });
1131
+ const state = {
1132
+ url,
1133
+ params,
1134
+ query,
1135
+ onError: () => null,
1136
+ layers: []
1137
+ };
1138
+ if (this.alepha.has(alepha_server_links.ServerLinksProvider)) this.alepha.state.set("alepha.server.request.apiLinks", await this.alepha.inject(alepha_server_links.ServerLinksProvider).getUserApiLinks({
1139
+ user: serverRequest.user,
1140
+ authorization: serverRequest.headers.authorization
1141
+ }));
1142
+ let target = route;
1143
+ while (target) {
1144
+ if (route.can && !route.can()) {
1145
+ reply.status = 403;
1146
+ reply.headers["content-type"] = "text/plain";
1147
+ return "Forbidden";
1148
+ }
1149
+ target = target.parent;
1150
+ }
1151
+ await this.alepha.events.emit("react:server:render:begin", {
1152
+ request: serverRequest,
1153
+ state
1154
+ });
1155
+ this.serverTimingProvider.beginTiming("createLayers");
1156
+ const { redirect } = await this.pageApi.createLayers(route, state);
1157
+ this.serverTimingProvider.endTiming("createLayers");
1158
+ if (redirect) return reply.redirect(redirect);
1159
+ reply.headers["content-type"] = "text/html";
1160
+ reply.headers["cache-control"] = "no-store, no-cache, must-revalidate, proxy-revalidate";
1161
+ reply.headers.pragma = "no-cache";
1162
+ reply.headers.expires = "0";
1163
+ const html = this.renderToHtml(template, state);
1164
+ if (html instanceof Redirection) {
1165
+ reply.redirect(typeof html.redirect === "string" ? html.redirect : this.pageApi.href(html.redirect));
1166
+ return;
1167
+ }
1168
+ const event = {
1169
+ request: serverRequest,
1170
+ state,
1171
+ html
1172
+ };
1173
+ await this.alepha.events.emit("react:server:render:end", event);
1174
+ route.onServerResponse?.(serverRequest);
1175
+ this.log.trace("Page rendered", { name: route.name });
1176
+ return event.html;
1177
+ };
1178
+ }
1179
+ renderToHtml(template, state, hydration = true) {
1180
+ const element = this.pageApi.root(state);
1181
+ this.alepha.state.set("alepha.react.router.state", state);
1182
+ this.serverTimingProvider.beginTiming("renderToString");
1183
+ let app = "";
1184
+ try {
1185
+ app = (0, react_dom_server.renderToString)(element);
1186
+ } catch (error) {
1187
+ this.log.error("renderToString has failed, fallback to error handler", error);
1188
+ const element$1 = state.onError(error, state);
1189
+ if (element$1 instanceof Redirection) return element$1;
1190
+ app = (0, react_dom_server.renderToString)(element$1);
1191
+ this.log.debug("Error handled successfully with fallback");
1192
+ }
1193
+ this.serverTimingProvider.endTiming("renderToString");
1194
+ const response = { html: template };
1195
+ if (hydration) {
1196
+ const { request, context, ...store } = this.alepha.context.als?.getStore() ?? {};
1197
+ const hydrationData = {
1198
+ ...store,
1199
+ "alepha.react.router.state": void 0,
1200
+ layers: state.layers.map((it) => ({
1201
+ ...it,
1202
+ error: it.error ? {
1203
+ ...it.error,
1204
+ name: it.error.name,
1205
+ message: it.error.message,
1206
+ stack: !this.alepha.isProduction() ? it.error.stack : void 0
1207
+ } : void 0,
1208
+ index: void 0,
1209
+ path: void 0,
1210
+ element: void 0,
1211
+ route: void 0
1212
+ }))
1213
+ };
1214
+ const script = `<script>window.__ssr=${JSON.stringify(hydrationData)}<\/script>`;
1215
+ this.fillTemplate(response, app, script);
1216
+ }
1217
+ return response.html;
1218
+ }
1219
+ preprocessTemplate(template) {
1220
+ const bodyCloseIndex = template.match(/<\/body>/i)?.index ?? template.length;
1221
+ const beforeScript = template.substring(0, bodyCloseIndex);
1222
+ const afterScript = template.substring(bodyCloseIndex);
1223
+ const rootDivMatch = beforeScript.match(this.ROOT_DIV_REGEX);
1224
+ if (rootDivMatch) {
1225
+ const beforeDiv = beforeScript.substring(0, rootDivMatch.index);
1226
+ const afterDivStart = rootDivMatch.index + rootDivMatch[0].length;
1227
+ const afterDiv = beforeScript.substring(afterDivStart);
1228
+ return {
1229
+ beforeApp: `${beforeDiv}<div${rootDivMatch[1]} id="${this.env.REACT_ROOT_ID}"${rootDivMatch[2]}>`,
1230
+ afterApp: `</div>${afterDiv}`,
1231
+ beforeScript: "",
1232
+ afterScript
1233
+ };
1234
+ }
1235
+ const bodyMatch = beforeScript.match(/<body([^>]*)>/i);
1236
+ if (bodyMatch) {
1237
+ const beforeBody = beforeScript.substring(0, bodyMatch.index + bodyMatch[0].length);
1238
+ const afterBody = beforeScript.substring(bodyMatch.index + bodyMatch[0].length);
1239
+ return {
1240
+ beforeApp: `${beforeBody}<div id="${this.env.REACT_ROOT_ID}">`,
1241
+ afterApp: `</div>${afterBody}`,
1242
+ beforeScript: "",
1243
+ afterScript
1244
+ };
1245
+ }
1246
+ return {
1247
+ beforeApp: `<div id="${this.env.REACT_ROOT_ID}">`,
1248
+ afterApp: `</div>`,
1249
+ beforeScript,
1250
+ afterScript
1251
+ };
1252
+ }
1253
+ fillTemplate(response, app, script) {
1254
+ if (!this.preprocessedTemplate) this.preprocessedTemplate = this.preprocessTemplate(response.html);
1255
+ response.html = this.preprocessedTemplate.beforeApp + app + this.preprocessedTemplate.afterApp + script + this.preprocessedTemplate.afterScript;
1256
+ }
1257
+ };
1258
+
1259
+ //#endregion
1260
+ //#region src/core/services/ReactPageServerService.ts
1261
+ var ReactPageServerService = class extends ReactPageService {
1262
+ reactServerProvider = (0, alepha.$inject)(ReactServerProvider);
1263
+ serverProvider = (0, alepha.$inject)(alepha_server.ServerProvider);
1264
+ async render(name, options = {}) {
1265
+ return this.reactServerProvider.render(name, options);
1266
+ }
1267
+ async fetch(pathname, options = {}) {
1268
+ const response = await fetch(`${this.serverProvider.hostname}/${pathname}`);
1269
+ const html = await response.text();
1270
+ if (options?.html) return {
1271
+ html,
1272
+ response
1273
+ };
1274
+ const match = html.match(this.reactServerProvider.ROOT_DIV_REGEX);
1275
+ if (match) return {
1276
+ html: match[3],
1277
+ response
1278
+ };
1279
+ throw new alepha.AlephaError("Invalid HTML response");
1280
+ }
1281
+ };
1282
+
1283
+ //#endregion
1284
+ //#region src/core/providers/ReactBrowserRouterProvider.ts
1285
+ var ReactBrowserRouterProvider = class extends alepha_router.RouterProvider {
1286
+ log = (0, alepha_logger.$logger)();
1287
+ alepha = (0, alepha.$inject)(alepha.Alepha);
1288
+ pageApi = (0, alepha.$inject)(ReactPageProvider);
1289
+ add(entry) {
1290
+ this.pageApi.add(entry);
1291
+ }
1292
+ configure = (0, alepha.$hook)({
1293
+ on: "configure",
1294
+ handler: async () => {
1295
+ for (const page of this.pageApi.getPages()) if (page.component || page.lazy) this.push({
1296
+ path: page.match,
1297
+ page
1298
+ });
1299
+ }
1300
+ });
1301
+ async transition(url, previous = [], meta = {}) {
1302
+ const { pathname, search } = url;
1303
+ const state = {
1304
+ url,
1305
+ query: {},
1306
+ params: {},
1307
+ layers: [],
1308
+ onError: () => null,
1309
+ meta
1310
+ };
1311
+ await this.alepha.events.emit("react:action:begin", { type: "transition" });
1312
+ await this.alepha.events.emit("react:transition:begin", {
1313
+ previous: this.alepha.state.get("alepha.react.router.state"),
1314
+ state
1315
+ });
1316
+ try {
1317
+ const { route, params } = this.match(pathname);
1318
+ const query = {};
1319
+ if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
1320
+ state.query = query;
1321
+ state.params = params ?? {};
1322
+ if (isPageRoute(route)) {
1323
+ const { redirect } = await this.pageApi.createLayers(route.page, state, previous);
1324
+ if (redirect) return redirect;
1325
+ }
1326
+ if (state.layers.length === 0) state.layers.push({
1327
+ name: "not-found",
1328
+ element: (0, react.createElement)(NotFoundPage),
1329
+ index: 0,
1330
+ path: "/"
1331
+ });
1332
+ await this.alepha.events.emit("react:action:success", { type: "transition" });
1333
+ await this.alepha.events.emit("react:transition:success", { state });
1334
+ } catch (e$1) {
1335
+ this.log.error("Transition has failed", e$1);
1336
+ state.layers = [{
1337
+ name: "error",
1338
+ element: this.pageApi.renderError(e$1),
1339
+ index: 0,
1340
+ path: "/"
1341
+ }];
1342
+ await this.alepha.events.emit("react:action:error", {
1343
+ type: "transition",
1344
+ error: e$1
1345
+ });
1346
+ await this.alepha.events.emit("react:transition:error", {
1347
+ error: e$1,
1348
+ state
1349
+ });
1350
+ }
1351
+ if (previous) for (let i = 0; i < previous.length; i++) {
1352
+ const layer = previous[i];
1353
+ if (state.layers[i]?.name !== layer.name) this.pageApi.page(layer.name)?.onLeave?.();
1354
+ }
1355
+ this.alepha.state.set("alepha.react.router.state", state);
1356
+ await this.alepha.events.emit("react:action:end", { type: "transition" });
1357
+ await this.alepha.events.emit("react:transition:end", { state });
1358
+ }
1359
+ root(state) {
1360
+ return this.pageApi.root(state);
1361
+ }
1362
+ };
1363
+
1364
+ //#endregion
1365
+ //#region src/core/providers/ReactBrowserProvider.ts
1366
+ const envSchema = alepha.t.object({ REACT_ROOT_ID: alepha.t.text({ default: "root" }) });
1367
+ /**
1368
+ * React browser renderer configuration atom
1369
+ */
1370
+ const reactBrowserOptions = (0, alepha.$atom)({
1371
+ name: "alepha.react.browser.options",
1372
+ schema: alepha.t.object({ scrollRestoration: alepha.t.enum(["top", "manual"]) }),
1373
+ default: { scrollRestoration: "top" }
1374
+ });
1375
+ var ReactBrowserProvider = class {
1376
+ env = (0, alepha.$env)(envSchema);
1377
+ log = (0, alepha_logger.$logger)();
1378
+ client = (0, alepha.$inject)(alepha_server_links.LinkProvider);
1379
+ alepha = (0, alepha.$inject)(alepha.Alepha);
1380
+ router = (0, alepha.$inject)(ReactBrowserRouterProvider);
1381
+ dateTimeProvider = (0, alepha.$inject)(alepha_datetime.DateTimeProvider);
1382
+ options = (0, alepha.$use)(reactBrowserOptions);
1383
+ getRootElement() {
1384
+ const root = this.document.getElementById(this.env.REACT_ROOT_ID);
1385
+ if (root) return root;
1386
+ const div = this.document.createElement("div");
1387
+ div.id = this.env.REACT_ROOT_ID;
1388
+ this.document.body.prepend(div);
1389
+ return div;
1390
+ }
1391
+ transitioning;
1392
+ get state() {
1393
+ return this.alepha.state.get("alepha.react.router.state");
1394
+ }
1395
+ /**
1396
+ * Accessor for Document DOM API.
1397
+ */
1398
+ get document() {
1399
+ return window.document;
1400
+ }
1401
+ /**
1402
+ * Accessor for History DOM API.
1403
+ */
1404
+ get history() {
1405
+ return window.history;
1406
+ }
1407
+ /**
1408
+ * Accessor for Location DOM API.
1409
+ */
1410
+ get location() {
1411
+ return window.location;
1412
+ }
1413
+ get base() {
1414
+ const base = {}.env?.BASE_URL;
1415
+ if (!base || base === "/") return "";
1416
+ return base;
1417
+ }
1418
+ get url() {
1419
+ const url = this.location.pathname + this.location.search;
1420
+ if (this.base) return url.replace(this.base, "");
1421
+ return url;
1422
+ }
1423
+ pushState(path, replace) {
1424
+ const url = this.base + path;
1425
+ if (replace) this.history.replaceState({}, "", url);
1426
+ else this.history.pushState({}, "", url);
1427
+ }
1428
+ async invalidate(props$1) {
1429
+ const previous = [];
1430
+ this.log.trace("Invalidating layers");
1431
+ if (props$1) {
1432
+ const [key] = Object.keys(props$1);
1433
+ const value = props$1[key];
1434
+ for (const layer of this.state.layers) {
1435
+ if (layer.props?.[key]) {
1436
+ previous.push({
1437
+ ...layer,
1438
+ props: {
1439
+ ...layer.props,
1440
+ [key]: value
1441
+ }
1442
+ });
1443
+ break;
1444
+ }
1445
+ previous.push(layer);
1446
+ }
1447
+ }
1448
+ await this.render({ previous });
1449
+ }
1450
+ async go(url, options = {}) {
1451
+ this.log.trace(`Going to ${url}`, {
1452
+ url,
1453
+ options
1454
+ });
1455
+ await this.render({
1456
+ url,
1457
+ previous: options.force ? [] : this.state.layers,
1458
+ meta: options.meta
1459
+ });
1460
+ if (this.state.url.pathname + this.state.url.search !== url) {
1461
+ this.pushState(this.state.url.pathname + this.state.url.search);
1462
+ return;
1463
+ }
1464
+ this.pushState(url, options.replace);
1465
+ }
1466
+ async render(options = {}) {
1467
+ const previous = options.previous ?? this.state.layers;
1468
+ const url = options.url ?? this.url;
1469
+ const start = this.dateTimeProvider.now();
1470
+ this.transitioning = {
1471
+ to: url,
1472
+ from: this.state?.url.pathname
1473
+ };
1474
+ this.log.debug("Transitioning...", { to: url });
1475
+ const redirect = await this.router.transition(new URL(`http://localhost${url}`), previous, options.meta);
1476
+ if (redirect) {
1477
+ this.log.info("Redirecting to", { redirect });
1478
+ if (redirect.startsWith("http")) window.location.href = redirect;
1479
+ else return await this.render({ url: redirect });
1480
+ }
1481
+ const ms = this.dateTimeProvider.now().diff(start);
1482
+ this.log.info(`Transition OK [${ms}ms]`, this.transitioning);
1483
+ this.transitioning = void 0;
1484
+ }
1485
+ /**
1486
+ * Get embedded layers from the server.
1487
+ */
1488
+ getHydrationState() {
1489
+ try {
1490
+ if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
1491
+ } catch (error) {
1492
+ console.error(error);
1493
+ }
1494
+ }
1495
+ onTransitionEnd = (0, alepha.$hook)({
1496
+ on: "react:transition:end",
1497
+ handler: () => {
1498
+ if (this.options.scrollRestoration === "top" && typeof window !== "undefined" && !this.alepha.isTest()) {
1499
+ this.log.trace("Restoring scroll position to top");
1500
+ window.scrollTo(0, 0);
1501
+ }
1502
+ }
1503
+ });
1504
+ ready = (0, alepha.$hook)({
1505
+ on: "ready",
1506
+ handler: async () => {
1507
+ const hydration = this.getHydrationState();
1508
+ const previous = hydration?.layers ?? [];
1509
+ if (hydration) {
1510
+ for (const [key, value] of Object.entries(hydration)) if (key !== "layers") this.alepha.state.set(key, value);
1511
+ }
1512
+ await this.render({ previous });
1513
+ const element = this.router.root(this.state);
1514
+ await this.alepha.events.emit("react:browser:render", {
1515
+ element,
1516
+ root: this.getRootElement(),
1517
+ hydration,
1518
+ state: this.state
1519
+ });
1520
+ window.addEventListener("popstate", () => {
1521
+ if (this.base + this.state.url.pathname === this.location.pathname) return;
1522
+ this.log.debug("Popstate event triggered - rendering new state", { url: this.location.pathname + this.location.search });
1523
+ this.render();
1524
+ });
1525
+ }
1526
+ });
1527
+ };
1528
+
1529
+ //#endregion
1530
+ //#region src/core/services/ReactRouter.ts
1531
+ var ReactRouter = class {
1532
+ alepha = (0, alepha.$inject)(alepha.Alepha);
1533
+ pageApi = (0, alepha.$inject)(ReactPageProvider);
1534
+ get state() {
1535
+ return this.alepha.state.get("alepha.react.router.state");
1536
+ }
1537
+ get pages() {
1538
+ return this.pageApi.getPages();
1539
+ }
1540
+ get concretePages() {
1541
+ return this.pageApi.getConcretePages();
1542
+ }
1543
+ get browser() {
1544
+ if (this.alepha.isBrowser()) return this.alepha.inject(ReactBrowserProvider);
1545
+ }
1546
+ isActive(href, options = {}) {
1547
+ const current = this.state.url.pathname;
1548
+ let isActive = current === href || current === `${href}/` || `${current}/` === href;
1549
+ if (options.startWith && !isActive) isActive = current.startsWith(href);
1550
+ return isActive;
1551
+ }
1552
+ path(name, config = {}) {
1553
+ return this.pageApi.pathname(name, {
1554
+ params: {
1555
+ ...this.state.params,
1556
+ ...config.params
1557
+ },
1558
+ query: config.query
1559
+ });
1560
+ }
1561
+ /**
1562
+ * Reload the current page.
1563
+ * This is equivalent to calling `go()` with the current pathname and search.
1564
+ */
1565
+ async reload() {
1566
+ if (!this.browser) return;
1567
+ await this.go(this.location.pathname + this.location.search, {
1568
+ replace: true,
1569
+ force: true
1570
+ });
1571
+ }
1572
+ getURL() {
1573
+ if (!this.browser) return this.state.url;
1574
+ return new URL(this.location.href);
1575
+ }
1576
+ get location() {
1577
+ if (!this.browser) throw new Error("Browser is required");
1578
+ return this.browser.location;
1579
+ }
1580
+ get current() {
1581
+ return this.state;
1582
+ }
1583
+ get pathname() {
1584
+ return this.state.url.pathname;
1585
+ }
1586
+ get query() {
1587
+ const query = {};
1588
+ for (const [key, value] of new URLSearchParams(this.state.url.search).entries()) query[key] = String(value);
1589
+ return query;
1590
+ }
1591
+ async back() {
1592
+ this.browser?.history.back();
1593
+ }
1594
+ async forward() {
1595
+ this.browser?.history.forward();
1596
+ }
1597
+ async invalidate(props$1) {
1598
+ await this.browser?.invalidate(props$1);
1599
+ }
1600
+ async go(path, options) {
1601
+ for (const page of this.pages) if (page.name === path) {
1602
+ await this.browser?.go(this.path(path, options), options);
1603
+ return;
1604
+ }
1605
+ await this.browser?.go(path, options);
1606
+ }
1607
+ anchor(path, options = {}) {
1608
+ let href = path;
1609
+ for (const page of this.pages) if (page.name === path) {
1610
+ href = this.path(path, options);
1611
+ break;
1612
+ }
1613
+ return {
1614
+ href: this.base(href),
1615
+ onClick: (ev) => {
1616
+ ev.stopPropagation();
1617
+ ev.preventDefault();
1618
+ this.go(href, options).catch(console.error);
1619
+ }
1620
+ };
1621
+ }
1622
+ base(path) {
1623
+ const base = {}.env?.BASE_URL;
1624
+ if (!base || base === "/") return path;
1625
+ return base + path;
1626
+ }
1627
+ /**
1628
+ * Set query params.
1629
+ *
1630
+ * @param record
1631
+ * @param options
1632
+ */
1633
+ setQueryParams(record, options = {}) {
1634
+ const func = typeof record === "function" ? record : () => record;
1635
+ const search = new URLSearchParams(func(this.query)).toString();
1636
+ const state = search ? `${this.pathname}?${search}` : this.pathname;
1637
+ if (options.push) window.history.pushState({}, "", state);
1638
+ else window.history.replaceState({}, "", state);
1639
+ }
1640
+ };
1641
+
1642
+ //#endregion
1643
+ //#region src/core/index.ts
1644
+ /**
1645
+ * Provides full-stack React development with declarative routing, server-side rendering, and client-side hydration.
1646
+ *
1647
+ * The React module enables building modern React applications using the `$page` descriptor on class properties.
1648
+ * It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
1649
+ * type safety and schema validation for route parameters and data.
1650
+ *
1651
+ * @see {@link $page}
1652
+ * @module alepha.react
1653
+ */
1654
+ const AlephaReact = (0, alepha.$module)({
1655
+ name: "alepha.react",
1656
+ descriptors: [$page],
1657
+ services: [
1658
+ ReactServerProvider,
1659
+ ReactPageProvider,
1660
+ ReactRouter,
1661
+ ReactPageService,
1662
+ ReactPageServerService
1663
+ ],
1664
+ register: (alepha$1) => alepha$1.with(alepha_datetime.AlephaDateTime).with(alepha_server.AlephaServer).with(alepha_server_cache.AlephaServerCache).with(alepha_server_links.AlephaServerLinks).with({
1665
+ provide: ReactPageService,
1666
+ use: ReactPageServerService
1667
+ }).with(ReactServerProvider).with(ReactPageProvider).with(ReactRouter)
1668
+ });
1669
+
1670
+ //#endregion
1671
+ //#region ../../node_modules/oauth4webapi/build/index.js
1672
+ let USER_AGENT$1;
1673
+ if (typeof navigator === "undefined" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) USER_AGENT$1 = `oauth4webapi/v3.8.2`;
1674
+ function looseInstanceOf(input, expected) {
1675
+ if (input == null) return false;
1676
+ try {
1677
+ return input instanceof expected || Object.getPrototypeOf(input)[Symbol.toStringTag] === expected.prototype[Symbol.toStringTag];
1678
+ } catch {
1679
+ return false;
1680
+ }
1681
+ }
1682
+ const ERR_INVALID_ARG_VALUE$1 = "ERR_INVALID_ARG_VALUE";
1683
+ const ERR_INVALID_ARG_TYPE$1 = "ERR_INVALID_ARG_TYPE";
1684
+ function CodedTypeError$1(message, code, cause) {
1685
+ const err = new TypeError(message, { cause });
1686
+ Object.assign(err, { code });
1687
+ return err;
1688
+ }
1689
+ const allowInsecureRequests$1 = Symbol();
1690
+ const clockSkew$1 = Symbol();
1691
+ const clockTolerance$1 = Symbol();
1692
+ const customFetch$1 = Symbol();
1693
+ const modifyAssertion$1 = Symbol();
1694
+ const jweDecrypt = Symbol();
1695
+ const encoder = new TextEncoder();
1696
+ const decoder$1 = new TextDecoder();
1697
+ function buf(input) {
1698
+ if (typeof input === "string") return encoder.encode(input);
1699
+ return decoder$1.decode(input);
1700
+ }
1701
+ let encodeBase64Url;
1702
+ if (Uint8Array.prototype.toBase64) encodeBase64Url = (input) => {
1703
+ if (input instanceof ArrayBuffer) input = new Uint8Array(input);
1704
+ return input.toBase64({
1705
+ alphabet: "base64url",
1706
+ omitPadding: true
1707
+ });
1708
+ };
1709
+ else {
1710
+ const CHUNK_SIZE = 32768;
1711
+ encodeBase64Url = (input) => {
1712
+ if (input instanceof ArrayBuffer) input = new Uint8Array(input);
1713
+ const arr = [];
1714
+ for (let i = 0; i < input.byteLength; i += CHUNK_SIZE) arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE)));
1715
+ return btoa(arr.join("")).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
1716
+ };
1717
+ }
1718
+ let decodeBase64Url;
1719
+ if (Uint8Array.fromBase64) decodeBase64Url = (input) => {
1720
+ try {
1721
+ return Uint8Array.fromBase64(input, { alphabet: "base64url" });
1722
+ } catch (cause) {
1723
+ throw CodedTypeError$1("The input to be decoded is not correctly encoded.", ERR_INVALID_ARG_VALUE$1, cause);
1724
+ }
1725
+ };
1726
+ else decodeBase64Url = (input) => {
1727
+ try {
1728
+ const binary = atob(input.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, ""));
1729
+ const bytes = new Uint8Array(binary.length);
1730
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
1731
+ return bytes;
1732
+ } catch (cause) {
1733
+ throw CodedTypeError$1("The input to be decoded is not correctly encoded.", ERR_INVALID_ARG_VALUE$1, cause);
1734
+ }
1735
+ };
1736
+ function b64u(input) {
1737
+ if (typeof input === "string") return decodeBase64Url(input);
1738
+ return encodeBase64Url(input);
1739
+ }
1740
+ var UnsupportedOperationError = class extends Error {
1741
+ code;
1742
+ constructor(message, options) {
1743
+ super(message, options);
1744
+ this.name = this.constructor.name;
1745
+ this.code = UNSUPPORTED_OPERATION;
1746
+ Error.captureStackTrace?.(this, this.constructor);
1747
+ }
1748
+ };
1749
+ var OperationProcessingError = class extends Error {
1750
+ code;
1751
+ constructor(message, options) {
1752
+ super(message, options);
1753
+ this.name = this.constructor.name;
1754
+ if (options?.code) this.code = options?.code;
1755
+ Error.captureStackTrace?.(this, this.constructor);
1756
+ }
1757
+ };
1758
+ function OPE(message, code, cause) {
1759
+ return new OperationProcessingError(message, {
1760
+ code,
1761
+ cause
1762
+ });
1763
+ }
1764
+ function isJsonObject(input) {
1765
+ if (input === null || typeof input !== "object" || Array.isArray(input)) return false;
1766
+ return true;
1767
+ }
1768
+ function prepareHeaders(input) {
1769
+ if (looseInstanceOf(input, Headers)) input = Object.fromEntries(input.entries());
1770
+ const headers$1 = new Headers(input ?? {});
1771
+ if (USER_AGENT$1 && !headers$1.has("user-agent")) headers$1.set("user-agent", USER_AGENT$1);
1772
+ if (headers$1.has("authorization")) throw CodedTypeError$1("\"options.headers\" must not include the \"authorization\" header name", ERR_INVALID_ARG_VALUE$1);
1773
+ return headers$1;
1774
+ }
1775
+ function signal$1(url, value) {
1776
+ if (value !== void 0) {
1777
+ if (typeof value === "function") value = value(url.href);
1778
+ if (!(value instanceof AbortSignal)) throw CodedTypeError$1("\"options.signal\" must return or be an instance of AbortSignal", ERR_INVALID_ARG_TYPE$1);
1779
+ return value;
1780
+ }
1781
+ }
1782
+ function replaceDoubleSlash(pathname) {
1783
+ if (pathname.includes("//")) return pathname.replace("//", "/");
1784
+ return pathname;
1785
+ }
1786
+ function prependWellKnown(url, wellKnown, allowTerminatingSlash = false) {
1787
+ if (url.pathname === "/") url.pathname = wellKnown;
1788
+ else url.pathname = replaceDoubleSlash(`${wellKnown}/${allowTerminatingSlash ? url.pathname : url.pathname.replace(/(\/)$/, "")}`);
1789
+ return url;
1790
+ }
1791
+ function appendWellKnown(url, wellKnown) {
1792
+ url.pathname = replaceDoubleSlash(`${url.pathname}/${wellKnown}`);
1793
+ return url;
1794
+ }
1795
+ async function performDiscovery$1(input, urlName, transform, options) {
1796
+ if (!(input instanceof URL)) throw CodedTypeError$1(`"${urlName}" must be an instance of URL`, ERR_INVALID_ARG_TYPE$1);
1797
+ checkProtocol(input, options?.[allowInsecureRequests$1] !== true);
1798
+ const url = transform(new URL(input.href));
1799
+ const headers$1 = prepareHeaders(options?.headers);
1800
+ headers$1.set("accept", "application/json");
1801
+ return (options?.[customFetch$1] || fetch)(url.href, {
1802
+ body: void 0,
1803
+ headers: Object.fromEntries(headers$1.entries()),
1804
+ method: "GET",
1805
+ redirect: "manual",
1806
+ signal: signal$1(url, options?.signal)
1807
+ });
1808
+ }
1809
+ async function discoveryRequest(issuerIdentifier, options) {
1810
+ return performDiscovery$1(issuerIdentifier, "issuerIdentifier", (url) => {
1811
+ switch (options?.algorithm) {
1812
+ case void 0:
1813
+ case "oidc":
1814
+ appendWellKnown(url, ".well-known/openid-configuration");
1815
+ break;
1816
+ case "oauth2":
1817
+ prependWellKnown(url, ".well-known/oauth-authorization-server");
1818
+ break;
1819
+ default: throw CodedTypeError$1("\"options.algorithm\" must be \"oidc\" (default), or \"oauth2\"", ERR_INVALID_ARG_VALUE$1);
1820
+ }
1821
+ return url;
1822
+ }, options);
1823
+ }
1824
+ function assertNumber(input, allow0, it, code, cause) {
1825
+ try {
1826
+ if (typeof input !== "number" || !Number.isFinite(input)) throw CodedTypeError$1(`${it} must be a number`, ERR_INVALID_ARG_TYPE$1, cause);
1827
+ if (input > 0) return;
1828
+ if (allow0) {
1829
+ if (input !== 0) throw CodedTypeError$1(`${it} must be a non-negative number`, ERR_INVALID_ARG_VALUE$1, cause);
1830
+ return;
1831
+ }
1832
+ throw CodedTypeError$1(`${it} must be a positive number`, ERR_INVALID_ARG_VALUE$1, cause);
1833
+ } catch (err) {
1834
+ if (code) throw OPE(err.message, code, cause);
1835
+ throw err;
1836
+ }
1837
+ }
1838
+ function assertString$1(input, it, code, cause) {
1839
+ try {
1840
+ if (typeof input !== "string") throw CodedTypeError$1(`${it} must be a string`, ERR_INVALID_ARG_TYPE$1, cause);
1841
+ if (input.length === 0) throw CodedTypeError$1(`${it} must not be empty`, ERR_INVALID_ARG_VALUE$1, cause);
1842
+ } catch (err) {
1843
+ if (code) throw OPE(err.message, code, cause);
1844
+ throw err;
1845
+ }
1846
+ }
1847
+ async function processDiscoveryResponse(expectedIssuerIdentifier, response) {
1848
+ const expected = expectedIssuerIdentifier;
1849
+ if (!(expected instanceof URL) && expected !== _nodiscoverycheck) throw CodedTypeError$1("\"expectedIssuerIdentifier\" must be an instance of URL", ERR_INVALID_ARG_TYPE$1);
1850
+ if (!looseInstanceOf(response, Response)) throw CodedTypeError$1("\"response\" must be an instance of Response", ERR_INVALID_ARG_TYPE$1);
1851
+ if (response.status !== 200) throw OPE("\"response\" is not a conform Authorization Server Metadata response (unexpected HTTP status code)", RESPONSE_IS_NOT_CONFORM, response);
1852
+ assertReadableResponse(response);
1853
+ const json = await getResponseJsonBody(response);
1854
+ assertString$1(json.issuer, "\"response\" body \"issuer\" property", INVALID_RESPONSE, { body: json });
1855
+ if (expected !== _nodiscoverycheck && new URL(json.issuer).href !== expected.href) throw OPE("\"response\" body \"issuer\" property does not match the expected value", JSON_ATTRIBUTE_COMPARISON, {
1856
+ expected: expected.href,
1857
+ body: json,
1858
+ attribute: "issuer"
1859
+ });
1860
+ return json;
1861
+ }
1862
+ function assertApplicationJson(response) {
1863
+ assertContentType(response, "application/json");
1864
+ }
1865
+ function notJson(response, ...types) {
1866
+ let msg = "\"response\" content-type must be ";
1867
+ if (types.length > 2) {
1868
+ const last = types.pop();
1869
+ msg += `${types.join(", ")}, or ${last}`;
1870
+ } else if (types.length === 2) msg += `${types[0]} or ${types[1]}`;
1871
+ else msg += types[0];
1872
+ return OPE(msg, RESPONSE_IS_NOT_JSON, response);
1873
+ }
1874
+ function assertContentType(response, contentType) {
1875
+ if (getContentType(response) !== contentType) throw notJson(response, contentType);
1876
+ }
1877
+ function randomBytes() {
1878
+ return b64u(crypto.getRandomValues(new Uint8Array(32)));
1879
+ }
1880
+ function generateRandomCodeVerifier() {
1881
+ return randomBytes();
1882
+ }
1883
+ function generateRandomState() {
1884
+ return randomBytes();
1885
+ }
1886
+ async function calculatePKCECodeChallenge$1(codeVerifier) {
1887
+ assertString$1(codeVerifier, "codeVerifier");
1888
+ return b64u(await crypto.subtle.digest("SHA-256", buf(codeVerifier)));
1889
+ }
1890
+ function getClockSkew(client) {
1891
+ const skew = client?.[clockSkew$1];
1892
+ return typeof skew === "number" && Number.isFinite(skew) ? skew : 0;
1893
+ }
1894
+ function getClockTolerance(client) {
1895
+ const tolerance = client?.[clockTolerance$1];
1896
+ return typeof tolerance === "number" && Number.isFinite(tolerance) && Math.sign(tolerance) !== -1 ? tolerance : 30;
1897
+ }
1898
+ function epochTime() {
1899
+ return Math.floor(Date.now() / 1e3);
1900
+ }
1901
+ function assertAs(as) {
1902
+ if (typeof as !== "object" || as === null) throw CodedTypeError$1("\"as\" must be an object", ERR_INVALID_ARG_TYPE$1);
1903
+ assertString$1(as.issuer, "\"as.issuer\"");
1904
+ }
1905
+ function assertClient(client) {
1906
+ if (typeof client !== "object" || client === null) throw CodedTypeError$1("\"client\" must be an object", ERR_INVALID_ARG_TYPE$1);
1907
+ assertString$1(client.client_id, "\"client.client_id\"");
1908
+ }
1909
+ function ClientSecretPost$1(clientSecret) {
1910
+ assertString$1(clientSecret, "\"clientSecret\"");
1911
+ return (_as, client, body, _headers) => {
1912
+ body.set("client_id", client.client_id);
1913
+ body.set("client_secret", clientSecret);
1914
+ };
1915
+ }
1916
+ function None$1() {
1917
+ return (_as, client, body, _headers) => {
1918
+ body.set("client_id", client.client_id);
1919
+ };
1920
+ }
1921
+ const URLParse = URL.parse ? (url, base) => URL.parse(url, base) : (url, base) => {
1922
+ try {
1923
+ return new URL(url, base);
1924
+ } catch {
1925
+ return null;
1926
+ }
1927
+ };
1928
+ function checkProtocol(url, enforceHttps) {
1929
+ if (enforceHttps && url.protocol !== "https:") throw OPE("only requests to HTTPS are allowed", HTTP_REQUEST_FORBIDDEN, url);
1930
+ if (url.protocol !== "https:" && url.protocol !== "http:") throw OPE("only HTTP and HTTPS requests are allowed", REQUEST_PROTOCOL_FORBIDDEN, url);
1931
+ }
1932
+ function validateEndpoint(value, endpoint, useMtlsAlias, enforceHttps) {
1933
+ let url;
1934
+ if (typeof value !== "string" || !(url = URLParse(value))) throw OPE(`authorization server metadata does not contain a valid ${useMtlsAlias ? `"as.mtls_endpoint_aliases.${endpoint}"` : `"as.${endpoint}"`}`, value === void 0 ? MISSING_SERVER_METADATA : INVALID_SERVER_METADATA, { attribute: useMtlsAlias ? `mtls_endpoint_aliases.${endpoint}` : endpoint });
1935
+ checkProtocol(url, enforceHttps);
1936
+ return url;
1937
+ }
1938
+ function resolveEndpoint(as, endpoint, useMtlsAlias, enforceHttps) {
1939
+ if (useMtlsAlias && as.mtls_endpoint_aliases && endpoint in as.mtls_endpoint_aliases) return validateEndpoint(as.mtls_endpoint_aliases[endpoint], endpoint, useMtlsAlias, enforceHttps);
1940
+ return validateEndpoint(as[endpoint], endpoint, useMtlsAlias, enforceHttps);
1941
+ }
1942
+ function isDPoPNonceError(err) {
1943
+ if (err instanceof WWWAuthenticateChallengeError) {
1944
+ const { 0: challenge, length } = err.cause;
1945
+ return length === 1 && challenge.scheme === "dpop" && challenge.parameters.error === "use_dpop_nonce";
1946
+ }
1947
+ if (err instanceof ResponseBodyError) return err.error === "use_dpop_nonce";
1948
+ return false;
1949
+ }
1950
+ var ResponseBodyError = class extends Error {
1951
+ cause;
1952
+ code;
1953
+ error;
1954
+ status;
1955
+ error_description;
1956
+ response;
1957
+ constructor(message, options) {
1958
+ super(message, options);
1959
+ this.name = this.constructor.name;
1960
+ this.code = RESPONSE_BODY_ERROR;
1961
+ this.cause = options.cause;
1962
+ this.error = options.cause.error;
1963
+ this.status = options.response.status;
1964
+ this.error_description = options.cause.error_description;
1965
+ Object.defineProperty(this, "response", {
1966
+ enumerable: false,
1967
+ value: options.response
1968
+ });
1969
+ Error.captureStackTrace?.(this, this.constructor);
1970
+ }
1971
+ };
1972
+ var AuthorizationResponseError = class extends Error {
1973
+ cause;
1974
+ code;
1975
+ error;
1976
+ error_description;
1977
+ constructor(message, options) {
1978
+ super(message, options);
1979
+ this.name = this.constructor.name;
1980
+ this.code = AUTHORIZATION_RESPONSE_ERROR;
1981
+ this.cause = options.cause;
1982
+ this.error = options.cause.get("error");
1983
+ this.error_description = options.cause.get("error_description") ?? void 0;
1984
+ Error.captureStackTrace?.(this, this.constructor);
1985
+ }
1986
+ };
1987
+ var WWWAuthenticateChallengeError = class extends Error {
1988
+ cause;
1989
+ code;
1990
+ response;
1991
+ status;
1992
+ constructor(message, options) {
1993
+ super(message, options);
1994
+ this.name = this.constructor.name;
1995
+ this.code = WWW_AUTHENTICATE_CHALLENGE;
1996
+ this.cause = options.cause;
1997
+ this.status = options.response.status;
1998
+ this.response = options.response;
1999
+ Object.defineProperty(this, "response", { enumerable: false });
2000
+ Error.captureStackTrace?.(this, this.constructor);
2001
+ }
2002
+ };
2003
+ const tokenMatch = "[a-zA-Z0-9!#$%&\\'\\*\\+\\-\\.\\^_`\\|~]+";
2004
+ const token68Match = "[a-zA-Z0-9\\-\\._\\~\\+\\/]+[=]{0,2}";
2005
+ const quotedParamMatcher = "(" + tokenMatch + ")\\s*=\\s*\"((?:[^\"\\\\]|\\\\.)*)\"";
2006
+ const paramMatcher = "(" + tokenMatch + ")\\s*=\\s*([a-zA-Z0-9!#$%&\\'\\*\\+\\-\\.\\^_`\\|~]+)";
2007
+ const schemeRE = /* @__PURE__ */ new RegExp("^[,\\s]*(" + tokenMatch + ")\\s(.*)");
2008
+ const quotedParamRE = /* @__PURE__ */ new RegExp("^[,\\s]*" + quotedParamMatcher + "[,\\s]*(.*)");
2009
+ const unquotedParamRE = /* @__PURE__ */ new RegExp("^[,\\s]*" + paramMatcher + "[,\\s]*(.*)");
2010
+ const token68ParamRE = /* @__PURE__ */ new RegExp("^(" + token68Match + ")(?:$|[,\\s])(.*)");
2011
+ function parseWwwAuthenticateChallenges(response) {
2012
+ if (!looseInstanceOf(response, Response)) throw CodedTypeError$1("\"response\" must be an instance of Response", ERR_INVALID_ARG_TYPE$1);
2013
+ const header = response.headers.get("www-authenticate");
2014
+ if (header === null) return;
2015
+ const challenges = [];
2016
+ let rest = header;
2017
+ while (rest) {
2018
+ let match = rest.match(schemeRE);
2019
+ const scheme = match?.["1"].toLowerCase();
2020
+ rest = match?.["2"];
2021
+ if (!scheme) return;
2022
+ const parameters = {};
2023
+ let token68;
2024
+ while (rest) {
2025
+ let key;
2026
+ let value;
2027
+ if (match = rest.match(quotedParamRE)) {
2028
+ [, key, value, rest] = match;
2029
+ if (value.includes("\\")) try {
2030
+ value = JSON.parse(`"${value}"`);
2031
+ } catch {}
2032
+ parameters[key.toLowerCase()] = value;
2033
+ continue;
2034
+ }
2035
+ if (match = rest.match(unquotedParamRE)) {
2036
+ [, key, value, rest] = match;
2037
+ parameters[key.toLowerCase()] = value;
2038
+ continue;
2039
+ }
2040
+ if (match = rest.match(token68ParamRE)) {
2041
+ if (Object.keys(parameters).length) break;
2042
+ [, token68, rest] = match;
2043
+ break;
2044
+ }
2045
+ return;
2046
+ }
2047
+ const challenge = {
2048
+ scheme,
2049
+ parameters
2050
+ };
2051
+ if (token68) challenge.token68 = token68;
2052
+ challenges.push(challenge);
2053
+ }
2054
+ if (!challenges.length) return;
2055
+ return challenges;
2056
+ }
2057
+ async function parseOAuthResponseErrorBody(response) {
2058
+ if (response.status > 399 && response.status < 500) {
2059
+ assertReadableResponse(response);
2060
+ assertApplicationJson(response);
2061
+ try {
2062
+ const json = await response.clone().json();
2063
+ if (isJsonObject(json) && typeof json.error === "string" && json.error.length) return json;
2064
+ } catch {}
2065
+ }
2066
+ }
2067
+ async function checkOAuthBodyError(response, expected, label) {
2068
+ if (response.status !== expected) {
2069
+ checkAuthenticationChallenges(response);
2070
+ let err;
2071
+ if (err = await parseOAuthResponseErrorBody(response)) {
2072
+ await response.body?.cancel();
2073
+ throw new ResponseBodyError("server responded with an error in the response body", {
2074
+ cause: err,
2075
+ response
2076
+ });
2077
+ }
2078
+ throw OPE(`"response" is not a conform ${label} response (unexpected HTTP status code)`, RESPONSE_IS_NOT_CONFORM, response);
2079
+ }
2080
+ }
2081
+ function assertDPoP(option) {
2082
+ if (!branded.has(option)) throw CodedTypeError$1("\"options.DPoP\" is not a valid DPoPHandle", ERR_INVALID_ARG_VALUE$1);
2083
+ }
2084
+ const skipSubjectCheck$1 = Symbol();
2085
+ function getContentType(input) {
2086
+ return input.headers.get("content-type")?.split(";")[0];
2087
+ }
2088
+ async function authenticatedRequest(as, client, clientAuthentication, url, body, headers$1, options) {
2089
+ await clientAuthentication(as, client, body, headers$1);
2090
+ headers$1.set("content-type", "application/x-www-form-urlencoded;charset=UTF-8");
2091
+ return (options?.[customFetch$1] || fetch)(url.href, {
2092
+ body,
2093
+ headers: Object.fromEntries(headers$1.entries()),
2094
+ method: "POST",
2095
+ redirect: "manual",
2096
+ signal: signal$1(url, options?.signal)
2097
+ });
2098
+ }
2099
+ async function tokenEndpointRequest(as, client, clientAuthentication, grantType, parameters, options) {
2100
+ const url = resolveEndpoint(as, "token_endpoint", client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests$1] !== true);
2101
+ parameters.set("grant_type", grantType);
2102
+ const headers$1 = prepareHeaders(options?.headers);
2103
+ headers$1.set("accept", "application/json");
2104
+ if (options?.DPoP !== void 0) {
2105
+ assertDPoP(options.DPoP);
2106
+ await options.DPoP.addProof(url, headers$1, "POST");
2107
+ }
2108
+ const response = await authenticatedRequest(as, client, clientAuthentication, url, parameters, headers$1, options);
2109
+ options?.DPoP?.cacheNonce(response, url);
2110
+ return response;
2111
+ }
2112
+ async function refreshTokenGrantRequest(as, client, clientAuthentication, refreshToken, options) {
2113
+ assertAs(as);
2114
+ assertClient(client);
2115
+ assertString$1(refreshToken, "\"refreshToken\"");
2116
+ const parameters = new URLSearchParams(options?.additionalParameters);
2117
+ parameters.set("refresh_token", refreshToken);
2118
+ return tokenEndpointRequest(as, client, clientAuthentication, "refresh_token", parameters, options);
2119
+ }
2120
+ const idTokenClaims = /* @__PURE__ */ new WeakMap();
2121
+ const jwtRefs = /* @__PURE__ */ new WeakMap();
2122
+ function getValidatedIdTokenClaims(ref) {
2123
+ if (!ref.id_token) return;
2124
+ const claims = idTokenClaims.get(ref);
2125
+ if (!claims) throw CodedTypeError$1("\"ref\" was already garbage collected or did not resolve from the proper sources", ERR_INVALID_ARG_VALUE$1);
2126
+ return claims;
2127
+ }
2128
+ async function processGenericAccessTokenResponse(as, client, response, additionalRequiredIdTokenClaims, decryptFn, recognizedTokenTypes) {
2129
+ assertAs(as);
2130
+ assertClient(client);
2131
+ if (!looseInstanceOf(response, Response)) throw CodedTypeError$1("\"response\" must be an instance of Response", ERR_INVALID_ARG_TYPE$1);
2132
+ await checkOAuthBodyError(response, 200, "Token Endpoint");
2133
+ assertReadableResponse(response);
2134
+ const json = await getResponseJsonBody(response);
2135
+ assertString$1(json.access_token, "\"response\" body \"access_token\" property", INVALID_RESPONSE, { body: json });
2136
+ assertString$1(json.token_type, "\"response\" body \"token_type\" property", INVALID_RESPONSE, { body: json });
2137
+ json.token_type = json.token_type.toLowerCase();
2138
+ if (json.expires_in !== void 0) {
2139
+ let expiresIn = typeof json.expires_in !== "number" ? parseFloat(json.expires_in) : json.expires_in;
2140
+ assertNumber(expiresIn, true, "\"response\" body \"expires_in\" property", INVALID_RESPONSE, { body: json });
2141
+ json.expires_in = expiresIn;
2142
+ }
2143
+ if (json.refresh_token !== void 0) assertString$1(json.refresh_token, "\"response\" body \"refresh_token\" property", INVALID_RESPONSE, { body: json });
2144
+ if (json.scope !== void 0 && typeof json.scope !== "string") throw OPE("\"response\" body \"scope\" property must be a string", INVALID_RESPONSE, { body: json });
2145
+ if (json.id_token !== void 0) {
2146
+ assertString$1(json.id_token, "\"response\" body \"id_token\" property", INVALID_RESPONSE, { body: json });
2147
+ const requiredClaims = [
2148
+ "aud",
2149
+ "exp",
2150
+ "iat",
2151
+ "iss",
2152
+ "sub"
2153
+ ];
2154
+ if (client.require_auth_time === true) requiredClaims.push("auth_time");
2155
+ if (client.default_max_age !== void 0) {
2156
+ assertNumber(client.default_max_age, true, "\"client.default_max_age\"");
2157
+ requiredClaims.push("auth_time");
2158
+ }
2159
+ if (additionalRequiredIdTokenClaims?.length) requiredClaims.push(...additionalRequiredIdTokenClaims);
2160
+ const { claims, jwt } = await validateJwt(json.id_token, checkSigningAlgorithm.bind(void 0, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported, "RS256"), getClockSkew(client), getClockTolerance(client), decryptFn).then(validatePresence.bind(void 0, requiredClaims)).then(validateIssuer.bind(void 0, as)).then(validateAudience.bind(void 0, client.client_id));
2161
+ if (Array.isArray(claims.aud) && claims.aud.length !== 1) {
2162
+ if (claims.azp === void 0) throw OPE("ID Token \"aud\" (audience) claim includes additional untrusted audiences", JWT_CLAIM_COMPARISON, {
2163
+ claims,
2164
+ claim: "aud"
2165
+ });
2166
+ if (claims.azp !== client.client_id) throw OPE("unexpected ID Token \"azp\" (authorized party) claim value", JWT_CLAIM_COMPARISON, {
2167
+ expected: client.client_id,
2168
+ claims,
2169
+ claim: "azp"
2170
+ });
2171
+ }
2172
+ if (claims.auth_time !== void 0) assertNumber(claims.auth_time, true, "ID Token \"auth_time\" (authentication time)", INVALID_RESPONSE, { claims });
2173
+ jwtRefs.set(response, jwt);
2174
+ idTokenClaims.set(json, claims);
2175
+ }
2176
+ if (recognizedTokenTypes?.[json.token_type] !== void 0) recognizedTokenTypes[json.token_type](response, json);
2177
+ else if (json.token_type !== "dpop" && json.token_type !== "bearer") throw new UnsupportedOperationError("unsupported `token_type` value", { cause: { body: json } });
2178
+ return json;
2179
+ }
2180
+ function checkAuthenticationChallenges(response) {
2181
+ let challenges;
2182
+ if (challenges = parseWwwAuthenticateChallenges(response)) throw new WWWAuthenticateChallengeError("server responded with a challenge in the WWW-Authenticate HTTP Header", {
2183
+ cause: challenges,
2184
+ response
2185
+ });
2186
+ }
2187
+ async function processRefreshTokenResponse(as, client, response, options) {
2188
+ return processGenericAccessTokenResponse(as, client, response, void 0, options?.[jweDecrypt], options?.recognizedTokenTypes);
2189
+ }
2190
+ function validateAudience(expected, result) {
2191
+ if (Array.isArray(result.claims.aud)) {
2192
+ if (!result.claims.aud.includes(expected)) throw OPE("unexpected JWT \"aud\" (audience) claim value", JWT_CLAIM_COMPARISON, {
2193
+ expected,
2194
+ claims: result.claims,
2195
+ claim: "aud"
2196
+ });
2197
+ } else if (result.claims.aud !== expected) throw OPE("unexpected JWT \"aud\" (audience) claim value", JWT_CLAIM_COMPARISON, {
2198
+ expected,
2199
+ claims: result.claims,
2200
+ claim: "aud"
2201
+ });
2202
+ return result;
2203
+ }
2204
+ function validateIssuer(as, result) {
2205
+ const expected = as[_expectedIssuer]?.(result) ?? as.issuer;
2206
+ if (result.claims.iss !== expected) throw OPE("unexpected JWT \"iss\" (issuer) claim value", JWT_CLAIM_COMPARISON, {
2207
+ expected,
2208
+ claims: result.claims,
2209
+ claim: "iss"
2210
+ });
2211
+ return result;
2212
+ }
2213
+ const branded = /* @__PURE__ */ new WeakSet();
2214
+ function brand(searchParams) {
2215
+ branded.add(searchParams);
2216
+ return searchParams;
2217
+ }
2218
+ const nopkce = Symbol();
2219
+ async function authorizationCodeGrantRequest(as, client, clientAuthentication, callbackParameters, redirectUri, codeVerifier, options) {
2220
+ assertAs(as);
2221
+ assertClient(client);
2222
+ if (!branded.has(callbackParameters)) throw CodedTypeError$1("\"callbackParameters\" must be an instance of URLSearchParams obtained from \"validateAuthResponse()\", or \"validateJwtAuthResponse()", ERR_INVALID_ARG_VALUE$1);
2223
+ assertString$1(redirectUri, "\"redirectUri\"");
2224
+ const code = getURLSearchParameter(callbackParameters, "code");
2225
+ if (!code) throw OPE("no authorization code in \"callbackParameters\"", INVALID_RESPONSE);
2226
+ const parameters = new URLSearchParams(options?.additionalParameters);
2227
+ parameters.set("redirect_uri", redirectUri);
2228
+ parameters.set("code", code);
2229
+ if (codeVerifier !== nopkce) {
2230
+ assertString$1(codeVerifier, "\"codeVerifier\"");
2231
+ parameters.set("code_verifier", codeVerifier);
2232
+ }
2233
+ return tokenEndpointRequest(as, client, clientAuthentication, "authorization_code", parameters, options);
2234
+ }
2235
+ const jwtClaimNames = {
2236
+ aud: "audience",
2237
+ c_hash: "code hash",
2238
+ client_id: "client id",
2239
+ exp: "expiration time",
2240
+ iat: "issued at",
2241
+ iss: "issuer",
2242
+ jti: "jwt id",
2243
+ nonce: "nonce",
2244
+ s_hash: "state hash",
2245
+ sub: "subject",
2246
+ ath: "access token hash",
2247
+ htm: "http method",
2248
+ htu: "http uri",
2249
+ cnf: "confirmation",
2250
+ auth_time: "authentication time"
2251
+ };
2252
+ function validatePresence(required, result) {
2253
+ for (const claim of required) if (result.claims[claim] === void 0) throw OPE(`JWT "${claim}" (${jwtClaimNames[claim]}) claim missing`, INVALID_RESPONSE, { claims: result.claims });
2254
+ return result;
2255
+ }
2256
+ const expectNoNonce = Symbol();
2257
+ const skipAuthTimeCheck = Symbol();
2258
+ async function processAuthorizationCodeResponse(as, client, response, options) {
2259
+ if (typeof options?.expectedNonce === "string" || typeof options?.maxAge === "number" || options?.requireIdToken) return processAuthorizationCodeOpenIDResponse(as, client, response, options.expectedNonce, options.maxAge, options[jweDecrypt], options.recognizedTokenTypes);
2260
+ return processAuthorizationCodeOAuth2Response(as, client, response, options?.[jweDecrypt], options?.recognizedTokenTypes);
2261
+ }
2262
+ async function processAuthorizationCodeOpenIDResponse(as, client, response, expectedNonce, maxAge, decryptFn, recognizedTokenTypes) {
2263
+ const additionalRequiredClaims = [];
2264
+ switch (expectedNonce) {
2265
+ case void 0:
2266
+ expectedNonce = expectNoNonce;
2267
+ break;
2268
+ case expectNoNonce: break;
2269
+ default:
2270
+ assertString$1(expectedNonce, "\"expectedNonce\" argument");
2271
+ additionalRequiredClaims.push("nonce");
2272
+ }
2273
+ maxAge ??= client.default_max_age;
2274
+ switch (maxAge) {
2275
+ case void 0:
2276
+ maxAge = skipAuthTimeCheck;
2277
+ break;
2278
+ case skipAuthTimeCheck: break;
2279
+ default:
2280
+ assertNumber(maxAge, true, "\"maxAge\" argument");
2281
+ additionalRequiredClaims.push("auth_time");
2282
+ }
2283
+ const result = await processGenericAccessTokenResponse(as, client, response, additionalRequiredClaims, decryptFn, recognizedTokenTypes);
2284
+ assertString$1(result.id_token, "\"response\" body \"id_token\" property", INVALID_RESPONSE, { body: result });
2285
+ const claims = getValidatedIdTokenClaims(result);
2286
+ if (maxAge !== skipAuthTimeCheck) {
2287
+ const now = epochTime() + getClockSkew(client);
2288
+ const tolerance = getClockTolerance(client);
2289
+ if (claims.auth_time + maxAge < now - tolerance) throw OPE("too much time has elapsed since the last End-User authentication", JWT_TIMESTAMP_CHECK, {
2290
+ claims,
2291
+ now,
2292
+ tolerance,
2293
+ claim: "auth_time"
2294
+ });
2295
+ }
2296
+ if (expectedNonce === expectNoNonce) {
2297
+ if (claims.nonce !== void 0) throw OPE("unexpected ID Token \"nonce\" claim value", JWT_CLAIM_COMPARISON, {
2298
+ expected: void 0,
2299
+ claims,
2300
+ claim: "nonce"
2301
+ });
2302
+ } else if (claims.nonce !== expectedNonce) throw OPE("unexpected ID Token \"nonce\" claim value", JWT_CLAIM_COMPARISON, {
2303
+ expected: expectedNonce,
2304
+ claims,
2305
+ claim: "nonce"
2306
+ });
2307
+ return result;
2308
+ }
2309
+ async function processAuthorizationCodeOAuth2Response(as, client, response, decryptFn, recognizedTokenTypes) {
2310
+ const result = await processGenericAccessTokenResponse(as, client, response, void 0, decryptFn, recognizedTokenTypes);
2311
+ const claims = getValidatedIdTokenClaims(result);
2312
+ if (claims) {
2313
+ if (client.default_max_age !== void 0) {
2314
+ assertNumber(client.default_max_age, true, "\"client.default_max_age\"");
2315
+ const now = epochTime() + getClockSkew(client);
2316
+ const tolerance = getClockTolerance(client);
2317
+ if (claims.auth_time + client.default_max_age < now - tolerance) throw OPE("too much time has elapsed since the last End-User authentication", JWT_TIMESTAMP_CHECK, {
2318
+ claims,
2319
+ now,
2320
+ tolerance,
2321
+ claim: "auth_time"
2322
+ });
2323
+ }
2324
+ if (claims.nonce !== void 0) throw OPE("unexpected ID Token \"nonce\" claim value", JWT_CLAIM_COMPARISON, {
2325
+ expected: void 0,
2326
+ claims,
2327
+ claim: "nonce"
2328
+ });
2329
+ }
2330
+ return result;
2331
+ }
2332
+ const WWW_AUTHENTICATE_CHALLENGE = "OAUTH_WWW_AUTHENTICATE_CHALLENGE";
2333
+ const RESPONSE_BODY_ERROR = "OAUTH_RESPONSE_BODY_ERROR";
2334
+ const UNSUPPORTED_OPERATION = "OAUTH_UNSUPPORTED_OPERATION";
2335
+ const AUTHORIZATION_RESPONSE_ERROR = "OAUTH_AUTHORIZATION_RESPONSE_ERROR";
2336
+ const PARSE_ERROR = "OAUTH_PARSE_ERROR";
2337
+ const INVALID_RESPONSE = "OAUTH_INVALID_RESPONSE";
2338
+ const RESPONSE_IS_NOT_JSON = "OAUTH_RESPONSE_IS_NOT_JSON";
2339
+ const RESPONSE_IS_NOT_CONFORM = "OAUTH_RESPONSE_IS_NOT_CONFORM";
2340
+ const HTTP_REQUEST_FORBIDDEN = "OAUTH_HTTP_REQUEST_FORBIDDEN";
2341
+ const REQUEST_PROTOCOL_FORBIDDEN = "OAUTH_REQUEST_PROTOCOL_FORBIDDEN";
2342
+ const JWT_TIMESTAMP_CHECK = "OAUTH_JWT_TIMESTAMP_CHECK_FAILED";
2343
+ const JWT_CLAIM_COMPARISON = "OAUTH_JWT_CLAIM_COMPARISON_FAILED";
2344
+ const JSON_ATTRIBUTE_COMPARISON = "OAUTH_JSON_ATTRIBUTE_COMPARISON_FAILED";
2345
+ const MISSING_SERVER_METADATA = "OAUTH_MISSING_SERVER_METADATA";
2346
+ const INVALID_SERVER_METADATA = "OAUTH_INVALID_SERVER_METADATA";
2347
+ function assertReadableResponse(response) {
2348
+ if (response.bodyUsed) throw CodedTypeError$1("\"response\" body has been used already", ERR_INVALID_ARG_VALUE$1);
2349
+ }
2350
+ async function validateJwt(jws, checkAlg, clockSkew$2, clockTolerance$2, decryptJwt) {
2351
+ let { 0: protectedHeader, 1: payload, length } = jws.split(".");
2352
+ if (length === 5) if (decryptJwt !== void 0) {
2353
+ jws = await decryptJwt(jws);
2354
+ ({0: protectedHeader, 1: payload, length} = jws.split("."));
2355
+ } else throw new UnsupportedOperationError("JWE decryption is not configured", { cause: jws });
2356
+ if (length !== 3) throw OPE("Invalid JWT", INVALID_RESPONSE, jws);
2357
+ let header;
2358
+ try {
2359
+ header = JSON.parse(buf(b64u(protectedHeader)));
2360
+ } catch (cause) {
2361
+ throw OPE("failed to parse JWT Header body as base64url encoded JSON", PARSE_ERROR, cause);
2362
+ }
2363
+ if (!isJsonObject(header)) throw OPE("JWT Header must be a top level object", INVALID_RESPONSE, jws);
2364
+ checkAlg(header);
2365
+ if (header.crit !== void 0) throw new UnsupportedOperationError("no JWT \"crit\" header parameter extensions are supported", { cause: { header } });
2366
+ let claims;
2367
+ try {
2368
+ claims = JSON.parse(buf(b64u(payload)));
2369
+ } catch (cause) {
2370
+ throw OPE("failed to parse JWT Payload body as base64url encoded JSON", PARSE_ERROR, cause);
2371
+ }
2372
+ if (!isJsonObject(claims)) throw OPE("JWT Payload must be a top level object", INVALID_RESPONSE, jws);
2373
+ const now = epochTime() + clockSkew$2;
2374
+ if (claims.exp !== void 0) {
2375
+ if (typeof claims.exp !== "number") throw OPE("unexpected JWT \"exp\" (expiration time) claim type", INVALID_RESPONSE, { claims });
2376
+ if (claims.exp <= now - clockTolerance$2) throw OPE("unexpected JWT \"exp\" (expiration time) claim value, expiration is past current timestamp", JWT_TIMESTAMP_CHECK, {
2377
+ claims,
2378
+ now,
2379
+ tolerance: clockTolerance$2,
2380
+ claim: "exp"
2381
+ });
2382
+ }
2383
+ if (claims.iat !== void 0) {
2384
+ if (typeof claims.iat !== "number") throw OPE("unexpected JWT \"iat\" (issued at) claim type", INVALID_RESPONSE, { claims });
2385
+ }
2386
+ if (claims.iss !== void 0) {
2387
+ if (typeof claims.iss !== "string") throw OPE("unexpected JWT \"iss\" (issuer) claim type", INVALID_RESPONSE, { claims });
2388
+ }
2389
+ if (claims.nbf !== void 0) {
2390
+ if (typeof claims.nbf !== "number") throw OPE("unexpected JWT \"nbf\" (not before) claim type", INVALID_RESPONSE, { claims });
2391
+ if (claims.nbf > now + clockTolerance$2) throw OPE("unexpected JWT \"nbf\" (not before) claim value", JWT_TIMESTAMP_CHECK, {
2392
+ claims,
2393
+ now,
2394
+ tolerance: clockTolerance$2,
2395
+ claim: "nbf"
2396
+ });
2397
+ }
2398
+ if (claims.aud !== void 0) {
2399
+ if (typeof claims.aud !== "string" && !Array.isArray(claims.aud)) throw OPE("unexpected JWT \"aud\" (audience) claim type", INVALID_RESPONSE, { claims });
2400
+ }
2401
+ return {
2402
+ header,
2403
+ claims,
2404
+ jwt: jws
2405
+ };
2406
+ }
2407
+ async function consumeStream(request) {
2408
+ if (request.bodyUsed) throw CodedTypeError$1("form_post Request instances must contain a readable body", ERR_INVALID_ARG_VALUE$1, { cause: request });
2409
+ return request.text();
2410
+ }
2411
+ async function formPostResponse(request) {
2412
+ if (request.method !== "POST") throw CodedTypeError$1("form_post responses are expected to use the POST method", ERR_INVALID_ARG_VALUE$1, { cause: request });
2413
+ if (getContentType(request) !== "application/x-www-form-urlencoded") throw CodedTypeError$1("form_post responses are expected to use the application/x-www-form-urlencoded content-type", ERR_INVALID_ARG_VALUE$1, { cause: request });
2414
+ return consumeStream(request);
2415
+ }
2416
+ function checkSigningAlgorithm(client, issuer, fallback, header) {
2417
+ if (client !== void 0) {
2418
+ if (typeof client === "string" ? header.alg !== client : !client.includes(header.alg)) throw OPE("unexpected JWT \"alg\" header parameter", INVALID_RESPONSE, {
2419
+ header,
2420
+ expected: client,
2421
+ reason: "client configuration"
2422
+ });
2423
+ return;
2424
+ }
2425
+ if (Array.isArray(issuer)) {
2426
+ if (!issuer.includes(header.alg)) throw OPE("unexpected JWT \"alg\" header parameter", INVALID_RESPONSE, {
2427
+ header,
2428
+ expected: issuer,
2429
+ reason: "authorization server metadata"
2430
+ });
2431
+ return;
2432
+ }
2433
+ if (fallback !== void 0) {
2434
+ if (typeof fallback === "string" ? header.alg !== fallback : typeof fallback === "function" ? !fallback(header.alg) : !fallback.includes(header.alg)) throw OPE("unexpected JWT \"alg\" header parameter", INVALID_RESPONSE, {
2435
+ header,
2436
+ expected: fallback,
2437
+ reason: "default value"
2438
+ });
2439
+ return;
2440
+ }
2441
+ throw OPE("missing client or server configuration to verify used JWT \"alg\" header parameter", void 0, {
2442
+ client,
2443
+ issuer,
2444
+ fallback
2445
+ });
2446
+ }
2447
+ function getURLSearchParameter(parameters, name) {
2448
+ const { 0: value, length } = parameters.getAll(name);
2449
+ if (length > 1) throw OPE(`"${name}" parameter must be provided only once`, INVALID_RESPONSE);
2450
+ return value;
2451
+ }
2452
+ const skipStateCheck$1 = Symbol();
2453
+ const expectNoState = Symbol();
2454
+ function validateAuthResponse(as, client, parameters, expectedState) {
2455
+ assertAs(as);
2456
+ assertClient(client);
2457
+ if (parameters instanceof URL) parameters = parameters.searchParams;
2458
+ if (!(parameters instanceof URLSearchParams)) throw CodedTypeError$1("\"parameters\" must be an instance of URLSearchParams, or URL", ERR_INVALID_ARG_TYPE$1);
2459
+ if (getURLSearchParameter(parameters, "response")) throw OPE("\"parameters\" contains a JARM response, use validateJwtAuthResponse() instead of validateAuthResponse()", INVALID_RESPONSE, { parameters });
2460
+ const iss = getURLSearchParameter(parameters, "iss");
2461
+ const state = getURLSearchParameter(parameters, "state");
2462
+ if (!iss && as.authorization_response_iss_parameter_supported) throw OPE("response parameter \"iss\" (issuer) missing", INVALID_RESPONSE, { parameters });
2463
+ if (iss && iss !== as.issuer) throw OPE("unexpected \"iss\" (issuer) response parameter value", INVALID_RESPONSE, {
2464
+ expected: as.issuer,
2465
+ parameters
2466
+ });
2467
+ switch (expectedState) {
2468
+ case void 0:
2469
+ case expectNoState:
2470
+ if (state !== void 0) throw OPE("unexpected \"state\" response parameter encountered", INVALID_RESPONSE, {
2471
+ expected: void 0,
2472
+ parameters
2473
+ });
2474
+ break;
2475
+ case skipStateCheck$1: break;
2476
+ default:
2477
+ assertString$1(expectedState, "\"expectedState\" argument");
2478
+ if (state !== expectedState) throw OPE(state === void 0 ? "response parameter \"state\" missing" : "unexpected \"state\" response parameter value", INVALID_RESPONSE, {
2479
+ expected: expectedState,
2480
+ parameters
2481
+ });
2482
+ }
2483
+ if (getURLSearchParameter(parameters, "error")) throw new AuthorizationResponseError("authorization response from the server is an error", { cause: parameters });
2484
+ const id_token = getURLSearchParameter(parameters, "id_token");
2485
+ const token = getURLSearchParameter(parameters, "token");
2486
+ if (id_token !== void 0 || token !== void 0) throw new UnsupportedOperationError("implicit and hybrid flows are not supported");
2487
+ return brand(new URLSearchParams(parameters));
2488
+ }
2489
+ async function getResponseJsonBody(response, check = assertApplicationJson) {
2490
+ let json;
2491
+ try {
2492
+ json = await response.json();
2493
+ } catch (cause) {
2494
+ check(response);
2495
+ throw OPE("failed to parse \"response\" body as JSON", PARSE_ERROR, cause);
2496
+ }
2497
+ if (!isJsonObject(json)) throw OPE("\"response\" body must be a top level object", INVALID_RESPONSE, { body: json });
2498
+ return json;
2499
+ }
2500
+ const _nodiscoverycheck = Symbol();
2501
+ const _expectedIssuer = Symbol();
2502
+
2503
+ //#endregion
2504
+ //#region ../../node_modules/openid-client/build/index.js
2505
+ let headers;
2506
+ let USER_AGENT;
2507
+ if (typeof navigator === "undefined" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) {
2508
+ USER_AGENT = `openid-client/v6.8.1`;
2509
+ headers = { "user-agent": USER_AGENT };
2510
+ }
2511
+ const int = (config) => {
2512
+ return props.get(config);
2513
+ };
2514
+ let props;
2515
+ let tbi;
2516
+ function ClientSecretPost(clientSecret) {
2517
+ if (clientSecret !== void 0) return ClientSecretPost$1(clientSecret);
2518
+ tbi ||= /* @__PURE__ */ new WeakMap();
2519
+ return (as, client, body, headers$1) => {
2520
+ let auth;
2521
+ if (!(auth = tbi.get(client))) {
2522
+ assertString(client.client_secret, "\"metadata.client_secret\"");
2523
+ auth = ClientSecretPost$1(client.client_secret);
2524
+ tbi.set(client, auth);
2525
+ }
2526
+ return auth(as, client, body, headers$1);
2527
+ };
2528
+ }
2529
+ function assertString(input, it) {
2530
+ if (typeof input !== "string") throw CodedTypeError(`${it} must be a string`, ERR_INVALID_ARG_TYPE);
2531
+ if (input.length === 0) throw CodedTypeError(`${it} must not be empty`, ERR_INVALID_ARG_VALUE);
2532
+ }
2533
+ function None() {
2534
+ return None$1();
2535
+ }
2536
+ const skipStateCheck = skipStateCheck$1;
2537
+ const skipSubjectCheck = skipSubjectCheck$1;
2538
+ const customFetch = customFetch$1;
2539
+ const modifyAssertion = modifyAssertion$1;
2540
+ const clockSkew = clockSkew$1;
2541
+ const clockTolerance = clockTolerance$1;
2542
+ const ERR_INVALID_ARG_VALUE = "ERR_INVALID_ARG_VALUE";
2543
+ const ERR_INVALID_ARG_TYPE = "ERR_INVALID_ARG_TYPE";
2544
+ function CodedTypeError(message, code, cause) {
2545
+ const err = new TypeError(message, { cause });
2546
+ Object.assign(err, { code });
2547
+ return err;
2548
+ }
2549
+ function calculatePKCECodeChallenge(codeVerifier) {
2550
+ return calculatePKCECodeChallenge$1(codeVerifier);
2551
+ }
2552
+ function randomPKCECodeVerifier() {
2553
+ return generateRandomCodeVerifier();
2554
+ }
2555
+ function randomState() {
2556
+ return generateRandomState();
2557
+ }
2558
+ var ClientError = class extends Error {
2559
+ code;
2560
+ constructor(message, options) {
2561
+ super(message, options);
2562
+ this.name = this.constructor.name;
2563
+ this.code = options?.code;
2564
+ Error.captureStackTrace?.(this, this.constructor);
2565
+ }
2566
+ };
2567
+ const decoder = new TextDecoder();
2568
+ function e(msg, cause, code) {
2569
+ return new ClientError(msg, {
2570
+ cause,
2571
+ code
2572
+ });
2573
+ }
2574
+ function errorHandler(err) {
2575
+ if (err instanceof TypeError || err instanceof ClientError || err instanceof ResponseBodyError || err instanceof AuthorizationResponseError || err instanceof WWWAuthenticateChallengeError) throw err;
2576
+ if (err instanceof OperationProcessingError) switch (err.code) {
2577
+ case HTTP_REQUEST_FORBIDDEN: throw e("only requests to HTTPS are allowed", err, err.code);
2578
+ case REQUEST_PROTOCOL_FORBIDDEN: throw e("only requests to HTTP or HTTPS are allowed", err, err.code);
2579
+ case RESPONSE_IS_NOT_CONFORM: throw e("unexpected HTTP response status code", err.cause, err.code);
2580
+ case RESPONSE_IS_NOT_JSON: throw e("unexpected response content-type", err.cause, err.code);
2581
+ case PARSE_ERROR: throw e("parsing error occured", err, err.code);
2582
+ case INVALID_RESPONSE: throw e("invalid response encountered", err, err.code);
2583
+ case JWT_CLAIM_COMPARISON: throw e("unexpected JWT claim value encountered", err, err.code);
2584
+ case JSON_ATTRIBUTE_COMPARISON: throw e("unexpected JSON attribute value encountered", err, err.code);
2585
+ case JWT_TIMESTAMP_CHECK: throw e("JWT timestamp claim value failed validation", err, err.code);
2586
+ default: throw e(err.message, err, err.code);
2587
+ }
2588
+ if (err instanceof UnsupportedOperationError) throw e("unsupported operation", err, err.code);
2589
+ if (err instanceof DOMException) switch (err.name) {
2590
+ case "OperationError": throw e("runtime operation error", err, UNSUPPORTED_OPERATION);
2591
+ case "NotSupportedError": throw e("runtime unsupported operation", err, UNSUPPORTED_OPERATION);
2592
+ case "TimeoutError": throw e("operation timed out", err, "OAUTH_TIMEOUT");
2593
+ case "AbortError": throw e("operation aborted", err, "OAUTH_ABORT");
2594
+ }
2595
+ throw new ClientError("something went wrong", { cause: err });
2596
+ }
2597
+ function handleEntraId(server, as, options) {
2598
+ if (server.origin === "https://login.microsoftonline.com" && (!options?.algorithm || options.algorithm === "oidc")) {
2599
+ as[kEntraId] = true;
2600
+ return true;
2601
+ }
2602
+ return false;
2603
+ }
2604
+ function handleB2Clogin(server, options) {
2605
+ if (server.hostname.endsWith(".b2clogin.com") && (!options?.algorithm || options.algorithm === "oidc")) return true;
2606
+ return false;
2607
+ }
2608
+ async function discovery(server, clientId, metadata, clientAuthentication, options) {
2609
+ const instance = new Configuration(await performDiscovery(server, options), clientId, metadata, clientAuthentication);
2610
+ let internals = int(instance);
2611
+ if (options?.[customFetch]) internals.fetch = options[customFetch];
2612
+ if (options?.timeout) internals.timeout = options.timeout;
2613
+ if (options?.execute) for (const extension of options.execute) extension(instance);
2614
+ return instance;
2615
+ }
2616
+ async function performDiscovery(server, options) {
2617
+ if (!(server instanceof URL)) throw CodedTypeError("\"server\" must be an instance of URL", ERR_INVALID_ARG_TYPE);
2618
+ const resolve = !server.href.includes("/.well-known/");
2619
+ const timeout = options?.timeout ?? 30;
2620
+ const signal$2 = AbortSignal.timeout(timeout * 1e3);
2621
+ const as = await (resolve ? discoveryRequest(server, {
2622
+ algorithm: options?.algorithm,
2623
+ [customFetch$1]: options?.[customFetch],
2624
+ [allowInsecureRequests$1]: options?.execute?.includes(allowInsecureRequests),
2625
+ signal: signal$2,
2626
+ headers: new Headers(headers)
2627
+ }) : (options?.[customFetch] || fetch)((() => {
2628
+ checkProtocol(server, options?.execute?.includes(allowInsecureRequests) ? false : true);
2629
+ return server.href;
2630
+ })(), {
2631
+ headers: Object.fromEntries(new Headers({
2632
+ accept: "application/json",
2633
+ ...headers
2634
+ }).entries()),
2635
+ body: void 0,
2636
+ method: "GET",
2637
+ redirect: "manual",
2638
+ signal: signal$2
2639
+ })).then((response) => processDiscoveryResponse(_nodiscoverycheck, response)).catch(errorHandler);
2640
+ if (resolve && new URL(as.issuer).href !== server.href) handleEntraId(server, as, options) || handleB2Clogin(server, options) || (() => {
2641
+ throw new ClientError("discovered metadata issuer does not match the expected issuer", {
2642
+ code: JSON_ATTRIBUTE_COMPARISON,
2643
+ cause: {
2644
+ expected: server.href,
2645
+ body: as,
2646
+ attribute: "issuer"
2647
+ }
2648
+ });
2649
+ })();
2650
+ return as;
2651
+ }
2652
+ function getServerHelpers(metadata) {
2653
+ return { supportsPKCE: {
2654
+ __proto__: null,
2655
+ value(method = "S256") {
2656
+ return metadata.code_challenge_methods_supported?.includes(method) === true;
2657
+ }
2658
+ } };
2659
+ }
2660
+ function addServerHelpers(metadata) {
2661
+ Object.defineProperties(metadata, getServerHelpers(metadata));
2662
+ }
2663
+ const kEntraId = Symbol();
2664
+ var Configuration = class {
2665
+ constructor(server, clientId, metadata, clientAuthentication) {
2666
+ if (typeof clientId !== "string" || !clientId.length) throw CodedTypeError("\"clientId\" must be a non-empty string", ERR_INVALID_ARG_TYPE);
2667
+ if (typeof metadata === "string") metadata = { client_secret: metadata };
2668
+ if (metadata?.client_id !== void 0 && clientId !== metadata.client_id) throw CodedTypeError("\"clientId\" and \"metadata.client_id\" must be the same", ERR_INVALID_ARG_VALUE);
2669
+ const client = {
2670
+ ...structuredClone(metadata),
2671
+ client_id: clientId
2672
+ };
2673
+ client[clockSkew$1] = metadata?.[clockSkew$1] ?? 0;
2674
+ client[clockTolerance$1] = metadata?.[clockTolerance$1] ?? 30;
2675
+ let auth;
2676
+ if (clientAuthentication) auth = clientAuthentication;
2677
+ else if (typeof client.client_secret === "string" && client.client_secret.length) auth = ClientSecretPost(client.client_secret);
2678
+ else auth = None();
2679
+ let c = Object.freeze(client);
2680
+ const clone = structuredClone(server);
2681
+ if (kEntraId in server) clone[_expectedIssuer] = ({ claims: { tid } }) => server.issuer.replace("{tenantid}", tid);
2682
+ let as = Object.freeze(clone);
2683
+ props ||= /* @__PURE__ */ new WeakMap();
2684
+ props.set(this, {
2685
+ __proto__: null,
2686
+ as,
2687
+ c,
2688
+ auth,
2689
+ tlsOnly: true,
2690
+ jwksCache: {}
2691
+ });
2692
+ }
2693
+ serverMetadata() {
2694
+ const metadata = structuredClone(int(this).as);
2695
+ addServerHelpers(metadata);
2696
+ return metadata;
2697
+ }
2698
+ clientMetadata() {
2699
+ return structuredClone(int(this).c);
2700
+ }
2701
+ get timeout() {
2702
+ return int(this).timeout;
2703
+ }
2704
+ set timeout(value) {
2705
+ int(this).timeout = value;
2706
+ }
2707
+ get [customFetch]() {
2708
+ return int(this).fetch;
2709
+ }
2710
+ set [customFetch](value) {
2711
+ int(this).fetch = value;
2712
+ }
2713
+ };
2714
+ Object.freeze(Configuration.prototype);
2715
+ function getHelpers(response) {
2716
+ let exp = void 0;
2717
+ if (response.expires_in !== void 0) {
2718
+ const now = /* @__PURE__ */ new Date();
2719
+ now.setSeconds(now.getSeconds() + response.expires_in);
2720
+ exp = now.getTime();
2721
+ }
2722
+ return {
2723
+ expiresIn: {
2724
+ __proto__: null,
2725
+ value() {
2726
+ if (exp) {
2727
+ const now = Date.now();
2728
+ if (exp > now) return Math.floor((exp - now) / 1e3);
2729
+ return 0;
2730
+ }
2731
+ }
2732
+ },
2733
+ claims: {
2734
+ __proto__: null,
2735
+ value() {
2736
+ try {
2737
+ return getValidatedIdTokenClaims(this);
2738
+ } catch {
2739
+ return;
2740
+ }
2741
+ }
2742
+ }
2743
+ };
2744
+ }
2745
+ function addHelpers(response) {
2746
+ Object.defineProperties(response, getHelpers(response));
2747
+ }
2748
+ function allowInsecureRequests(config) {
2749
+ int(config).tlsOnly = false;
2750
+ }
2751
+ function stripParams(url) {
2752
+ url = new URL(url);
2753
+ url.search = "";
2754
+ url.hash = "";
2755
+ return url.href;
2756
+ }
2757
+ function webInstanceOf(input, toStringTag) {
2758
+ try {
2759
+ return Object.getPrototypeOf(input)[Symbol.toStringTag] === toStringTag;
2760
+ } catch {
2761
+ return false;
2762
+ }
2763
+ }
2764
+ async function authorizationCodeGrant(config, currentUrl, checks, tokenEndpointParameters, options) {
2765
+ checkConfig(config);
2766
+ if (options?.flag !== retry && !(currentUrl instanceof URL) && !webInstanceOf(currentUrl, "Request")) throw CodedTypeError("\"currentUrl\" must be an instance of URL, or Request", ERR_INVALID_ARG_TYPE);
2767
+ let authResponse;
2768
+ let redirectUri;
2769
+ const { as, c, auth, fetch: fetch$1, tlsOnly, jarm, hybrid, nonRepudiation, timeout, decrypt, implicit } = int(config);
2770
+ if (options?.flag === retry) {
2771
+ authResponse = options.authResponse;
2772
+ redirectUri = options.redirectUri;
2773
+ } else {
2774
+ if (!(currentUrl instanceof URL)) {
2775
+ const request = currentUrl;
2776
+ currentUrl = new URL(currentUrl.url);
2777
+ switch (request.method) {
2778
+ case "GET": break;
2779
+ case "POST":
2780
+ const params = new URLSearchParams(await formPostResponse(request));
2781
+ if (hybrid) currentUrl.hash = params.toString();
2782
+ else for (const [k, v] of params.entries()) currentUrl.searchParams.append(k, v);
2783
+ break;
2784
+ default: throw CodedTypeError("unexpected Request HTTP method", ERR_INVALID_ARG_VALUE);
2785
+ }
2786
+ }
2787
+ redirectUri = stripParams(currentUrl);
2788
+ switch (true) {
2789
+ case !!jarm:
2790
+ authResponse = await jarm(currentUrl, checks?.expectedState);
2791
+ break;
2792
+ case !!hybrid:
2793
+ authResponse = await hybrid(currentUrl, checks?.expectedNonce, checks?.expectedState, checks?.maxAge);
2794
+ break;
2795
+ case !!implicit: throw new TypeError("authorizationCodeGrant() cannot be used by response_type=id_token clients");
2796
+ default: try {
2797
+ authResponse = validateAuthResponse(as, c, currentUrl.searchParams, checks?.expectedState);
2798
+ } catch (err) {
2799
+ errorHandler(err);
2800
+ }
2801
+ }
2802
+ }
2803
+ const response = await authorizationCodeGrantRequest(as, c, auth, authResponse, redirectUri, checks?.pkceCodeVerifier || nopkce, {
2804
+ additionalParameters: tokenEndpointParameters,
2805
+ [customFetch$1]: fetch$1,
2806
+ [allowInsecureRequests$1]: !tlsOnly,
2807
+ DPoP: options?.DPoP,
2808
+ headers: new Headers(headers),
2809
+ signal: signal(timeout)
2810
+ }).catch(errorHandler);
2811
+ if (typeof checks?.expectedNonce === "string" || typeof checks?.maxAge === "number") checks.idTokenExpected = true;
2812
+ const p = processAuthorizationCodeResponse(as, c, response, {
2813
+ expectedNonce: checks?.expectedNonce,
2814
+ maxAge: checks?.maxAge,
2815
+ requireIdToken: checks?.idTokenExpected,
2816
+ [jweDecrypt]: decrypt
2817
+ });
2818
+ let result;
2819
+ try {
2820
+ result = await p;
2821
+ } catch (err) {
2822
+ if (retryable(err, options)) return authorizationCodeGrant(config, void 0, checks, tokenEndpointParameters, {
2823
+ ...options,
2824
+ flag: retry,
2825
+ authResponse,
2826
+ redirectUri
2827
+ });
2828
+ errorHandler(err);
2829
+ }
2830
+ result.id_token && await nonRepudiation?.(response);
2831
+ addHelpers(result);
2832
+ return result;
2833
+ }
2834
+ async function refreshTokenGrant(config, refreshToken, parameters, options) {
2835
+ checkConfig(config);
2836
+ parameters = new URLSearchParams(parameters);
2837
+ const { as, c, auth, fetch: fetch$1, tlsOnly, nonRepudiation, timeout, decrypt } = int(config);
2838
+ const response = await refreshTokenGrantRequest(as, c, auth, refreshToken, {
2839
+ [customFetch$1]: fetch$1,
2840
+ [allowInsecureRequests$1]: !tlsOnly,
2841
+ additionalParameters: parameters,
2842
+ DPoP: options?.DPoP,
2843
+ headers: new Headers(headers),
2844
+ signal: signal(timeout)
2845
+ }).catch(errorHandler);
2846
+ const p = processRefreshTokenResponse(as, c, response, { [jweDecrypt]: decrypt });
2847
+ let result;
2848
+ try {
2849
+ result = await p;
2850
+ } catch (err) {
2851
+ if (retryable(err, options)) return refreshTokenGrant(config, refreshToken, parameters, {
2852
+ ...options,
2853
+ flag: retry
2854
+ });
2855
+ errorHandler(err);
2856
+ }
2857
+ result.id_token && await nonRepudiation?.(response);
2858
+ addHelpers(result);
2859
+ return result;
2860
+ }
2861
+ function buildAuthorizationUrl(config, parameters) {
2862
+ checkConfig(config);
2863
+ const { as, c, tlsOnly, hybrid, jarm, implicit } = int(config);
2864
+ const authorizationEndpoint = resolveEndpoint(as, "authorization_endpoint", false, tlsOnly);
2865
+ parameters = new URLSearchParams(parameters);
2866
+ if (!parameters.has("client_id")) parameters.set("client_id", c.client_id);
2867
+ if (!parameters.has("request_uri") && !parameters.has("request")) {
2868
+ if (!parameters.has("response_type")) parameters.set("response_type", hybrid ? "code id_token" : implicit ? "id_token" : "code");
2869
+ if (implicit && !parameters.has("nonce")) throw CodedTypeError("response_type=id_token clients must provide a nonce parameter in their authorization request parameters", ERR_INVALID_ARG_VALUE);
2870
+ if (jarm) parameters.set("response_mode", "jwt");
2871
+ }
2872
+ for (const [k, v] of parameters.entries()) authorizationEndpoint.searchParams.append(k, v);
2873
+ return authorizationEndpoint;
2874
+ }
2875
+ function buildEndSessionUrl(config, parameters) {
2876
+ checkConfig(config);
2877
+ const { as, c, tlsOnly } = int(config);
2878
+ const endSessionEndpoint = resolveEndpoint(as, "end_session_endpoint", false, tlsOnly);
2879
+ parameters = new URLSearchParams(parameters);
2880
+ if (!parameters.has("client_id")) parameters.set("client_id", c.client_id);
2881
+ for (const [k, v] of parameters.entries()) endSessionEndpoint.searchParams.append(k, v);
2882
+ return endSessionEndpoint;
2883
+ }
2884
+ function checkConfig(input) {
2885
+ if (!(input instanceof Configuration)) throw CodedTypeError("\"config\" must be an instance of Configuration", ERR_INVALID_ARG_TYPE);
2886
+ if (Object.getPrototypeOf(input) !== Configuration.prototype) throw CodedTypeError("subclassing Configuration is not allowed", ERR_INVALID_ARG_VALUE);
2887
+ }
2888
+ function signal(timeout) {
2889
+ return timeout ? AbortSignal.timeout(timeout * 1e3) : void 0;
2890
+ }
2891
+ function retryable(err, options) {
2892
+ if (options?.DPoP && options.flag !== retry) return isDPoPNonceError(err);
2893
+ return false;
2894
+ }
2895
+ const retry = Symbol();
2896
+
2897
+ //#endregion
2898
+ //#region src/auth/descriptors/$auth.ts
2899
+ /**
2900
+ * Creates an authentication provider descriptor for handling user login flows.
2901
+ *
2902
+ * Supports multiple authentication strategies: credentials (username/password), OAuth2,
2903
+ * and OIDC (OpenID Connect). Handles token management, user profile retrieval, and
2904
+ * integration with both external identity providers (Auth0, Keycloak) and internal realms.
2905
+ *
2906
+ * **Authentication Types**: Credentials, OAuth2 (Google, GitHub), OIDC, External providers
2907
+ *
2908
+ * @example
2909
+ * ```ts
2910
+ * class AuthProviders {
2911
+ * // Internal credentials-based auth
2912
+ * credentials = $auth({
2913
+ * realm: this.userRealm,
2914
+ * credentials: {
2915
+ * account: async ({ username, password }) => {
2916
+ * return await this.validateUser(username, password);
2917
+ * }
2918
+ * }
2919
+ * });
2920
+ *
2921
+ * // External OIDC provider
2922
+ * keycloak = $auth({
2923
+ * oidc: {
2924
+ * issuer: "https://auth.example.com",
2925
+ * clientId: "my-app",
2926
+ * clientSecret: "secret",
2927
+ * redirectUri: "/auth/callback"
2928
+ * }
2929
+ * });
2930
+ * }
2931
+ * ```
2932
+ */
2933
+ const $auth = (options) => {
2934
+ return (0, alepha.createDescriptor)(AuthDescriptor, options);
2935
+ };
2936
+ var AuthDescriptor = class extends alepha.Descriptor {
2937
+ securityProvider = (0, alepha.$inject)(alepha_security.SecurityProvider);
2938
+ dateTimeProvider = (0, alepha.$inject)(alepha_datetime.DateTimeProvider);
2939
+ oauth;
2940
+ get name() {
2941
+ return this.options.name ?? this.config.propertyKey;
2942
+ }
2943
+ get jwks_uri() {
2944
+ const jwks = this.oauth?.serverMetadata().jwks_uri;
2945
+ if (!jwks) throw new alepha.AlephaError("No JWKS URI available for the auth provider");
2946
+ return jwks;
2947
+ }
2948
+ get scope() {
2949
+ if ("oauth" in this.options) return this.options.oauth.scope;
2950
+ if ("oidc" in this.options) return this.options.oidc.scope || "openid profile email";
2951
+ throw new alepha.AlephaError("No OAuth2 or OIDC configuration available for the auth provider");
2952
+ }
2953
+ get redirect_uri() {
2954
+ if ("oauth" in this.options) return this.options.oauth.redirectUri;
2955
+ if ("oidc" in this.options) return this.options.oidc.redirectUri;
2956
+ throw new alepha.AlephaError("No OAuth2 or OIDC configuration available for the auth provider");
2957
+ }
2958
+ /**
2959
+ * Refreshes the access token using the refresh token.
2960
+ * Can be used on oauth2, oidc or credentials auth providers.
2961
+ */
2962
+ async refresh(refreshToken, accessToken) {
2963
+ if ("realm" in this.options) return this.options.realm.refreshToken(refreshToken, accessToken).then((it) => it.tokens).catch((error) => {
2964
+ throw new alepha_security.SecurityError("Failed to refresh access token using the refresh token (realm)", { cause: error });
2965
+ });
2966
+ else if (this.oauth) try {
2967
+ return {
2968
+ ...await refreshTokenGrant(this.oauth, refreshToken),
2969
+ issued_at: this.dateTimeProvider.now().unix()
2970
+ };
2971
+ } catch (error) {
2972
+ throw new alepha_security.SecurityError("Failed to refresh access token using the refresh token (oauth2)", { cause: error });
2973
+ }
2974
+ throw new alepha.AlephaError("No realm or OAuth2 configuration available for refreshing the access token");
2975
+ }
2976
+ /**
2977
+ * Extracts user information from the access token.
2978
+ * This is used to create a user account from the access token.
2979
+ */
2980
+ async user(tokens) {
2981
+ try {
2982
+ if ("oauth" in this.options) {
2983
+ const profile = await this.options.oauth.userinfo(tokens);
2984
+ if (this.options.oauth.account) return this.options.oauth.account({
2985
+ ...tokens,
2986
+ user: profile
2987
+ });
2988
+ return this.securityProvider.createUserFromPayload(profile);
2989
+ }
2990
+ if ("oidc" in this.options) {
2991
+ const payload = this.getUserFromIdToken(tokens.id_token || "");
2992
+ if (this.options.oidc.account) return this.options.oidc.account({
2993
+ ...tokens,
2994
+ user: payload
2995
+ });
2996
+ return this.securityProvider.createUserFromPayload(payload);
2997
+ }
2998
+ } catch (error) {
2999
+ throw new alepha_security.SecurityError("Failed to extract user from identity provider tokens", { cause: error });
3000
+ }
3001
+ throw new alepha.AlephaError("This authentication does not support user extraction from tokens");
3002
+ }
3003
+ getUserFromIdToken(idToken) {
3004
+ try {
3005
+ return JSON.parse(Buffer.from(idToken.split(".")[1], "base64").toString("utf8"));
3006
+ } catch (error) {
3007
+ throw new alepha.AlephaError("Failed to parse ID Token payload", { cause: error });
3008
+ }
3009
+ }
3010
+ async prepare() {
3011
+ const addons = [];
3012
+ addons.push(allowInsecureRequests);
3013
+ if ("oidc" in this.options) {
3014
+ const { oidc } = this.options;
3015
+ this.oauth = await discovery(new URL(oidc.issuer), oidc.clientId, { client_secret: oidc.clientSecret }, void 0, { execute: addons });
3016
+ }
3017
+ if ("oauth" in this.options) {
3018
+ const { oauth } = this.options;
3019
+ this.oauth = new Configuration({
3020
+ authorization_endpoint: oauth.authorization,
3021
+ token_endpoint: oauth.token,
3022
+ issuer: oauth.authorization,
3023
+ jwks_uri: void 0,
3024
+ end_session_endpoint: void 0
3025
+ }, oauth.clientId, { client_secret: oauth.clientSecret });
3026
+ }
3027
+ }
3028
+ };
3029
+ $auth[alepha.KIND] = AuthDescriptor;
3030
+
3031
+ //#endregion
3032
+ //#region src/auth/schemas/tokensSchema.ts
3033
+ const tokensSchema = alepha.t.object({
3034
+ provider: alepha.t.text(),
3035
+ access_token: alepha.t.text({ size: "rich" }),
3036
+ issued_at: alepha.t.number(),
3037
+ expires_in: alepha.t.optional(alepha.t.number()),
3038
+ refresh_token: alepha.t.optional(alepha.t.text({ size: "rich" })),
3039
+ refresh_token_expires_in: alepha.t.optional(alepha.t.number()),
3040
+ refresh_expires_in: alepha.t.optional(alepha.t.number({ description: "Alias of `refresh_token_expires_in` for compatibility with some providers." })),
3041
+ id_token: alepha.t.optional(alepha.t.text({ size: "rich" })),
3042
+ scope: alepha.t.optional(alepha.t.text())
3043
+ });
3044
+
3045
+ //#endregion
3046
+ //#region src/auth/schemas/tokenResponseSchema.ts
3047
+ const tokenResponseSchema = alepha.t.extend(tokensSchema, {
3048
+ user: alepha_security.userAccountInfoSchema,
3049
+ api: alepha_server_links.apiLinksResponseSchema
3050
+ });
3051
+
3052
+ //#endregion
3053
+ //#region src/auth/schemas/userinfoResponseSchema.ts
3054
+ const userinfoResponseSchema = alepha.t.object({
3055
+ user: alepha.t.optional(alepha_security.userAccountInfoSchema),
3056
+ api: alepha_server_links.apiLinksResponseSchema
3057
+ });
3058
+
3059
+ //#endregion
3060
+ //#region src/auth/services/ReactAuth.ts
3061
+ /**
3062
+ * Browser, SSR friendly, service to handle authentication.
3063
+ */
3064
+ var ReactAuth = class ReactAuth {
3065
+ log = (0, alepha_logger.$logger)();
3066
+ alepha = (0, alepha.$inject)(alepha.Alepha);
3067
+ httpClient = (0, alepha.$inject)(alepha_server.HttpClient);
3068
+ static path = {
3069
+ login: "/oauth/login",
3070
+ callback: "/oauth/callback",
3071
+ logout: "/oauth/logout",
3072
+ token: "/_auth/token",
3073
+ refresh: "/_auth/refresh",
3074
+ userinfo: "/_auth/userinfo"
3075
+ };
3076
+ onBeginTransition = (0, alepha.$hook)({
3077
+ on: "react:transition:begin",
3078
+ handler: async (event) => {
3079
+ if (this.alepha.isBrowser()) Object.defineProperty(event.state, "user", { get: () => this.user });
3080
+ }
3081
+ });
3082
+ onFetchRequest = (0, alepha.$hook)({
3083
+ on: "client:onRequest",
3084
+ handler: async ({ request }) => {
3085
+ if (this.alepha.isBrowser() && this.user) request.credentials ??= "include";
3086
+ }
3087
+ });
3088
+ /**
3089
+ * Get the current authenticated user.
3090
+ *
3091
+ * Alias for `alepha.state.get("user")`
3092
+ */
3093
+ get user() {
3094
+ return this.alepha.state.get("alepha.server.request.user");
3095
+ }
3096
+ async ping() {
3097
+ const { data } = await this.httpClient.fetch(ReactAuth.path.userinfo, { schema: { response: userinfoResponseSchema } });
3098
+ this.alepha.state.set("alepha.server.request.apiLinks", data.api);
3099
+ this.alepha.state.set("alepha.server.request.user", data.user);
3100
+ return data.user;
3101
+ }
3102
+ async login(provider, options) {
3103
+ if (options.username || options.password) {
3104
+ const { data } = await this.httpClient.fetch(`${options.hostname || ""}${ReactAuth.path.token}?provider=${provider}`, {
3105
+ method: "POST",
3106
+ body: JSON.stringify({
3107
+ username: options.username,
3108
+ password: options.password,
3109
+ ...options
3110
+ }),
3111
+ schema: { response: tokenResponseSchema }
3112
+ });
3113
+ this.alepha.state.set("alepha.server.request.apiLinks", data.api);
3114
+ this.alepha.state.set("alepha.server.request.user", data.user);
3115
+ return data;
3116
+ }
3117
+ if (this.alepha.isBrowser()) {
3118
+ const browser = this.alepha.inject(ReactBrowserProvider);
3119
+ const redirect = options.redirect || (browser.transitioning ? window.location.origin + browser.transitioning.to : window.location.href);
3120
+ const href = `${window.location.origin}${ReactAuth.path.login}?provider=${provider}&redirect_uri=${encodeURIComponent(redirect)}`;
3121
+ if (browser.transitioning) throw new Redirection(href);
3122
+ else {
3123
+ window.location.href = href;
3124
+ return {};
3125
+ }
3126
+ }
3127
+ throw new Redirection(`${ReactAuth.path.login}?provider=${provider}&redirect_uri=${options.redirect || "/"}`);
3128
+ }
3129
+ logout() {
3130
+ window.location.href = `${ReactAuth.path.logout}?post_logout_redirect_uri=${encodeURIComponent(window.location.origin)}`;
3131
+ }
3132
+ };
3133
+
3134
+ //#endregion
3135
+ //#region src/auth/providers/ReactAuthProvider.ts
3136
+ var ReactAuthProvider = class {
3137
+ log = (0, alepha_logger.$logger)();
3138
+ alepha = (0, alepha.$inject)(alepha.Alepha);
3139
+ serverCookiesProvider = (0, alepha.$inject)(alepha_server_cookies.ServerCookiesProvider);
3140
+ dateTimeProvider = (0, alepha.$inject)(alepha_datetime.DateTimeProvider);
3141
+ serverLinksProvider = (0, alepha.$inject)(alepha_server_links.ServerLinksProvider);
3142
+ reactAuth = (0, alepha.$inject)(ReactAuth);
3143
+ authorizationCode = (0, alepha_server_cookies.$cookie)({
3144
+ name: "authorizationCode",
3145
+ ttl: [15, "minutes"],
3146
+ httpOnly: true,
3147
+ schema: alepha.t.object({
3148
+ provider: alepha.t.text(),
3149
+ codeVerifier: alepha.t.optional(alepha.t.text({ size: "long" })),
3150
+ redirectUri: alepha.t.optional(alepha.t.text({ size: "long" })),
3151
+ state: alepha.t.optional(alepha.t.text()),
3152
+ nonce: alepha.t.optional(alepha.t.text())
3153
+ })
3154
+ });
3155
+ tokens = (0, alepha_server_cookies.$cookie)({
3156
+ name: "tokens",
3157
+ ttl: [30, "days"],
3158
+ httpOnly: true,
3159
+ compress: true,
3160
+ encrypt: true,
3161
+ schema: tokensSchema
3162
+ });
3163
+ onRender = (0, alepha.$hook)({
3164
+ on: "react:server:render:begin",
3165
+ handler: async ({ request, state }) => {
3166
+ if (request?.user) {
3167
+ const { token, realm, ...user } = request.user;
3168
+ this.alepha.state.set("alepha.server.request.user", user);
3169
+ state.user = user;
3170
+ }
3171
+ }
3172
+ });
3173
+ get identities() {
3174
+ return this.alepha.descriptors($auth).filter((auth) => !auth.options.disabled);
3175
+ }
3176
+ configure = (0, alepha.$hook)({
3177
+ on: "configure",
3178
+ handler: async () => {
3179
+ for (const identity of this.identities) await identity.prepare();
3180
+ }
3181
+ });
3182
+ getAccessTokens(tokens) {
3183
+ const idp = this.provider(tokens.provider);
3184
+ if ("oidc" in idp.options && !("realm" in idp.options) && idp.options.oidc?.useIdToken) return tokens.id_token;
3185
+ return tokens.access_token;
3186
+ }
3187
+ /**
3188
+ * Fill request headers with access token from cookies or fallback to provider's fallback function.
3189
+ */
3190
+ onRequest = (0, alepha.$hook)({
3191
+ on: "server:onRequest",
3192
+ after: this.serverCookiesProvider,
3193
+ handler: async ({ request }) => {
3194
+ const cookies = request.cookies;
3195
+ if (cookies) {
3196
+ const tokens = await this.cookiesToTokens(cookies);
3197
+ if (tokens) {
3198
+ request.headers.authorization = `Bearer ${this.getAccessTokens(tokens)}`;
3199
+ this.log.trace("Access token set in request headers", { provider: tokens.provider });
3200
+ }
3201
+ }
3202
+ if (!request.headers.authorization) {
3203
+ for (const provider of this.identities) if (!("realm" in provider.options) && !!provider.options.fallback) {
3204
+ const token = await provider.options.fallback();
3205
+ if (token) {
3206
+ request.headers.authorization = `Bearer ${token}`;
3207
+ break;
3208
+ }
3209
+ }
3210
+ }
3211
+ }
3212
+ });
3213
+ /**
3214
+ * Convert cookies to tokens.
3215
+ * If the tokens are expired, try to refresh them using the refresh token.
3216
+ */
3217
+ async cookiesToTokens(cookies) {
3218
+ const tokens = this.tokens.get({ cookies });
3219
+ if (!tokens) {
3220
+ this.log.trace("No tokens found in cookies");
3221
+ return;
3222
+ }
3223
+ this.log.trace("Tokens found in cookies", {
3224
+ expires_in: tokens.expires_in,
3225
+ issued_at: tokens.issued_at
3226
+ });
3227
+ const refreshedTokens = await this.refreshTokens(tokens);
3228
+ if (!refreshedTokens) {
3229
+ this.tokens.del({ cookies });
3230
+ return;
3231
+ }
3232
+ if (refreshedTokens.access_token !== tokens.access_token) this.setTokens(refreshedTokens, cookies);
3233
+ return refreshedTokens;
3234
+ }
3235
+ async refreshTokens(tokens) {
3236
+ if (tokens.expires_in && tokens.issued_at) {
3237
+ if (tokens.issued_at + (tokens.expires_in - 10) < this.dateTimeProvider.now().unix()) {
3238
+ this.log.trace("Tokens are expired");
3239
+ if (tokens.refresh_token) {
3240
+ this.log.trace("Trying to refresh tokens using refresh token");
3241
+ try {
3242
+ const newTokens = {
3243
+ ...await this.provider(tokens).refresh(tokens.refresh_token, tokens.access_token),
3244
+ provider: tokens.provider,
3245
+ issued_at: this.dateTimeProvider.now().unix()
3246
+ };
3247
+ this.log.debug("Tokens refreshed successfully");
3248
+ return newTokens;
3249
+ } catch (e$1) {
3250
+ this.log.warn("Failed to refresh token", e$1);
3251
+ }
3252
+ }
3253
+ return;
3254
+ }
3255
+ }
3256
+ if (!tokens.issued_at && tokens.access_token) return;
3257
+ return tokens;
3258
+ }
3259
+ /**
3260
+ * Get user information.
3261
+ */
3262
+ userinfo = (0, alepha_server.$route)({
3263
+ path: ReactAuth.path.userinfo,
3264
+ schema: { response: userinfoResponseSchema },
3265
+ handler: async ({ user, headers: headers$1, cookies }) => {
3266
+ const tokens = this.tokens.get({ cookies });
3267
+ if (tokens) {
3268
+ const provider = this.provider(tokens);
3269
+ if (!("realm" in provider.options)) {
3270
+ const user$1 = await provider.user(tokens);
3271
+ return {
3272
+ api: await this.serverLinksProvider.getUserApiLinks({
3273
+ authorization: headers$1.authorization,
3274
+ user: user$1
3275
+ }),
3276
+ user: user$1
3277
+ };
3278
+ }
3279
+ }
3280
+ return {
3281
+ api: await this.serverLinksProvider.getUserApiLinks({
3282
+ authorization: headers$1.authorization,
3283
+ user
3284
+ }),
3285
+ user
3286
+ };
3287
+ }
3288
+ });
3289
+ /**
3290
+ * Refresh a token for internal providers.
3291
+ */
3292
+ refresh = (0, alepha_server.$route)({
3293
+ path: ReactAuth.path.refresh,
3294
+ method: "POST",
3295
+ schema: {
3296
+ query: alepha.t.object({ provider: alepha.t.text() }),
3297
+ body: alepha.t.object({
3298
+ refresh_token: alepha.t.text({ size: "rich" }),
3299
+ access_token: alepha.t.optional(alepha.t.text({
3300
+ size: "rich",
3301
+ description: "Required if provider has stateless refresh token on credentials mode"
3302
+ }))
3303
+ }),
3304
+ response: tokensSchema
3305
+ },
3306
+ handler: async ({ query, body, cookies }) => {
3307
+ const provider = this.provider(query);
3308
+ const tokens = {
3309
+ provider: query.provider,
3310
+ ...await provider.refresh(body.refresh_token, body.access_token)
3311
+ };
3312
+ this.setTokens(tokens, cookies);
3313
+ return tokens;
3314
+ }
3315
+ });
3316
+ /**
3317
+ * Login for local password-based authentication.
3318
+ */
3319
+ token = (0, alepha_server.$route)({
3320
+ path: ReactAuth.path.token,
3321
+ method: "POST",
3322
+ schema: {
3323
+ query: alepha.t.object({ provider: alepha.t.text() }),
3324
+ body: alepha.t.object({
3325
+ username: alepha.t.text(),
3326
+ password: alepha.t.text()
3327
+ }),
3328
+ response: tokenResponseSchema
3329
+ },
3330
+ handler: async ({ query, body, cookies }) => {
3331
+ const provider = this.provider(query);
3332
+ const realm = "realm" in provider.options && provider.options.realm;
3333
+ if (!realm) throw new alepha_security.SecurityError(`Auth provider '${query.provider}' does not support password grant`);
3334
+ const credentials = "credentials" in provider.options && provider.options.credentials;
3335
+ if (!credentials) throw new alepha_security.SecurityError(`Auth provider '${query.provider}' does not support password grant`);
3336
+ let user;
3337
+ try {
3338
+ user = await credentials.account(body);
3339
+ } catch (e$1) {
3340
+ throw new alepha_server.UnauthorizedError(`Failed to authenticate user`, { cause: e$1 });
3341
+ }
3342
+ const tokens = {
3343
+ provider: query.provider,
3344
+ ...await realm.createToken(user)
3345
+ };
3346
+ this.setTokens(tokens, cookies);
3347
+ const api = await this.serverLinksProvider.getUserApiLinks({ user });
3348
+ return {
3349
+ ...tokens,
3350
+ user,
3351
+ api
3352
+ };
3353
+ }
3354
+ });
3355
+ /**
3356
+ * Oauth2/OIDC login route.
3357
+ */
3358
+ login = (0, alepha_server.$route)({
3359
+ path: ReactAuth.path.login,
3360
+ schema: { query: alepha.t.object({
3361
+ provider: alepha.t.text(),
3362
+ redirect_uri: alepha.t.optional(alepha.t.text({ size: "rich" }))
3363
+ }) },
3364
+ handler: async ({ query, url, reply }) => {
3365
+ const provider = this.provider(query);
3366
+ const oauth = provider.oauth;
3367
+ if (!oauth) throw new alepha_security.SecurityError(`Auth provider '${query.provider}' does not support OAuth2`);
3368
+ const scope = provider.scope;
3369
+ let redirect_uri = provider.redirect_uri || ReactAuth.path.callback;
3370
+ if (redirect_uri.startsWith("/")) redirect_uri = `${url.protocol}//${url.host}${redirect_uri}`;
3371
+ const oidc = "oidc" in provider.options && provider.options.oidc;
3372
+ if (!oauth.serverMetadata().supportsPKCE()) {
3373
+ const state = randomState();
3374
+ const parameters$1 = {
3375
+ redirect_uri,
3376
+ state
3377
+ };
3378
+ if (oidc) parameters$1.nonce = randomState();
3379
+ if (scope) parameters$1.scope = scope;
3380
+ this.authorizationCode.set({
3381
+ state,
3382
+ nonce: parameters$1.nonce,
3383
+ redirectUri: query.redirect_uri ?? "/",
3384
+ provider: query.provider
3385
+ });
3386
+ reply.redirect(buildAuthorizationUrl(oauth, parameters$1).toString());
3387
+ return;
3388
+ }
3389
+ const codeVerifier = randomPKCECodeVerifier();
3390
+ const codeChallenge = await calculatePKCECodeChallenge(codeVerifier);
3391
+ const parameters = {
3392
+ redirect_uri,
3393
+ code_challenge: codeChallenge,
3394
+ code_challenge_method: "S256"
3395
+ };
3396
+ if (scope) parameters.scope = scope;
3397
+ this.authorizationCode.set({
3398
+ codeVerifier,
3399
+ redirectUri: query.redirect_uri ?? "/",
3400
+ provider: query.provider
3401
+ });
3402
+ reply.redirect(buildAuthorizationUrl(oauth, parameters).toString());
3403
+ }
3404
+ });
3405
+ /**
3406
+ * Callback for OAuth2/OIDC providers.
3407
+ * It handles the authorization code flow and retrieves the access token.
3408
+ */
3409
+ callback = (0, alepha_server.$route)({
3410
+ path: ReactAuth.path.callback,
3411
+ handler: async ({ url, reply, cookies }) => {
3412
+ const authorizationCode = this.authorizationCode.get({ cookies });
3413
+ if (!authorizationCode) throw new alepha_server.BadRequestError("Missing code verifier");
3414
+ const provider = this.provider(authorizationCode);
3415
+ const oauth = provider.oauth;
3416
+ if (!oauth) throw new alepha_security.SecurityError(`Auth provider '${provider.name}' does not support OAuth2`);
3417
+ const redirectUri = authorizationCode.redirectUri ?? "/";
3418
+ const externalTokens = await authorizationCodeGrant(oauth, url, {
3419
+ pkceCodeVerifier: authorizationCode.codeVerifier,
3420
+ expectedState: authorizationCode.state,
3421
+ expectedNonce: authorizationCode.nonce
3422
+ }).then((tokens$1) => ({
3423
+ issued_at: this.dateTimeProvider.now().unix(),
3424
+ provider: provider.name,
3425
+ ...tokens$1
3426
+ })).catch((e$1) => {
3427
+ this.log.error("Failed to get access token", e$1);
3428
+ throw new alepha_security.SecurityError("Failed to get access token", { cause: e$1 });
3429
+ });
3430
+ this.authorizationCode.del({ cookies });
3431
+ const realm = "realm" in provider.options && provider.options.realm;
3432
+ if (!realm) {
3433
+ this.setTokens(externalTokens, cookies);
3434
+ reply.redirect(redirectUri);
3435
+ return;
3436
+ }
3437
+ const user = await provider.user(externalTokens);
3438
+ const tokens = await realm.createToken(user);
3439
+ this.setTokens({
3440
+ ...tokens,
3441
+ issued_at: this.dateTimeProvider.now().unix(),
3442
+ provider: provider.name
3443
+ }, cookies);
3444
+ reply.redirect(redirectUri);
3445
+ }
3446
+ });
3447
+ /**
3448
+ * Logout route for OAuth2/OIDC providers.
3449
+ */
3450
+ logout = (0, alepha_server.$route)({
3451
+ path: ReactAuth.path.logout,
3452
+ method: "GET",
3453
+ schema: { query: alepha.t.object({ post_logout_redirect_uri: alepha.t.optional(alepha.t.text()) }) },
3454
+ handler: async ({ query, reply, cookies }) => {
3455
+ const redirect = query.post_logout_redirect_uri ?? "/";
3456
+ const tokens = this.tokens.get({ cookies });
3457
+ if (!tokens) {
3458
+ reply.redirect(redirect);
3459
+ return;
3460
+ }
3461
+ const provider = this.provider(tokens.provider);
3462
+ this.tokens.del({ cookies });
3463
+ if ("realm" in provider.options && tokens.refresh_token) {
3464
+ const onDeleteSession = provider.options.realm.options.settings?.onDeleteSession;
3465
+ if (onDeleteSession) try {
3466
+ await onDeleteSession(tokens.refresh_token);
3467
+ } catch (e$1) {
3468
+ this.log.error("Failed to delete session", e$1);
3469
+ }
3470
+ }
3471
+ const oauth = provider.oauth;
3472
+ if (!oauth) {
3473
+ reply.redirect(redirect);
3474
+ return;
3475
+ }
3476
+ const params = new URLSearchParams();
3477
+ const idToken = tokens?.id_token;
3478
+ params.set("post_logout_redirect_uri", redirect);
3479
+ if (idToken) params.set("id_token_hint", idToken);
3480
+ const customLogoutUri = "oidc" in provider.options ? provider.options.oidc?.logoutUri : void 0;
3481
+ if (customLogoutUri) {
3482
+ reply.redirect(`${customLogoutUri}?${params}`);
3483
+ return;
3484
+ }
3485
+ if (!oauth.serverMetadata().end_session_endpoint) {
3486
+ reply.redirect(redirect);
3487
+ return;
3488
+ }
3489
+ reply.redirect(buildEndSessionUrl(oauth, params).toString());
3490
+ }
3491
+ });
3492
+ provider(opts) {
3493
+ const name = typeof opts === "string" ? opts : opts.provider;
3494
+ const identity = this.identities.find((identity$1) => identity$1.name === name);
3495
+ if (!identity) throw new alepha_security.SecurityError(`Auth provider '${name}' not found`);
3496
+ return identity;
3497
+ }
3498
+ setTokens(tokens, cookies) {
3499
+ const exp = tokens.refresh_token_expires_in || tokens.refresh_expires_in || tokens.expires_in;
3500
+ const ttl = exp ? this.dateTimeProvider.duration(exp, "seconds") : void 0;
3501
+ this.tokens.set(tokens, {
3502
+ cookies,
3503
+ ttl
3504
+ });
3505
+ }
3506
+ };
3507
+
3508
+ //#endregion
3509
+ //#region src/auth/descriptors/$authGithub.ts
3510
+ /**
3511
+ * Already configured GitHub authentication descriptor.
3512
+ *
3513
+ * Uses OAuth2 to authenticate users via their GitHub accounts.
3514
+ * Upon successful authentication, it links the GitHub account to a user session.
3515
+ *
3516
+ * Environment Variables:
3517
+ * - `GITHUB_CLIENT_ID`: The client ID obtained from the GitHub Developer Settings.
3518
+ * - `GITHUB_CLIENT_SECRET`: The client secret obtained from the GitHub Developer Settings.
3519
+ */
3520
+ const $authGithub = (realm, options) => {
3521
+ const { alepha: alepha$1 } = (0, alepha.$context)();
3522
+ const env = alepha$1.parseEnv(alepha.t.object({
3523
+ GITHUB_CLIENT_ID: alepha.t.string(),
3524
+ GITHUB_CLIENT_SECRET: alepha.t.string()
3525
+ }));
3526
+ return $auth({
3527
+ realm,
3528
+ name: "github",
3529
+ oauth: {
3530
+ clientId: env.GITHUB_CLIENT_ID,
3531
+ clientSecret: env.GITHUB_CLIENT_SECRET,
3532
+ authorization: "https://github.com/login/oauth/authorize",
3533
+ token: "https://github.com/login/oauth/access_token",
3534
+ scope: "read:user user:email",
3535
+ userinfo: async (tokens) => {
3536
+ const BASE_URL = "https://api.github.com";
3537
+ const res = await fetch(`${BASE_URL}/user`, { headers: {
3538
+ Authorization: `Bearer ${tokens.access_token}`,
3539
+ "User-Agent": "Alepha"
3540
+ } }).then((res$1) => res$1.json());
3541
+ const user = { sub: res.id.toString() };
3542
+ if (res.email) user.email = res.email;
3543
+ if (res.name) user.name = res.name.trim();
3544
+ if (res.avatar_url) user.picture = res.avatar_url;
3545
+ if (!user.email) {
3546
+ const res$1 = await fetch(`${BASE_URL}/user/emails`, { headers: {
3547
+ Authorization: `Bearer ${tokens.access_token}`,
3548
+ "User-Agent": "Alepha"
3549
+ } });
3550
+ if (res$1.ok) {
3551
+ const emails = await res$1.json();
3552
+ user.email = (emails.find((e$1) => e$1.primary) ?? emails[0]).email;
3553
+ }
3554
+ }
3555
+ return user;
3556
+ },
3557
+ ...options
3558
+ }
3559
+ });
3560
+ };
3561
+
3562
+ //#endregion
3563
+ //#region src/auth/descriptors/$authGoogle.ts
3564
+ /**
3565
+ * Already configured Google authentication descriptor.
3566
+ *
3567
+ * Uses OpenID Connect (OIDC) to authenticate users via their Google accounts.
3568
+ * Upon successful authentication, it links the Google account to a user session.
3569
+ *
3570
+ * Environment Variables:
3571
+ * - `GOOGLE_CLIENT_ID`: The client ID obtained from the Google Developer Console.
3572
+ * - `GOOGLE_CLIENT_SECRET`: The client secret obtained from the Google Developer Console.
3573
+ */
3574
+ const $authGoogle = (realm, options) => {
3575
+ const { alepha: alepha$1 } = (0, alepha.$context)();
3576
+ const env = alepha$1.parseEnv(alepha.t.object({
3577
+ GOOGLE_CLIENT_ID: alepha.t.string(),
3578
+ GOOGLE_CLIENT_SECRET: alepha.t.string()
3579
+ }));
3580
+ return $auth({
3581
+ realm,
3582
+ name: "google",
3583
+ oidc: {
3584
+ issuer: "https://accounts.google.com",
3585
+ clientId: env.GOOGLE_CLIENT_ID,
3586
+ clientSecret: env.GOOGLE_CLIENT_SECRET,
3587
+ ...options
3588
+ }
3589
+ });
3590
+ };
3591
+
3592
+ //#endregion
3593
+ //#region src/auth/errors/SessionExpiredError.ts
3594
+ var SessionExpiredError = class extends alepha.AlephaError {
3595
+ name = "SessionExpiredError";
3596
+ status = 401;
3597
+ };
3598
+
3599
+ //#endregion
3600
+ //#region src/auth/hooks/useAuth.ts
3601
+ const useAuth = () => {
3602
+ const alepha$1 = useAlepha();
3603
+ const [user] = useStore("alepha.server.request.user");
3604
+ return {
3605
+ user,
3606
+ logout: () => {
3607
+ alepha$1.inject(ReactAuth).logout();
3608
+ },
3609
+ login: async (provider, options = {}) => {
3610
+ await alepha$1.inject(ReactAuth).login(provider, options);
3611
+ },
3612
+ can: (name) => {
3613
+ return alepha$1.inject(alepha_server_links.LinkProvider).can(name);
3614
+ }
3615
+ };
3616
+ };
3617
+
3618
+ //#endregion
3619
+ //#region src/auth/index.ts
3620
+ /**
3621
+ * The ReactAuthModule provides authentication services for React applications.
3622
+ *
3623
+ * @see {@link ReactAuthProvider}
3624
+ * @module alepha.react.auth
3625
+ */
3626
+ const AlephaReactAuth = (0, alepha.$module)({
3627
+ name: "alepha.react.auth",
3628
+ descriptors: [$auth],
3629
+ services: [
3630
+ AlephaReact,
3631
+ alepha_server_cookies.AlephaServerCookies,
3632
+ ReactAuthProvider,
3633
+ ReactAuth
3634
+ ]
3635
+ });
3636
+
3637
+ //#endregion
3638
+ exports.$auth = $auth;
3639
+ exports.$authGithub = $authGithub;
3640
+ exports.$authGoogle = $authGoogle;
3641
+ exports.AlephaReactAuth = AlephaReactAuth;
3642
+ exports.AuthDescriptor = AuthDescriptor;
3643
+ exports.ReactAuth = ReactAuth;
3644
+ exports.ReactAuthProvider = ReactAuthProvider;
3645
+ exports.SessionExpiredError = SessionExpiredError;
3646
+ exports.useAuth = useAuth;
3647
+ //# sourceMappingURL=index.cjs.map