odac 0.9.0 → 1.0.0
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/.github/workflows/auto-pr-description.yml +0 -2
- package/.github/workflows/codeql.yml +46 -0
- package/.github/workflows/release.yml +13 -6
- package/.github/workflows/test-coverage.yml +10 -9
- package/.releaserc.js +9 -6
- package/CHANGELOG.md +62 -150
- package/CODE_OF_CONDUCT.md +1 -1
- package/CONTRIBUTING.md +8 -8
- package/LICENSE +21 -661
- package/README.md +12 -12
- package/SECURITY.md +4 -4
- package/bin/odac.js +101 -0
- package/{framework/web/candy.js → client/odac.js} +310 -44
- package/docs/backend/01-overview/{01-whats-in-the-candy-box.md → 01-whats-in-the-odac-box.md} +4 -2
- package/docs/backend/01-overview/02-super-handy-helper-functions.md +29 -1
- package/docs/backend/01-overview/03-development-server.md +11 -11
- package/docs/backend/02-structure/01-typical-project-layout.md +4 -4
- package/docs/backend/03-config/00-configuration-overview.md +6 -6
- package/docs/backend/03-config/01-database-connection.md +1 -1
- package/docs/backend/03-config/02-static-route-mapping-optional.md +4 -4
- package/docs/backend/03-config/04-environment-variables.md +20 -20
- package/docs/backend/03-config/05-early-hints.md +4 -4
- package/docs/backend/04-routing/01-basic-page-routes.md +4 -4
- package/docs/backend/04-routing/02-controller-less-view-routes.md +5 -5
- package/docs/backend/04-routing/03-api-and-data-routes.md +3 -3
- package/docs/backend/04-routing/04-authentication-aware-routes.md +5 -5
- package/docs/backend/04-routing/05-advanced-routing.md +3 -3
- package/docs/backend/04-routing/06-error-pages.md +17 -17
- package/docs/backend/04-routing/07-cron-jobs.md +13 -13
- package/docs/backend/04-routing/08-middleware.md +214 -0
- package/docs/backend/04-routing/09-websocket-auth-middleware.md +292 -0
- package/docs/backend/04-routing/09-websocket-examples.md +381 -0
- package/docs/backend/04-routing/09-websocket-quick-reference.md +211 -0
- package/docs/backend/04-routing/09-websocket.md +298 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +3 -3
- package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +41 -0
- package/docs/backend/05-controllers/03-controller-classes.md +19 -19
- package/docs/backend/05-forms/01-custom-forms.md +114 -114
- package/docs/backend/05-forms/02-automatic-database-insert.md +82 -82
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +26 -26
- package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +10 -10
- package/docs/backend/07-views/01-the-view-directory.md +1 -1
- package/docs/backend/07-views/02-rendering-a-view.md +22 -22
- package/docs/backend/07-views/03-template-syntax.md +52 -52
- package/docs/backend/07-views/03-variables.md +84 -84
- package/docs/backend/07-views/04-request-data.md +57 -57
- package/docs/backend/07-views/05-conditionals.md +78 -78
- package/docs/backend/07-views/06-loops.md +114 -114
- package/docs/backend/07-views/07-translations.md +66 -66
- package/docs/backend/07-views/08-backend-javascript.md +103 -103
- package/docs/backend/07-views/09-comments.md +71 -71
- package/docs/backend/08-database/01-database-connection.md +8 -8
- package/docs/backend/08-database/02-using-mysql.md +49 -49
- package/docs/backend/09-validation/01-the-validator-service.md +38 -38
- package/docs/backend/10-authentication/01-user-logins-with-authjs.md +15 -15
- package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +10 -10
- package/docs/backend/10-authentication/03-register.md +12 -12
- package/docs/backend/10-authentication/{04-candy-register-forms.md → 04-odac-register-forms.md} +141 -141
- package/docs/backend/10-authentication/05-session-management.md +10 -10
- package/docs/backend/10-authentication/{06-candy-login-forms.md → 06-odac-login-forms.md} +125 -125
- package/docs/backend/11-mail/01-the-mail-service.md +5 -5
- package/docs/backend/12-streaming/01-streaming-overview.md +96 -54
- package/docs/backend/13-utilities/{01-candy-var.md → 01-odac-var.md} +109 -109
- package/docs/frontend/01-overview/01-introduction.md +30 -30
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +45 -45
- package/docs/frontend/02-ajax-navigation/02-configuration.md +14 -14
- package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +36 -36
- package/docs/frontend/03-forms/01-form-handling.md +32 -32
- package/docs/frontend/04-api-requests/01-get-post.md +33 -33
- package/docs/frontend/05-streaming/01-client-streaming.md +15 -15
- package/docs/frontend/06-websocket/00-overview.md +76 -0
- package/docs/frontend/06-websocket/01-websocket-client.md +139 -0
- package/docs/frontend/06-websocket/02-shared-websocket.md +149 -0
- package/docs/index.json +49 -11
- package/eslint.config.mjs +6 -6
- package/{framework/index.js → index.js} +1 -1
- package/package.json +14 -39
- package/{framework/src → src}/Auth.js +59 -59
- package/{framework/src → src}/Config.js +3 -3
- package/{framework/src → src}/Lang.js +7 -7
- package/{framework/src → src}/Mail.js +5 -5
- package/{framework/src → src}/Mysql.js +42 -42
- package/src/Odac.js +112 -0
- package/{framework/src → src}/Request.js +38 -36
- package/{framework/src → src}/Route/Internal.js +116 -116
- package/src/Route/Middleware.js +75 -0
- package/src/Route.js +621 -0
- package/src/Server.js +22 -0
- package/{framework/src → src}/Stream.js +11 -3
- package/{framework/src → src}/Validator.js +21 -21
- package/{framework/src → src}/Var.js +5 -5
- package/{framework/src → src}/View/EarlyHints.js +1 -1
- package/{framework/src → src}/View/Form.js +69 -69
- package/{framework/src → src}/View.js +78 -81
- package/src/WebSocket.js +403 -0
- package/template/config.json +5 -0
- package/{web → template}/controller/page/about.js +6 -6
- package/{web → template}/controller/page/index.js +9 -9
- package/{web → template}/package.json +4 -5
- package/{web → template}/public/assets/css/style.css +4 -4
- package/{web → template}/public/assets/js/app.js +6 -6
- package/{web → template}/route/www.js +6 -6
- package/{web → template}/skeleton/main.html +1 -1
- package/{web → template}/view/content/about.html +5 -5
- package/{web → template}/view/content/home.html +12 -12
- package/template/view/footer/main.html +11 -0
- package/{web → template}/view/head/main.html +1 -1
- package/{web → template}/view/header/main.html +2 -2
- package/test/core/Candy.test.js +58 -58
- package/test/core/Commands.test.js +7 -7
- package/test/core/Config.test.js +82 -85
- package/test/core/Lang.test.js +2 -2
- package/test/core/Process.test.js +6 -6
- package/test/framework/Route.test.js +56 -37
- package/test/framework/View/EarlyHints.test.js +2 -2
- package/test/framework/WebSocket.test.js +100 -0
- package/test/framework/middleware.test.js +85 -0
- package/test/server/Api.test.js +31 -31
- package/test/server/DNS.test.js +11 -11
- package/test/server/Hub.test.js +497 -0
- package/test/server/Mail.account.test_.js +3 -3
- package/test/server/Mail.init.test_.js +10 -10
- package/test/server/Mail.test_.js +20 -20
- package/test/server/SSL.test_.js +54 -54
- package/test/server/Server.test.js +39 -39
- package/test/server/Service.test_.js +7 -7
- package/test/server/Subdomain.test.js +7 -7
- package/test/server/Web/Firewall.test.js +87 -87
- package/test/server/Web/Proxy.test.js +397 -0
- package/test/server/{Web.test_.js → Web.test.js} +137 -205
- package/test/server/__mocks__/fs.js +2 -2
- package/test/server/__mocks__/{globalCandy.js → globalOdac.js} +5 -5
- package/test/server/__mocks__/index.js +6 -6
- package/test/server/__mocks__/testFactories.js +1 -1
- package/test/server/__mocks__/testHelpers.js +7 -7
- package/.husky/pre-commit +0 -2
- package/.kiro/steering/code-style.md +0 -56
- package/.kiro/steering/product.md +0 -20
- package/.kiro/steering/structure.md +0 -77
- package/.kiro/steering/tech.md +0 -87
- package/AGENTS.md +0 -84
- package/bin/candy +0 -10
- package/bin/candypack +0 -10
- package/cli/index.js +0 -3
- package/cli/src/Cli.js +0 -348
- package/cli/src/Connector.js +0 -93
- package/cli/src/Monitor.js +0 -416
- package/core/Candy.js +0 -87
- package/core/Commands.js +0 -239
- package/core/Config.js +0 -1094
- package/core/Lang.js +0 -52
- package/core/Log.js +0 -43
- package/core/Process.js +0 -26
- package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +0 -20
- package/docs/server/01-installation/01-quick-install.md +0 -19
- package/docs/server/01-installation/02-manual-installation-via-npm.md +0 -9
- package/docs/server/02-get-started/01-core-concepts.md +0 -7
- package/docs/server/02-get-started/02-basic-commands.md +0 -57
- package/docs/server/02-get-started/03-cli-reference.md +0 -276
- package/docs/server/02-get-started/04-cli-quick-reference.md +0 -102
- package/docs/server/03-service/01-start-a-new-service.md +0 -57
- package/docs/server/03-service/02-delete-a-service.md +0 -48
- package/docs/server/04-web/01-create-a-website.md +0 -36
- package/docs/server/04-web/02-list-websites.md +0 -9
- package/docs/server/04-web/03-delete-a-website.md +0 -29
- package/docs/server/05-subdomain/01-create-a-subdomain.md +0 -32
- package/docs/server/05-subdomain/02-list-subdomains.md +0 -33
- package/docs/server/05-subdomain/03-delete-a-subdomain.md +0 -41
- package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +0 -34
- package/docs/server/07-mail/01-create-a-mail-account.md +0 -23
- package/docs/server/07-mail/02-delete-a-mail-account.md +0 -20
- package/docs/server/07-mail/03-list-mail-accounts.md +0 -20
- package/docs/server/07-mail/04-change-account-password.md +0 -23
- package/framework/src/Candy.js +0 -81
- package/framework/src/Route.js +0 -455
- package/framework/src/Server.js +0 -15
- package/locale/de-DE.json +0 -80
- package/locale/en-US.json +0 -79
- package/locale/es-ES.json +0 -80
- package/locale/fr-FR.json +0 -80
- package/locale/pt-BR.json +0 -80
- package/locale/ru-RU.json +0 -80
- package/locale/tr-TR.json +0 -85
- package/locale/zh-CN.json +0 -80
- package/server/index.js +0 -5
- package/server/src/Api.js +0 -88
- package/server/src/DNS.js +0 -940
- package/server/src/Hub.js +0 -535
- package/server/src/Mail.js +0 -571
- package/server/src/SSL.js +0 -180
- package/server/src/Server.js +0 -27
- package/server/src/Service.js +0 -248
- package/server/src/Subdomain.js +0 -64
- package/server/src/Web/Firewall.js +0 -170
- package/server/src/Web/Proxy.js +0 -134
- package/server/src/Web.js +0 -451
- package/server/src/mail/imap.js +0 -1091
- package/server/src/mail/server.js +0 -32
- package/server/src/mail/smtp.js +0 -786
- package/test/server/Client.test.js +0 -338
- package/test/server/__mocks__/http-proxy.js +0 -105
- package/watchdog/index.js +0 -3
- package/watchdog/src/Watchdog.js +0 -156
- package/web/config.json +0 -5
- package/web/view/footer/main.html +0 -11
- /package/{framework/src → src}/Env.js +0 -0
- /package/{framework/src → src}/Route/Cron.js +0 -0
- /package/{framework/src → src}/Token.js +0 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# WebSocket Routes
|
|
2
|
+
|
|
3
|
+
Odac provides built-in WebSocket support for real-time bidirectional communication.
|
|
4
|
+
|
|
5
|
+
## Route Definition
|
|
6
|
+
|
|
7
|
+
WebSocket routes are defined in your route files (e.g., `route/www.js` or `route/websocket.js`) using `Odac.Route.ws()`:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
// route/websocket.js
|
|
11
|
+
Odac.Route.ws('/chat', Odac => {
|
|
12
|
+
Odac.ws.send({type: 'welcome', message: 'Connected!'})
|
|
13
|
+
|
|
14
|
+
Odac.ws.on('message', data => {
|
|
15
|
+
console.log('Received:', data)
|
|
16
|
+
Odac.ws.send({type: 'echo', data})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
Odac.ws.on('close', () => {
|
|
20
|
+
console.log('Client disconnected')
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Handler Signature:**
|
|
26
|
+
|
|
27
|
+
The handler receives the `Odac` instance as the only parameter. The WebSocket client is accessible via `Odac.ws`, providing a consistent API with HTTP routes where everything is accessed through the `Odac` object.
|
|
28
|
+
|
|
29
|
+
**CSRF Token Protection:**
|
|
30
|
+
|
|
31
|
+
By default, WebSocket routes require a valid CSRF token (like `Route.get()` and `Route.post()`). The token is sent via the `Sec-WebSocket-Protocol` header during the initial handshake.
|
|
32
|
+
|
|
33
|
+
**Disable token requirement:**
|
|
34
|
+
```javascript
|
|
35
|
+
Odac.Route.ws('/public', Odac => {
|
|
36
|
+
Odac.ws.send({type: 'public'})
|
|
37
|
+
}, {token: false})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Route File Structure:**
|
|
41
|
+
```
|
|
42
|
+
web/
|
|
43
|
+
├── route/
|
|
44
|
+
│ ├── www.js # HTTP routes
|
|
45
|
+
│ └── websocket.js # WebSocket routes (recommended)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## WebSocket Client API (Odac.ws)
|
|
49
|
+
|
|
50
|
+
The WebSocket client is accessible via `Odac.ws` in your handler, providing a consistent API pattern with HTTP routes.
|
|
51
|
+
|
|
52
|
+
### Sending Messages
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
Odac.ws.send({type: 'message', text: 'Hello'}) // JSON object
|
|
56
|
+
Odac.ws.send('Plain text message') // String
|
|
57
|
+
Odac.ws.sendBinary(buffer) // Binary data
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Event Handlers
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
Odac.ws.on('message', data => {}) // Incoming message
|
|
64
|
+
Odac.ws.on('close', () => {}) // Connection closed
|
|
65
|
+
Odac.ws.on('error', err => {}) // Error occurred
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Connection Management
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
Odac.ws.close() // Close connection
|
|
72
|
+
Odac.ws.ping() // Send ping frame
|
|
73
|
+
Odac.ws.id // Unique client ID
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Rooms
|
|
77
|
+
|
|
78
|
+
Group clients into rooms for targeted broadcasting:
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
Odac.Route.ws('/game', Odac => {
|
|
82
|
+
const roomId = Odac.Request.data.url.room || 'lobby'
|
|
83
|
+
|
|
84
|
+
Odac.ws.join(roomId)
|
|
85
|
+
|
|
86
|
+
Odac.ws.on('message', data => {
|
|
87
|
+
Odac.ws.to(roomId).send({
|
|
88
|
+
type: 'chat',
|
|
89
|
+
message: data.message
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
Odac.ws.on('close', () => {
|
|
94
|
+
Odac.ws.leave(roomId)
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Broadcasting
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
// Send to all clients except sender
|
|
103
|
+
Odac.ws.broadcast({type: 'notification', text: 'New user joined'})
|
|
104
|
+
|
|
105
|
+
// Send to all clients in a room
|
|
106
|
+
Odac.ws.to('room-name').send({type: 'update', data: {}})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## URL Parameters
|
|
110
|
+
|
|
111
|
+
WebSocket routes support dynamic parameters:
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
Odac.Route.ws('/room/{roomId}/user/{userId}', Odac => {
|
|
115
|
+
const {roomId, userId} = Odac.Request.data.url
|
|
116
|
+
|
|
117
|
+
Odac.ws.join(roomId)
|
|
118
|
+
Odac.ws.data.userId = userId
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Authentication
|
|
123
|
+
|
|
124
|
+
### Manual Authentication Check
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
Odac.Route.ws('/secure', async Odac => {
|
|
128
|
+
const isAuthenticated = await Odac.Auth.check()
|
|
129
|
+
|
|
130
|
+
if (!isAuthenticated) {
|
|
131
|
+
Odac.ws.close(4001, 'Unauthorized')
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const user = await Odac.Auth.user()
|
|
136
|
+
Odac.ws.data.user = user
|
|
137
|
+
})
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Using auth.ws() (Recommended)
|
|
141
|
+
|
|
142
|
+
Automatically requires authentication (also requires token by default):
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
Odac.Route.auth.ws('/secure', async Odac => {
|
|
146
|
+
const user = await Odac.Auth.user()
|
|
147
|
+
Odac.ws.data.user = user
|
|
148
|
+
|
|
149
|
+
Odac.ws.send({
|
|
150
|
+
type: 'welcome',
|
|
151
|
+
user: user.name
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
If the user is not authenticated, the connection is automatically closed with code `4001`.
|
|
157
|
+
|
|
158
|
+
### Options
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
Odac.Route.ws('/path', handler, {
|
|
162
|
+
token: true // Require CSRF token (default: true)
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Examples:**
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
// Public WebSocket (no token, no auth)
|
|
170
|
+
Odac.Route.ws('/public', handler, {token: false})
|
|
171
|
+
|
|
172
|
+
// Token required, no auth (default)
|
|
173
|
+
Odac.Route.ws('/chat', handler)
|
|
174
|
+
|
|
175
|
+
// Both token and auth required
|
|
176
|
+
Odac.Route.auth.ws('/secure', handler)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Middleware
|
|
180
|
+
|
|
181
|
+
WebSocket routes support middleware just like HTTP routes:
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
// Define middleware
|
|
185
|
+
Odac.Route.use('auth-check', 'rate-limit').ws('/chat', Odac => {
|
|
186
|
+
Odac.ws.send({type: 'welcome'})
|
|
187
|
+
})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Middleware behavior:**
|
|
191
|
+
- If middleware returns `false`, connection closes with code `4003` (Forbidden)
|
|
192
|
+
- If middleware returns anything other than `true` or `undefined`, connection closes with code `4000`
|
|
193
|
+
- Middleware runs before the WebSocket handler
|
|
194
|
+
|
|
195
|
+
**Example with custom middleware:**
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
// middleware/websocket-auth.js
|
|
199
|
+
module.exports = async Odac => {
|
|
200
|
+
const token = Odac.Request.header('Authorization')
|
|
201
|
+
if (!token) return false
|
|
202
|
+
|
|
203
|
+
const user = await validateToken(token)
|
|
204
|
+
if (!user) return false
|
|
205
|
+
|
|
206
|
+
Odac.Auth.setUser(user)
|
|
207
|
+
return true
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// route/websocket.js
|
|
211
|
+
Odac.Route.use('websocket-auth').ws('/secure', Odac => {
|
|
212
|
+
Odac.ws.send({type: 'authenticated'})
|
|
213
|
+
})
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Client Data Storage
|
|
217
|
+
|
|
218
|
+
Store per-connection data:
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
Odac.ws.data.username = 'john'
|
|
222
|
+
Odac.ws.data.joinedAt = Date.now()
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Intervals and Timeouts
|
|
226
|
+
|
|
227
|
+
Use `Odac.setInterval()` and `Odac.setTimeout()` instead of global functions. They are automatically cleaned up when the WebSocket connection closes:
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
Odac.Route.ws('/live-updates', Odac => {
|
|
231
|
+
Odac.setInterval(() => {
|
|
232
|
+
Odac.ws.send({
|
|
233
|
+
type: 'update',
|
|
234
|
+
timestamp: Date.now()
|
|
235
|
+
})
|
|
236
|
+
}, 1000)
|
|
237
|
+
|
|
238
|
+
Odac.setTimeout(() => {
|
|
239
|
+
Odac.ws.send({type: 'delayed-message'})
|
|
240
|
+
}, 5000)
|
|
241
|
+
})
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Why use Odac.setInterval/setTimeout?**
|
|
245
|
+
- Prevents memory leaks by auto-cleanup on disconnect
|
|
246
|
+
- No need to manually track and clear intervals
|
|
247
|
+
- Works seamlessly with WebSocket lifecycle
|
|
248
|
+
|
|
249
|
+
**Manual cleanup (if needed):**
|
|
250
|
+
|
|
251
|
+
```javascript
|
|
252
|
+
const intervalId = Odac.setInterval(() => {}, 1000)
|
|
253
|
+
Odac.clearInterval(intervalId)
|
|
254
|
+
|
|
255
|
+
const timeoutId = Odac.setTimeout(() => {}, 5000)
|
|
256
|
+
Odac.clearTimeout(timeoutId)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Real-Time Notifications Example
|
|
260
|
+
|
|
261
|
+
```javascript
|
|
262
|
+
Odac.Route.ws('/notifications', async Odac => {
|
|
263
|
+
const user = await Odac.Auth.user()
|
|
264
|
+
if (!user) {
|
|
265
|
+
Odac.ws.close(4001, 'Unauthorized')
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
Odac.ws.data.userId = user.id
|
|
270
|
+
Odac.ws.join(`user-${user.id}`)
|
|
271
|
+
|
|
272
|
+
Odac.ws.on('close', () => {
|
|
273
|
+
console.log(`User ${user.id} disconnected`)
|
|
274
|
+
})
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
// Send notification to specific user from anywhere in your app
|
|
278
|
+
function notifyUser(userId, message) {
|
|
279
|
+
const wsServer = Odac.Route.wsServer
|
|
280
|
+
wsServer.toRoom(`user-${userId}`, {
|
|
281
|
+
type: 'notification',
|
|
282
|
+
message
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Client-Side Usage
|
|
288
|
+
|
|
289
|
+
Frontend clients can use shared connections across tabs:
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
// All browser tabs share one connection
|
|
293
|
+
const ws = Odac.ws('/notifications', {shared: true})
|
|
294
|
+
|
|
295
|
+
ws.on('message', data => {
|
|
296
|
+
console.log('Notification:', data)
|
|
297
|
+
})
|
|
298
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## 🏗️ How to Build a Controller
|
|
2
2
|
|
|
3
|
-
A controller is just a JavaScript module that exports a function. This function automatically gets the magical `
|
|
3
|
+
A controller is just a JavaScript module that exports a function. This function automatically gets the magical `Odac` context object we talked about in the overview.
|
|
4
4
|
|
|
5
5
|
#### A Simple "Hello World" Controller
|
|
6
6
|
|
|
@@ -8,9 +8,9 @@ Check out this basic example from `controller/page/index.js`:
|
|
|
8
8
|
|
|
9
9
|
```javascript
|
|
10
10
|
// This function is our controller!
|
|
11
|
-
module.exports = function (
|
|
11
|
+
module.exports = function (Odac) {
|
|
12
12
|
// It simply returns a string.
|
|
13
|
-
return 'Welcome to my awesome
|
|
13
|
+
return 'Welcome to my awesome Odac server!'
|
|
14
14
|
}
|
|
15
15
|
```
|
|
16
16
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
## 🤝 Your trusty `Odac` Assistant
|
|
2
|
+
|
|
3
|
+
Remember the `Odac` object? It's your best friend inside a controller. It's passed to your controller function and gives you all the tools you need for the current request.
|
|
4
|
+
|
|
5
|
+
#### Awesome Services at Your Fingertips
|
|
6
|
+
|
|
7
|
+
* `Odac.Request`: Info about the user's request.
|
|
8
|
+
* `Odac.View`: Renders your HTML pages.
|
|
9
|
+
* `Odac.Auth`: Manages user logins.
|
|
10
|
+
* `Odac.Token`: Protects your forms.
|
|
11
|
+
* `Odac.Lang`: Helps with different languages.
|
|
12
|
+
|
|
13
|
+
#### Handy Helper Functions
|
|
14
|
+
|
|
15
|
+
* `Odac.return(data)`: Send back a response.
|
|
16
|
+
* `Odac.direct(url)`: Redirect the user to a new page.
|
|
17
|
+
* `Odac.cookie(key, value)`: Set a browser cookie.
|
|
18
|
+
* `Odac.validator()`: Check user input easily.
|
|
19
|
+
* `Odac.setInterval(callback, delay)`: Schedule repeating tasks (auto-cleanup).
|
|
20
|
+
* `Odac.setTimeout(callback, delay)`: Schedule one-time tasks (auto-cleanup).
|
|
21
|
+
* `Odac.stream(input)`: Create streaming responses (SSE).
|
|
22
|
+
|
|
23
|
+
#### Memory-Safe Timers
|
|
24
|
+
|
|
25
|
+
Always use `Odac.setInterval()` and `Odac.setTimeout()` instead of global functions:
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
module.exports = async (Odac) => {
|
|
29
|
+
// ✅ Good - automatically cleaned up
|
|
30
|
+
Odac.setInterval(() => {
|
|
31
|
+
// This stops when request ends
|
|
32
|
+
}, 1000)
|
|
33
|
+
|
|
34
|
+
// ❌ Bad - memory leak!
|
|
35
|
+
setInterval(() => {
|
|
36
|
+
// This runs forever
|
|
37
|
+
}, 1000)
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
With controllers and the `Odac` object, you have everything you need to start building powerful application logic!
|
|
@@ -4,25 +4,25 @@ While simple function exports work great for basic controllers, you can also org
|
|
|
4
4
|
|
|
5
5
|
#### Creating a Controller Class
|
|
6
6
|
|
|
7
|
-
A controller class receives the `
|
|
7
|
+
A controller class receives the `Odac` object in its constructor, giving you access to all services throughout your class methods:
|
|
8
8
|
|
|
9
9
|
```javascript
|
|
10
10
|
// controller/User.js
|
|
11
11
|
class User {
|
|
12
|
-
constructor(
|
|
13
|
-
this.
|
|
12
|
+
constructor(Odac) {
|
|
13
|
+
this.Odac = Odac
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
async getProfile() {
|
|
17
|
-
const user = await this.
|
|
18
|
-
return this.
|
|
17
|
+
const user = await this.Odac.Auth.user()
|
|
18
|
+
return this.Odac.return({
|
|
19
19
|
success: true,
|
|
20
20
|
user: user
|
|
21
21
|
})
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
async updateProfile() {
|
|
25
|
-
const validator = this.
|
|
25
|
+
const validator = this.Odac.validator()
|
|
26
26
|
validator.post('name').required().min(3)
|
|
27
27
|
validator.post('email').required().email()
|
|
28
28
|
|
|
@@ -30,17 +30,17 @@ class User {
|
|
|
30
30
|
return validator.result()
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const name = await this.
|
|
34
|
-
const email = await this.
|
|
33
|
+
const name = await this.Odac.request('name')
|
|
34
|
+
const email = await this.Odac.request('email')
|
|
35
35
|
|
|
36
36
|
// Update user in database
|
|
37
|
-
await this.
|
|
37
|
+
await this.Odac.Mysql.query('UPDATE users SET name = ?, email = ? WHERE id = ?', [
|
|
38
38
|
name,
|
|
39
39
|
email,
|
|
40
|
-
this.
|
|
40
|
+
this.Odac.Auth.user().id
|
|
41
41
|
])
|
|
42
42
|
|
|
43
|
-
return this.
|
|
43
|
+
return this.Odac.return({
|
|
44
44
|
success: true,
|
|
45
45
|
message: 'Profile updated successfully'
|
|
46
46
|
})
|
|
@@ -56,23 +56,23 @@ Once you've created a controller class, you can use it in your routes just like
|
|
|
56
56
|
|
|
57
57
|
```javascript
|
|
58
58
|
// route/www.js
|
|
59
|
-
|
|
59
|
+
Odac.Route.buff = 'www'
|
|
60
60
|
|
|
61
61
|
// Access class methods using dot notation
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
Odac.Route.get('/profile', 'User.getProfile')
|
|
63
|
+
Odac.Route.post('/profile/update', 'User.updateProfile')
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
#### Accessing Classes in Controllers
|
|
67
67
|
|
|
68
|
-
Controller classes are automatically instantiated for each request and attached to the `
|
|
68
|
+
Controller classes are automatically instantiated for each request and attached to the `Odac` object. You can access them from any controller:
|
|
69
69
|
|
|
70
70
|
```javascript
|
|
71
|
-
module.exports = async function (
|
|
71
|
+
module.exports = async function (Odac) {
|
|
72
72
|
// Access your User class
|
|
73
|
-
const profile = await
|
|
73
|
+
const profile = await Odac.User.getProfile()
|
|
74
74
|
|
|
75
|
-
return
|
|
75
|
+
return Odac.return(profile)
|
|
76
76
|
}
|
|
77
77
|
```
|
|
78
78
|
|
|
@@ -81,7 +81,7 @@ module.exports = async function (Candy) {
|
|
|
81
81
|
- **Organization**: Group related methods together
|
|
82
82
|
- **Reusability**: Share logic between different routes
|
|
83
83
|
- **Maintainability**: Easier to manage complex controllers
|
|
84
|
-
- **Context**: The `
|
|
84
|
+
- **Context**: The `Odac` object is always available via `this.Odac`
|
|
85
85
|
|
|
86
86
|
#### Class vs Function Controllers
|
|
87
87
|
|