@relax.js/core 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +194 -188
- package/dist/DependencyInjection.d.ts +42 -24
- package/dist/collections/Index.d.ts +2 -0
- package/dist/collections/index.js +1 -1
- package/dist/collections/index.js.map +4 -4
- package/dist/collections/index.mjs +1 -1
- package/dist/collections/index.mjs.map +4 -4
- package/dist/di/index.js +1 -1
- package/dist/di/index.js.map +3 -3
- package/dist/di/index.mjs +1 -1
- package/dist/di/index.mjs.map +3 -3
- package/dist/errors.d.ts +20 -0
- package/dist/forms/FormValidator.d.ts +1 -20
- package/dist/forms/ValidationRules.d.ts +2 -0
- package/dist/forms/index.js +1 -1
- package/dist/forms/index.js.map +4 -4
- package/dist/forms/index.mjs +1 -1
- package/dist/forms/index.mjs.map +4 -4
- package/dist/html/TableRenderer.d.ts +1 -0
- package/dist/html/index.js.map +2 -2
- package/dist/html/index.mjs.map +2 -2
- package/dist/html/template.d.ts +4 -0
- package/dist/http/http.d.ts +1 -0
- package/dist/http/index.js.map +2 -2
- package/dist/http/index.mjs.map +2 -2
- package/dist/index.d.ts +0 -2
- package/dist/index.js +3 -3
- package/dist/index.js.map +4 -4
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +4 -4
- package/dist/routing/index.js +3 -3
- package/dist/routing/index.js.map +3 -3
- package/dist/routing/index.mjs +3 -3
- package/dist/routing/index.mjs.map +3 -3
- package/dist/routing/routeTargetRegistry.d.ts +1 -0
- package/dist/routing/types.d.ts +2 -1
- package/dist/templates/NodeTemplate.d.ts +2 -0
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +2 -2
- package/dist/utils/index.mjs +1 -1
- package/dist/utils/index.mjs.map +2 -2
- package/docs/Architecture.md +333 -333
- package/docs/DependencyInjection.md +277 -237
- package/docs/Errors.md +87 -87
- package/docs/GettingStarted.md +231 -231
- package/docs/Pipes.md +5 -5
- package/docs/Translations.md +167 -312
- package/docs/WhyRelaxjs.md +336 -336
- package/docs/api/.nojekyll +1 -0
- package/docs/api/assets/hierarchy.js +1 -0
- package/docs/api/assets/highlight.css +120 -0
- package/docs/api/assets/icons.js +18 -0
- package/docs/api/assets/icons.svg +1 -0
- package/docs/api/assets/main.js +60 -0
- package/docs/api/assets/navigation.js +1 -0
- package/docs/api/assets/search.js +1 -0
- package/docs/api/assets/style.css +1633 -0
- package/docs/api/classes/http.WebSocketClient.html +26 -0
- package/docs/api/classes/i18n.LocaleChangeEvent.html +66 -0
- package/docs/api/classes/index.Blueprint.html +3 -0
- package/docs/api/classes/index.BoundNode.html +3 -0
- package/docs/api/classes/index.DigitsValidation.html +10 -0
- package/docs/api/classes/index.FormValidator.html +32 -0
- package/docs/api/classes/index.HttpError.html +13 -0
- package/docs/api/classes/index.LinkedList.html +26 -0
- package/docs/api/classes/index.NavigateRouteEvent.html +76 -0
- package/docs/api/classes/index.Node.html +15 -0
- package/docs/api/classes/index.PageSelectedEvent.html +61 -0
- package/docs/api/classes/index.Pager.html +4 -0
- package/docs/api/classes/index.RangeValidation.html +15 -0
- package/docs/api/classes/index.RelaxError.html +17 -0
- package/docs/api/classes/index.RequiredValidation.html +10 -0
- package/docs/api/classes/index.RouteError.html +11 -0
- package/docs/api/classes/index.RouteGuardError.html +12 -0
- package/docs/api/classes/index.RouteLink.html +779 -0
- package/docs/api/classes/index.RouteTarget.html +788 -0
- package/docs/api/classes/index.SSEClient.html +13 -0
- package/docs/api/classes/index.SSEDataEvent.html +63 -0
- package/docs/api/classes/index.ServiceCollection.html +28 -0
- package/docs/api/classes/index.ServiceContainer.html +24 -0
- package/docs/api/classes/index.SortChangeEvent.html +61 -0
- package/docs/api/classes/index.TableRenderer.html +5 -0
- package/docs/api/classes/index.TableSorter.html +4 -0
- package/docs/api/enums/index.GuardResult.html +9 -0
- package/docs/api/functions/elements.formError.html +6 -0
- package/docs/api/functions/elements.selectOne.html +6 -0
- package/docs/api/functions/i18n.getCurrentLocale.html +3 -0
- package/docs/api/functions/i18n.loadNamespace.html +7 -0
- package/docs/api/functions/i18n.loadNamespaces.html +6 -0
- package/docs/api/functions/i18n.onMissingTranslation.html +7 -0
- package/docs/api/functions/i18n.setLocale.html +7 -0
- package/docs/api/functions/i18n.setMessageFormatter.html +7 -0
- package/docs/api/functions/i18n.t.html +9 -0
- package/docs/api/functions/index.BooleanConverter.html +6 -0
- package/docs/api/functions/index.ContainerService.html +13 -0
- package/docs/api/functions/index.DateConverter.html +11 -0
- package/docs/api/functions/index.Inject.html +16 -0
- package/docs/api/functions/index.NumberConverter.html +5 -0
- package/docs/api/functions/index.RegisterValidator.html +7 -0
- package/docs/api/functions/index.applyPipes.html +17 -0
- package/docs/api/functions/index.asyncHandler.html +11 -0
- package/docs/api/functions/index.capitalizePipe.html +4 -0
- package/docs/api/functions/index.clearPendingNavigations.html +1 -0
- package/docs/api/functions/index.compileTemplate.html +26 -0
- package/docs/api/functions/index.configure.html +5 -0
- package/docs/api/functions/index.createBluePrint.html +1 -0
- package/docs/api/functions/index.createConverterFromDataType.html +4 -0
- package/docs/api/functions/index.createConverterFromInputType.html +5 -0
- package/docs/api/functions/index.createPipeRegistry.html +12 -0
- package/docs/api/functions/index.currencyPipe.html +9 -0
- package/docs/api/functions/index.datePipe.html +9 -0
- package/docs/api/functions/index.daysAgoPipe.html +8 -0
- package/docs/api/functions/index.defaultPipe.html +5 -0
- package/docs/api/functions/index.defineRoutes.html +8 -0
- package/docs/api/functions/index.del.html +8 -0
- package/docs/api/functions/index.findRouteByName.html +5 -0
- package/docs/api/functions/index.findRouteByUrl.html +4 -0
- package/docs/api/functions/index.firstPipe.html +4 -0
- package/docs/api/functions/index.generateSequentialId.html +21 -0
- package/docs/api/functions/index.get.html +9 -0
- package/docs/api/functions/index.getDataConverter.html +11 -0
- package/docs/api/functions/index.getParentComponent.html +18 -0
- package/docs/api/functions/index.getValidator.html +4 -0
- package/docs/api/functions/index.html.html +19 -0
- package/docs/api/functions/index.joinPipe.html +5 -0
- package/docs/api/functions/index.keysPipe.html +4 -0
- package/docs/api/functions/index.lastPipe.html +4 -0
- package/docs/api/functions/index.lowercasePipe.html +4 -0
- package/docs/api/functions/index.mapFormToClass.html +17 -0
- package/docs/api/functions/index.matchRoute.html +5 -0
- package/docs/api/functions/index.navigate.html +8 -0
- package/docs/api/functions/index.onError.html +8 -0
- package/docs/api/functions/index.piecesPipe.html +8 -0
- package/docs/api/functions/index.post.html +9 -0
- package/docs/api/functions/index.printRoutes.html +2 -0
- package/docs/api/functions/index.put.html +9 -0
- package/docs/api/functions/index.readData.html +17 -0
- package/docs/api/functions/index.registerRouteTarget.html +9 -0
- package/docs/api/functions/index.reportError.html +10 -0
- package/docs/api/functions/index.request.html +8 -0
- package/docs/api/functions/index.resolveValue.html +18 -0
- package/docs/api/functions/index.setFetch.html +6 -0
- package/docs/api/functions/index.setFormData.html +17 -0
- package/docs/api/functions/index.shortenPipe.html +5 -0
- package/docs/api/functions/index.startRouting.html +6 -0
- package/docs/api/functions/index.ternaryPipe.html +6 -0
- package/docs/api/functions/index.trimPipe.html +4 -0
- package/docs/api/functions/index.unregisterRouteTarget.html +3 -0
- package/docs/api/functions/index.uppercasePipe.html +4 -0
- package/docs/api/hierarchy.html +1 -0
- package/docs/api/index.html +323 -0
- package/docs/api/interfaces/http.SimpleDataEvent.html +3 -0
- package/docs/api/interfaces/http.WebSocketAbstraction.html +9 -0
- package/docs/api/interfaces/http.WebSocketCodec.html +4 -0
- package/docs/api/interfaces/http.WebSocketOptions.html +20 -0
- package/docs/api/interfaces/index.CompiledTemplate.html +10 -0
- package/docs/api/interfaces/index.DataLoader.html +19 -0
- package/docs/api/interfaces/index.EngineConfig.html +11 -0
- package/docs/api/interfaces/index.ErrorContext.html +4 -0
- package/docs/api/interfaces/index.FormReaderOptions.html +8 -0
- package/docs/api/interfaces/index.HttpOptions.html +16 -0
- package/docs/api/interfaces/index.HttpResponse.html +17 -0
- package/docs/api/interfaces/index.LoadRoute.html +7 -0
- package/docs/api/interfaces/index.NavigateOptions.html +7 -0
- package/docs/api/interfaces/index.PipeRegistry.html +12 -0
- package/docs/api/interfaces/index.RegistrationOptions.html +22 -0
- package/docs/api/interfaces/index.RenderTemplate.html +7 -0
- package/docs/api/interfaces/index.RequestOptions.html +11 -0
- package/docs/api/interfaces/index.Routable.html +10 -0
- package/docs/api/interfaces/index.Route.html +13 -0
- package/docs/api/interfaces/index.RouteGuard.html +2 -0
- package/docs/api/interfaces/index.RouteValue.html +6 -0
- package/docs/api/interfaces/index.SSEOptions.html +24 -0
- package/docs/api/interfaces/index.ValidationContext.html +8 -0
- package/docs/api/interfaces/index.ValidatorOptions.html +14 -0
- package/docs/api/media/Architecture.md +333 -0
- package/docs/api/media/DependencyInjection.md +277 -0
- package/docs/api/media/GettingStarted.md +231 -0
- package/docs/api/media/HttpClient.md +459 -0
- package/docs/api/media/Pipes.md +211 -0
- package/docs/api/media/Routing.md +332 -0
- package/docs/api/media/WhyRelaxjs.md +336 -0
- package/docs/api/media/forms.md +99 -0
- package/docs/api/media/html.md +175 -0
- package/docs/api/media/i18n.md +354 -0
- package/docs/api/media/utilities.md +143 -0
- package/docs/api/media/validation.md +351 -0
- package/docs/api/modules/collections_Index.html +1 -0
- package/docs/api/modules/di.html +1 -0
- package/docs/api/modules/elements.html +1 -0
- package/docs/api/modules/forms.html +1 -0
- package/docs/api/modules/html.html +1 -0
- package/docs/api/modules/http.html +1 -0
- package/docs/api/modules/i18n.html +1 -0
- package/docs/api/modules/index.html +1 -0
- package/docs/api/modules/routing.html +1 -0
- package/docs/api/modules/utils.html +1 -0
- package/docs/api/modules.html +1 -0
- package/docs/api/types/http.WebSocketFactory.html +2 -0
- package/docs/api/types/i18n.MessageFormatter.html +3 -0
- package/docs/api/types/i18n.MissingTranslationHandler.html +1 -0
- package/docs/api/types/index.Constructor.html +7 -0
- package/docs/api/types/index.ConverterFunc.html +2 -0
- package/docs/api/types/index.DataType.html +2 -0
- package/docs/api/types/index.InputType.html +2 -0
- package/docs/api/types/index.PipeFunction.html +6 -0
- package/docs/api/types/index.RouteData.html +1 -0
- package/docs/api/types/index.RouteMatchResult.html +9 -0
- package/docs/api/types/index.RouteParamType.html +1 -0
- package/docs/api/types/index.RouteSegmentType.html +2 -0
- package/docs/api/types/index.SSEEventFactory.html +5 -0
- package/docs/api/types/index.ServiceScope.html +10 -0
- package/docs/api/types/index.SortColumn.html +3 -0
- package/docs/api/variables/i18n.formatICU.html +3 -0
- package/docs/api/variables/index.container.html +6 -0
- package/docs/api/variables/index.defaultPipes.html +6 -0
- package/docs/api/variables/index.internalRoutes.html +1 -0
- package/docs/api/variables/index.serviceCollection.html +6 -0
- package/docs/api.json +93171 -0
- package/docs/elements/dom.md +102 -102
- package/docs/forms/creating-form-components.md +924 -924
- package/docs/forms/form-api.md +94 -94
- package/docs/forms/forms.md +99 -99
- package/docs/forms/patterns.md +311 -311
- package/docs/forms/reading-writing.md +365 -365
- package/docs/forms/validation.md +351 -351
- package/docs/html/TableRenderer.md +291 -291
- package/docs/html/html.md +175 -175
- package/docs/html/index.md +54 -54
- package/docs/html/template.md +422 -422
- package/docs/http/HttpClient.md +459 -459
- package/docs/http/ServerSentEvents.md +184 -184
- package/docs/http/index.md +109 -109
- package/docs/i18n/i18n.md +49 -4
- package/docs/i18n/intl-standard.md +178 -178
- package/docs/routing/RouteLink.md +98 -98
- package/docs/routing/Routing.md +332 -332
- package/docs/routing/layouts.md +207 -207
- package/docs/utilities.md +143 -143
- package/package.json +4 -3
package/docs/routing/layouts.md
CHANGED
|
@@ -1,207 +1,207 @@
|
|
|
1
|
-
Layouts
|
|
2
|
-
=======
|
|
3
|
-
|
|
4
|
-
Layouts allow different HTML pages to serve as shells for different parts of your application. Common use cases:
|
|
5
|
-
|
|
6
|
-
- Public pages (login, register) with minimal UI
|
|
7
|
-
- Authenticated pages with navigation, sidebar, header
|
|
8
|
-
- Admin pages with different structure
|
|
9
|
-
|
|
10
|
-
## How It Works
|
|
11
|
-
|
|
12
|
-
Each layout is a separate static HTML file. When navigating to a route with a different layout than the current one, the router:
|
|
13
|
-
|
|
14
|
-
1. Stores the target route name and params in `sessionStorage`
|
|
15
|
-
2. Redirects the browser to the new HTML file
|
|
16
|
-
3. On the new page, `startRouting()` reads from `sessionStorage` and navigates to the intended route
|
|
17
|
-
4. The URL is updated to show the route path (not the `.html` file)
|
|
18
|
-
|
|
19
|
-
## File Structure
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
/index.html <- default layout (no layout property needed)
|
|
23
|
-
/public.html <- public layout
|
|
24
|
-
/admin.html <- admin layout
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Route Configuration
|
|
28
|
-
|
|
29
|
-
```ts
|
|
30
|
-
import { Route, defineRoutes, startRouting } from '
|
|
31
|
-
|
|
32
|
-
const routes: Route[] = [
|
|
33
|
-
// Uses public.html
|
|
34
|
-
{ name: 'login', path: '/login', componentTagName: 'login-page', layout: 'public' },
|
|
35
|
-
{ name: 'register', path: '/register', componentTagName: 'register-page', layout: 'public' },
|
|
36
|
-
|
|
37
|
-
// Uses index.html (default - no layout property needed)
|
|
38
|
-
{ name: 'home', path: '/', componentTagName: 'home-page' },
|
|
39
|
-
{ name: 'settings', path: '/settings', componentTagName: 'settings-page' },
|
|
40
|
-
|
|
41
|
-
// Uses admin.html
|
|
42
|
-
{ name: 'admin', path: '/admin', componentTagName: 'admin-dashboard', layout: 'admin' },
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
defineRoutes(routes);
|
|
46
|
-
startRouting();
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Layout HTML Files
|
|
50
|
-
|
|
51
|
-
Each layout file must:
|
|
52
|
-
- Include `<r-route-target>` where routed components render
|
|
53
|
-
- Import and call `defineRoutes()` and `startRouting()`
|
|
54
|
-
- Register all components that may be rendered in that layout
|
|
55
|
-
|
|
56
|
-
### index.html (default layout)
|
|
57
|
-
|
|
58
|
-
```html
|
|
59
|
-
<!DOCTYPE html>
|
|
60
|
-
<html>
|
|
61
|
-
<head>
|
|
62
|
-
<title>My App</title>
|
|
63
|
-
<script type="module" src="/src/app.ts"></script>
|
|
64
|
-
</head>
|
|
65
|
-
<body>
|
|
66
|
-
<app-header></app-header>
|
|
67
|
-
<app-sidebar></app-sidebar>
|
|
68
|
-
<main>
|
|
69
|
-
<r-route-target></r-route-target>
|
|
70
|
-
</main>
|
|
71
|
-
</body>
|
|
72
|
-
</html>
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### public.html
|
|
76
|
-
|
|
77
|
-
```html
|
|
78
|
-
<!DOCTYPE html>
|
|
79
|
-
<html>
|
|
80
|
-
<head>
|
|
81
|
-
<title>My App - Login</title>
|
|
82
|
-
<script type="module" src="/src/app.ts"></script>
|
|
83
|
-
</head>
|
|
84
|
-
<body>
|
|
85
|
-
<div class="auth-container">
|
|
86
|
-
<r-route-target></r-route-target>
|
|
87
|
-
</div>
|
|
88
|
-
</body>
|
|
89
|
-
</html>
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Layout Detection
|
|
93
|
-
|
|
94
|
-
The router determines the current layout from the URL:
|
|
95
|
-
|
|
96
|
-
| URL | Detected Layout |
|
|
97
|
-
|-----|-----------------|
|
|
98
|
-
| `/index.html` | `default` |
|
|
99
|
-
| `/public.html` | `public` |
|
|
100
|
-
| `/admin.html` | `admin` |
|
|
101
|
-
| `/` or `/settings` | `default` (inferred) |
|
|
102
|
-
|
|
103
|
-
## Navigation Flow
|
|
104
|
-
|
|
105
|
-
When a user on `/settings` (default layout) navigates to `login` (public layout):
|
|
106
|
-
|
|
107
|
-
1. `navigate('login')` is called
|
|
108
|
-
2. Router detects layout mismatch: `default` !== `public`
|
|
109
|
-
3. Stores `{ routeName: 'login', params: {} }` in `sessionStorage`
|
|
110
|
-
4. Redirects browser to `/public.html#layout`
|
|
111
|
-
5. `public.html` loads and calls `startRouting()`
|
|
112
|
-
6. `startRouting()` reads from `sessionStorage`, calls `navigate('login')`
|
|
113
|
-
7. URL updates to `/login`, `login-page` component renders
|
|
114
|
-
|
|
115
|
-
## Shared Routes Configuration
|
|
116
|
-
|
|
117
|
-
Since all layout files need the same routes, keep route configuration in a shared module:
|
|
118
|
-
|
|
119
|
-
```ts
|
|
120
|
-
// routes.ts
|
|
121
|
-
import { Route } from '
|
|
122
|
-
|
|
123
|
-
export const routes: Route[] = [
|
|
124
|
-
{ name: 'login', path: '/login', componentTagName: 'login-page', layout: 'public' },
|
|
125
|
-
{ name: 'home', path: '/', componentTagName: 'home-page' },
|
|
126
|
-
// ...
|
|
127
|
-
];
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
```ts
|
|
131
|
-
// app.ts (imported by all layout HTML files)
|
|
132
|
-
import { defineRoutes, startRouting } from '
|
|
133
|
-
import { routes } from './routes';
|
|
134
|
-
|
|
135
|
-
defineRoutes(routes);
|
|
136
|
-
startRouting();
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
## Error Handling
|
|
140
|
-
|
|
141
|
-
If a layout redirect fails (e.g., the HTML file doesn't exist), the router throws an error:
|
|
142
|
-
|
|
143
|
-
```
|
|
144
|
-
A redirect failed, does the requested layout exist? "admin"?
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
The `#layout` hash in the redirect URL is used to detect this: if the hash is still present after redirect, something went wrong.
|
|
148
|
-
|
|
149
|
-
## Security Considerations
|
|
150
|
-
|
|
151
|
-
Layout files are static HTML served to the browser. This has security implications:
|
|
152
|
-
|
|
153
|
-
### Route Exposure
|
|
154
|
-
|
|
155
|
-
A file like `admin.html` contains (or imports) route configurations that reveal admin URLs. Anyone requesting the file can see what endpoints exist, even without authentication.
|
|
156
|
-
|
|
157
|
-
### Client-Side Guards Are Not Security
|
|
158
|
-
|
|
159
|
-
Route guards in Relaxjs prevent components from rendering, but they don't protect data. An attacker can:
|
|
160
|
-
- Read the JavaScript bundle to find all routes
|
|
161
|
-
- Call backend APIs directly, bypassing the UI entirely
|
|
162
|
-
|
|
163
|
-
**The real security boundary is always your backend API.** Every API endpoint must validate authentication and authorization.
|
|
164
|
-
|
|
165
|
-
### Server-Side Protection
|
|
166
|
-
|
|
167
|
-
For defense in depth, protect sensitive layout files at the HTTP server level:
|
|
168
|
-
|
|
169
|
-
**Nginx:**
|
|
170
|
-
```nginx
|
|
171
|
-
location /admin.html {
|
|
172
|
-
auth_request /auth/verify;
|
|
173
|
-
error_page 401 = @unauthorized;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
location @unauthorized {
|
|
177
|
-
return 302 /login;
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
**Express:**
|
|
182
|
-
```ts
|
|
183
|
-
app.get('/admin.html', verifyJwt, (req, res) => {
|
|
184
|
-
res.sendFile('admin.html');
|
|
185
|
-
});
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
**Caddy:**
|
|
189
|
-
```
|
|
190
|
-
admin.html {
|
|
191
|
-
forward_auth /auth/verify
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### Alternatives
|
|
196
|
-
|
|
197
|
-
If hiding routes is important:
|
|
198
|
-
|
|
199
|
-
1. **Single layout with dynamic navigation**: Load admin navigation only after auth verification
|
|
200
|
-
2. **Separate builds**: Build different bundles for public/authenticated/admin with different route configurations
|
|
201
|
-
3. **Server-rendered shells**: Generate layout HTML server-side based on user role
|
|
202
|
-
|
|
203
|
-
### Recommendation
|
|
204
|
-
|
|
205
|
-
1. Always secure your backend APIs with proper authentication/authorization
|
|
206
|
-
2. Protect sensitive layout files at the server level in production
|
|
207
|
-
3. Don't rely on client-side guards for security. They're for UX, not protection
|
|
1
|
+
Layouts
|
|
2
|
+
=======
|
|
3
|
+
|
|
4
|
+
Layouts allow different HTML pages to serve as shells for different parts of your application. Common use cases:
|
|
5
|
+
|
|
6
|
+
- Public pages (login, register) with minimal UI
|
|
7
|
+
- Authenticated pages with navigation, sidebar, header
|
|
8
|
+
- Admin pages with different structure
|
|
9
|
+
|
|
10
|
+
## How It Works
|
|
11
|
+
|
|
12
|
+
Each layout is a separate static HTML file. When navigating to a route with a different layout than the current one, the router:
|
|
13
|
+
|
|
14
|
+
1. Stores the target route name and params in `sessionStorage`
|
|
15
|
+
2. Redirects the browser to the new HTML file
|
|
16
|
+
3. On the new page, `startRouting()` reads from `sessionStorage` and navigates to the intended route
|
|
17
|
+
4. The URL is updated to show the route path (not the `.html` file)
|
|
18
|
+
|
|
19
|
+
## File Structure
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
/index.html <- default layout (no layout property needed)
|
|
23
|
+
/public.html <- public layout
|
|
24
|
+
/admin.html <- admin layout
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Route Configuration
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { Route, defineRoutes, startRouting } from '@relax.js/core/routing';
|
|
31
|
+
|
|
32
|
+
const routes: Route[] = [
|
|
33
|
+
// Uses public.html
|
|
34
|
+
{ name: 'login', path: '/login', componentTagName: 'login-page', layout: 'public' },
|
|
35
|
+
{ name: 'register', path: '/register', componentTagName: 'register-page', layout: 'public' },
|
|
36
|
+
|
|
37
|
+
// Uses index.html (default - no layout property needed)
|
|
38
|
+
{ name: 'home', path: '/', componentTagName: 'home-page' },
|
|
39
|
+
{ name: 'settings', path: '/settings', componentTagName: 'settings-page' },
|
|
40
|
+
|
|
41
|
+
// Uses admin.html
|
|
42
|
+
{ name: 'admin', path: '/admin', componentTagName: 'admin-dashboard', layout: 'admin' },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
defineRoutes(routes);
|
|
46
|
+
startRouting();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Layout HTML Files
|
|
50
|
+
|
|
51
|
+
Each layout file must:
|
|
52
|
+
- Include `<r-route-target>` where routed components render
|
|
53
|
+
- Import and call `defineRoutes()` and `startRouting()`
|
|
54
|
+
- Register all components that may be rendered in that layout
|
|
55
|
+
|
|
56
|
+
### index.html (default layout)
|
|
57
|
+
|
|
58
|
+
```html
|
|
59
|
+
<!DOCTYPE html>
|
|
60
|
+
<html>
|
|
61
|
+
<head>
|
|
62
|
+
<title>My App</title>
|
|
63
|
+
<script type="module" src="/src/app.ts"></script>
|
|
64
|
+
</head>
|
|
65
|
+
<body>
|
|
66
|
+
<app-header></app-header>
|
|
67
|
+
<app-sidebar></app-sidebar>
|
|
68
|
+
<main>
|
|
69
|
+
<r-route-target></r-route-target>
|
|
70
|
+
</main>
|
|
71
|
+
</body>
|
|
72
|
+
</html>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### public.html
|
|
76
|
+
|
|
77
|
+
```html
|
|
78
|
+
<!DOCTYPE html>
|
|
79
|
+
<html>
|
|
80
|
+
<head>
|
|
81
|
+
<title>My App - Login</title>
|
|
82
|
+
<script type="module" src="/src/app.ts"></script>
|
|
83
|
+
</head>
|
|
84
|
+
<body>
|
|
85
|
+
<div class="auth-container">
|
|
86
|
+
<r-route-target></r-route-target>
|
|
87
|
+
</div>
|
|
88
|
+
</body>
|
|
89
|
+
</html>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Layout Detection
|
|
93
|
+
|
|
94
|
+
The router determines the current layout from the URL:
|
|
95
|
+
|
|
96
|
+
| URL | Detected Layout |
|
|
97
|
+
|-----|-----------------|
|
|
98
|
+
| `/index.html` | `default` |
|
|
99
|
+
| `/public.html` | `public` |
|
|
100
|
+
| `/admin.html` | `admin` |
|
|
101
|
+
| `/` or `/settings` | `default` (inferred) |
|
|
102
|
+
|
|
103
|
+
## Navigation Flow
|
|
104
|
+
|
|
105
|
+
When a user on `/settings` (default layout) navigates to `login` (public layout):
|
|
106
|
+
|
|
107
|
+
1. `navigate('login')` is called
|
|
108
|
+
2. Router detects layout mismatch: `default` !== `public`
|
|
109
|
+
3. Stores `{ routeName: 'login', params: {} }` in `sessionStorage`
|
|
110
|
+
4. Redirects browser to `/public.html#layout`
|
|
111
|
+
5. `public.html` loads and calls `startRouting()`
|
|
112
|
+
6. `startRouting()` reads from `sessionStorage`, calls `navigate('login')`
|
|
113
|
+
7. URL updates to `/login`, `login-page` component renders
|
|
114
|
+
|
|
115
|
+
## Shared Routes Configuration
|
|
116
|
+
|
|
117
|
+
Since all layout files need the same routes, keep route configuration in a shared module:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
// routes.ts
|
|
121
|
+
import { Route } from '@relax.js/core/routing';
|
|
122
|
+
|
|
123
|
+
export const routes: Route[] = [
|
|
124
|
+
{ name: 'login', path: '/login', componentTagName: 'login-page', layout: 'public' },
|
|
125
|
+
{ name: 'home', path: '/', componentTagName: 'home-page' },
|
|
126
|
+
// ...
|
|
127
|
+
];
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
// app.ts (imported by all layout HTML files)
|
|
132
|
+
import { defineRoutes, startRouting } from '@relax.js/core/routing';
|
|
133
|
+
import { routes } from './routes';
|
|
134
|
+
|
|
135
|
+
defineRoutes(routes);
|
|
136
|
+
startRouting();
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Error Handling
|
|
140
|
+
|
|
141
|
+
If a layout redirect fails (e.g., the HTML file doesn't exist), the router throws an error:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
A redirect failed, does the requested layout exist? "admin"?
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The `#layout` hash in the redirect URL is used to detect this: if the hash is still present after redirect, something went wrong.
|
|
148
|
+
|
|
149
|
+
## Security Considerations
|
|
150
|
+
|
|
151
|
+
Layout files are static HTML served to the browser. This has security implications:
|
|
152
|
+
|
|
153
|
+
### Route Exposure
|
|
154
|
+
|
|
155
|
+
A file like `admin.html` contains (or imports) route configurations that reveal admin URLs. Anyone requesting the file can see what endpoints exist, even without authentication.
|
|
156
|
+
|
|
157
|
+
### Client-Side Guards Are Not Security
|
|
158
|
+
|
|
159
|
+
Route guards in Relaxjs prevent components from rendering, but they don't protect data. An attacker can:
|
|
160
|
+
- Read the JavaScript bundle to find all routes
|
|
161
|
+
- Call backend APIs directly, bypassing the UI entirely
|
|
162
|
+
|
|
163
|
+
**The real security boundary is always your backend API.** Every API endpoint must validate authentication and authorization.
|
|
164
|
+
|
|
165
|
+
### Server-Side Protection
|
|
166
|
+
|
|
167
|
+
For defense in depth, protect sensitive layout files at the HTTP server level:
|
|
168
|
+
|
|
169
|
+
**Nginx:**
|
|
170
|
+
```nginx
|
|
171
|
+
location /admin.html {
|
|
172
|
+
auth_request /auth/verify;
|
|
173
|
+
error_page 401 = @unauthorized;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
location @unauthorized {
|
|
177
|
+
return 302 /login;
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Express:**
|
|
182
|
+
```ts
|
|
183
|
+
app.get('/admin.html', verifyJwt, (req, res) => {
|
|
184
|
+
res.sendFile('admin.html');
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Caddy:**
|
|
189
|
+
```
|
|
190
|
+
admin.html {
|
|
191
|
+
forward_auth /auth/verify
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Alternatives
|
|
196
|
+
|
|
197
|
+
If hiding routes is important:
|
|
198
|
+
|
|
199
|
+
1. **Single layout with dynamic navigation**: Load admin navigation only after auth verification
|
|
200
|
+
2. **Separate builds**: Build different bundles for public/authenticated/admin with different route configurations
|
|
201
|
+
3. **Server-rendered shells**: Generate layout HTML server-side based on user role
|
|
202
|
+
|
|
203
|
+
### Recommendation
|
|
204
|
+
|
|
205
|
+
1. Always secure your backend APIs with proper authentication/authorization
|
|
206
|
+
2. Protect sensitive layout files at the server level in production
|
|
207
|
+
3. Don't rely on client-side guards for security. They're for UX, not protection
|