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.
Files changed (208) hide show
  1. package/.github/workflows/auto-pr-description.yml +0 -2
  2. package/.github/workflows/codeql.yml +46 -0
  3. package/.github/workflows/release.yml +13 -6
  4. package/.github/workflows/test-coverage.yml +10 -9
  5. package/.releaserc.js +9 -6
  6. package/CHANGELOG.md +62 -150
  7. package/CODE_OF_CONDUCT.md +1 -1
  8. package/CONTRIBUTING.md +8 -8
  9. package/LICENSE +21 -661
  10. package/README.md +12 -12
  11. package/SECURITY.md +4 -4
  12. package/bin/odac.js +101 -0
  13. package/{framework/web/candy.js → client/odac.js} +310 -44
  14. package/docs/backend/01-overview/{01-whats-in-the-candy-box.md → 01-whats-in-the-odac-box.md} +4 -2
  15. package/docs/backend/01-overview/02-super-handy-helper-functions.md +29 -1
  16. package/docs/backend/01-overview/03-development-server.md +11 -11
  17. package/docs/backend/02-structure/01-typical-project-layout.md +4 -4
  18. package/docs/backend/03-config/00-configuration-overview.md +6 -6
  19. package/docs/backend/03-config/01-database-connection.md +1 -1
  20. package/docs/backend/03-config/02-static-route-mapping-optional.md +4 -4
  21. package/docs/backend/03-config/04-environment-variables.md +20 -20
  22. package/docs/backend/03-config/05-early-hints.md +4 -4
  23. package/docs/backend/04-routing/01-basic-page-routes.md +4 -4
  24. package/docs/backend/04-routing/02-controller-less-view-routes.md +5 -5
  25. package/docs/backend/04-routing/03-api-and-data-routes.md +3 -3
  26. package/docs/backend/04-routing/04-authentication-aware-routes.md +5 -5
  27. package/docs/backend/04-routing/05-advanced-routing.md +3 -3
  28. package/docs/backend/04-routing/06-error-pages.md +17 -17
  29. package/docs/backend/04-routing/07-cron-jobs.md +13 -13
  30. package/docs/backend/04-routing/08-middleware.md +214 -0
  31. package/docs/backend/04-routing/09-websocket-auth-middleware.md +292 -0
  32. package/docs/backend/04-routing/09-websocket-examples.md +381 -0
  33. package/docs/backend/04-routing/09-websocket-quick-reference.md +211 -0
  34. package/docs/backend/04-routing/09-websocket.md +298 -0
  35. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +3 -3
  36. package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +41 -0
  37. package/docs/backend/05-controllers/03-controller-classes.md +19 -19
  38. package/docs/backend/05-forms/01-custom-forms.md +114 -114
  39. package/docs/backend/05-forms/02-automatic-database-insert.md +82 -82
  40. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +26 -26
  41. package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +10 -10
  42. package/docs/backend/07-views/01-the-view-directory.md +1 -1
  43. package/docs/backend/07-views/02-rendering-a-view.md +22 -22
  44. package/docs/backend/07-views/03-template-syntax.md +52 -52
  45. package/docs/backend/07-views/03-variables.md +84 -84
  46. package/docs/backend/07-views/04-request-data.md +57 -57
  47. package/docs/backend/07-views/05-conditionals.md +78 -78
  48. package/docs/backend/07-views/06-loops.md +114 -114
  49. package/docs/backend/07-views/07-translations.md +66 -66
  50. package/docs/backend/07-views/08-backend-javascript.md +103 -103
  51. package/docs/backend/07-views/09-comments.md +71 -71
  52. package/docs/backend/08-database/01-database-connection.md +8 -8
  53. package/docs/backend/08-database/02-using-mysql.md +49 -49
  54. package/docs/backend/09-validation/01-the-validator-service.md +38 -38
  55. package/docs/backend/10-authentication/01-user-logins-with-authjs.md +15 -15
  56. package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +10 -10
  57. package/docs/backend/10-authentication/03-register.md +12 -12
  58. package/docs/backend/10-authentication/{04-candy-register-forms.md → 04-odac-register-forms.md} +141 -141
  59. package/docs/backend/10-authentication/05-session-management.md +10 -10
  60. package/docs/backend/10-authentication/{06-candy-login-forms.md → 06-odac-login-forms.md} +125 -125
  61. package/docs/backend/11-mail/01-the-mail-service.md +5 -5
  62. package/docs/backend/12-streaming/01-streaming-overview.md +96 -54
  63. package/docs/backend/13-utilities/{01-candy-var.md → 01-odac-var.md} +109 -109
  64. package/docs/frontend/01-overview/01-introduction.md +30 -30
  65. package/docs/frontend/02-ajax-navigation/01-quick-start.md +45 -45
  66. package/docs/frontend/02-ajax-navigation/02-configuration.md +14 -14
  67. package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +36 -36
  68. package/docs/frontend/03-forms/01-form-handling.md +32 -32
  69. package/docs/frontend/04-api-requests/01-get-post.md +33 -33
  70. package/docs/frontend/05-streaming/01-client-streaming.md +15 -15
  71. package/docs/frontend/06-websocket/00-overview.md +76 -0
  72. package/docs/frontend/06-websocket/01-websocket-client.md +139 -0
  73. package/docs/frontend/06-websocket/02-shared-websocket.md +149 -0
  74. package/docs/index.json +49 -11
  75. package/eslint.config.mjs +6 -6
  76. package/{framework/index.js → index.js} +1 -1
  77. package/package.json +14 -39
  78. package/{framework/src → src}/Auth.js +59 -59
  79. package/{framework/src → src}/Config.js +3 -3
  80. package/{framework/src → src}/Lang.js +7 -7
  81. package/{framework/src → src}/Mail.js +5 -5
  82. package/{framework/src → src}/Mysql.js +42 -42
  83. package/src/Odac.js +112 -0
  84. package/{framework/src → src}/Request.js +38 -36
  85. package/{framework/src → src}/Route/Internal.js +116 -116
  86. package/src/Route/Middleware.js +75 -0
  87. package/src/Route.js +621 -0
  88. package/src/Server.js +22 -0
  89. package/{framework/src → src}/Stream.js +11 -3
  90. package/{framework/src → src}/Validator.js +21 -21
  91. package/{framework/src → src}/Var.js +5 -5
  92. package/{framework/src → src}/View/EarlyHints.js +1 -1
  93. package/{framework/src → src}/View/Form.js +69 -69
  94. package/{framework/src → src}/View.js +78 -81
  95. package/src/WebSocket.js +403 -0
  96. package/template/config.json +5 -0
  97. package/{web → template}/controller/page/about.js +6 -6
  98. package/{web → template}/controller/page/index.js +9 -9
  99. package/{web → template}/package.json +4 -5
  100. package/{web → template}/public/assets/css/style.css +4 -4
  101. package/{web → template}/public/assets/js/app.js +6 -6
  102. package/{web → template}/route/www.js +6 -6
  103. package/{web → template}/skeleton/main.html +1 -1
  104. package/{web → template}/view/content/about.html +5 -5
  105. package/{web → template}/view/content/home.html +12 -12
  106. package/template/view/footer/main.html +11 -0
  107. package/{web → template}/view/head/main.html +1 -1
  108. package/{web → template}/view/header/main.html +2 -2
  109. package/test/core/Candy.test.js +58 -58
  110. package/test/core/Commands.test.js +7 -7
  111. package/test/core/Config.test.js +82 -85
  112. package/test/core/Lang.test.js +2 -2
  113. package/test/core/Process.test.js +6 -6
  114. package/test/framework/Route.test.js +56 -37
  115. package/test/framework/View/EarlyHints.test.js +2 -2
  116. package/test/framework/WebSocket.test.js +100 -0
  117. package/test/framework/middleware.test.js +85 -0
  118. package/test/server/Api.test.js +31 -31
  119. package/test/server/DNS.test.js +11 -11
  120. package/test/server/Hub.test.js +497 -0
  121. package/test/server/Mail.account.test_.js +3 -3
  122. package/test/server/Mail.init.test_.js +10 -10
  123. package/test/server/Mail.test_.js +20 -20
  124. package/test/server/SSL.test_.js +54 -54
  125. package/test/server/Server.test.js +39 -39
  126. package/test/server/Service.test_.js +7 -7
  127. package/test/server/Subdomain.test.js +7 -7
  128. package/test/server/Web/Firewall.test.js +87 -87
  129. package/test/server/Web/Proxy.test.js +397 -0
  130. package/test/server/{Web.test_.js → Web.test.js} +137 -205
  131. package/test/server/__mocks__/fs.js +2 -2
  132. package/test/server/__mocks__/{globalCandy.js → globalOdac.js} +5 -5
  133. package/test/server/__mocks__/index.js +6 -6
  134. package/test/server/__mocks__/testFactories.js +1 -1
  135. package/test/server/__mocks__/testHelpers.js +7 -7
  136. package/.husky/pre-commit +0 -2
  137. package/.kiro/steering/code-style.md +0 -56
  138. package/.kiro/steering/product.md +0 -20
  139. package/.kiro/steering/structure.md +0 -77
  140. package/.kiro/steering/tech.md +0 -87
  141. package/AGENTS.md +0 -84
  142. package/bin/candy +0 -10
  143. package/bin/candypack +0 -10
  144. package/cli/index.js +0 -3
  145. package/cli/src/Cli.js +0 -348
  146. package/cli/src/Connector.js +0 -93
  147. package/cli/src/Monitor.js +0 -416
  148. package/core/Candy.js +0 -87
  149. package/core/Commands.js +0 -239
  150. package/core/Config.js +0 -1094
  151. package/core/Lang.js +0 -52
  152. package/core/Log.js +0 -43
  153. package/core/Process.js +0 -26
  154. package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +0 -20
  155. package/docs/server/01-installation/01-quick-install.md +0 -19
  156. package/docs/server/01-installation/02-manual-installation-via-npm.md +0 -9
  157. package/docs/server/02-get-started/01-core-concepts.md +0 -7
  158. package/docs/server/02-get-started/02-basic-commands.md +0 -57
  159. package/docs/server/02-get-started/03-cli-reference.md +0 -276
  160. package/docs/server/02-get-started/04-cli-quick-reference.md +0 -102
  161. package/docs/server/03-service/01-start-a-new-service.md +0 -57
  162. package/docs/server/03-service/02-delete-a-service.md +0 -48
  163. package/docs/server/04-web/01-create-a-website.md +0 -36
  164. package/docs/server/04-web/02-list-websites.md +0 -9
  165. package/docs/server/04-web/03-delete-a-website.md +0 -29
  166. package/docs/server/05-subdomain/01-create-a-subdomain.md +0 -32
  167. package/docs/server/05-subdomain/02-list-subdomains.md +0 -33
  168. package/docs/server/05-subdomain/03-delete-a-subdomain.md +0 -41
  169. package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +0 -34
  170. package/docs/server/07-mail/01-create-a-mail-account.md +0 -23
  171. package/docs/server/07-mail/02-delete-a-mail-account.md +0 -20
  172. package/docs/server/07-mail/03-list-mail-accounts.md +0 -20
  173. package/docs/server/07-mail/04-change-account-password.md +0 -23
  174. package/framework/src/Candy.js +0 -81
  175. package/framework/src/Route.js +0 -455
  176. package/framework/src/Server.js +0 -15
  177. package/locale/de-DE.json +0 -80
  178. package/locale/en-US.json +0 -79
  179. package/locale/es-ES.json +0 -80
  180. package/locale/fr-FR.json +0 -80
  181. package/locale/pt-BR.json +0 -80
  182. package/locale/ru-RU.json +0 -80
  183. package/locale/tr-TR.json +0 -85
  184. package/locale/zh-CN.json +0 -80
  185. package/server/index.js +0 -5
  186. package/server/src/Api.js +0 -88
  187. package/server/src/DNS.js +0 -940
  188. package/server/src/Hub.js +0 -535
  189. package/server/src/Mail.js +0 -571
  190. package/server/src/SSL.js +0 -180
  191. package/server/src/Server.js +0 -27
  192. package/server/src/Service.js +0 -248
  193. package/server/src/Subdomain.js +0 -64
  194. package/server/src/Web/Firewall.js +0 -170
  195. package/server/src/Web/Proxy.js +0 -134
  196. package/server/src/Web.js +0 -451
  197. package/server/src/mail/imap.js +0 -1091
  198. package/server/src/mail/server.js +0 -32
  199. package/server/src/mail/smtp.js +0 -786
  200. package/test/server/Client.test.js +0 -338
  201. package/test/server/__mocks__/http-proxy.js +0 -105
  202. package/watchdog/index.js +0 -3
  203. package/watchdog/src/Watchdog.js +0 -156
  204. package/web/config.json +0 -5
  205. package/web/view/footer/main.html +0 -11
  206. /package/{framework/src → src}/Env.js +0 -0
  207. /package/{framework/src → src}/Route/Cron.js +0 -0
  208. /package/{framework/src → src}/Token.js +0 -0
@@ -0,0 +1,381 @@
1
+ # WebSocket Examples
2
+
3
+ Complete examples for common WebSocket use cases.
4
+
5
+ ## Echo Server
6
+
7
+ Simple echo server that sends back received messages:
8
+
9
+ ```javascript
10
+ // route/websocket.js
11
+ Odac.Route.ws('/echo', Odac => {
12
+ Odac.ws.send({type: 'welcome', message: 'Connected!'})
13
+
14
+ Odac.ws.on('message', data => {
15
+ Odac.ws.send({type: 'echo', data})
16
+ })
17
+ })
18
+ ```
19
+
20
+ **Client:**
21
+ ```javascript
22
+ const ws = Odac.ws('/echo')
23
+ ws.on('message', data => console.log(data))
24
+ ws.send({message: 'Hello!'})
25
+ ```
26
+
27
+ ## Authenticated Chat Room
28
+
29
+ ### Using auth.ws() (Recommended)
30
+
31
+ ```javascript
32
+ Odac.Route.auth.ws('/chat', async Odac => {
33
+ const user = await Odac.Auth.user()
34
+
35
+ Odac.ws.join('general')
36
+ Odac.ws.data.user = user
37
+
38
+ Odac.ws.to('general').send({
39
+ type: 'user_joined',
40
+ user: user.name
41
+ })
42
+
43
+ Odac.ws.on('message', data => {
44
+ Odac.ws.to('general').send({
45
+ type: 'message',
46
+ user: user.name,
47
+ text: data.text
48
+ })
49
+ })
50
+
51
+ Odac.ws.on('close', () => {
52
+ Odac.ws.to('general').send({
53
+ type: 'user_left',
54
+ user: user.name
55
+ })
56
+ })
57
+ })
58
+ ```
59
+
60
+ ### Manual Authentication Check
61
+
62
+ ```javascript
63
+ Odac.Route.ws('/chat', async Odac => {
64
+ const user = await Odac.Auth.user()
65
+
66
+ if (!user) {
67
+ Odac.ws.close(4001, 'Unauthorized')
68
+ return
69
+ }
70
+
71
+ Odac.ws.join('general')
72
+ Odac.ws.data.user = user
73
+
74
+ Odac.ws.to('general').send({
75
+ type: 'user_joined',
76
+ user: user.name
77
+ })
78
+
79
+ Odac.ws.on('message', data => {
80
+ Odac.ws.to('general').send({
81
+ type: 'message',
82
+ user: user.name,
83
+ text: data.text
84
+ })
85
+ })
86
+
87
+ Odac.ws.on('close', () => {
88
+ Odac.ws.to('general').send({
89
+ type: 'user_left',
90
+ user: user.name
91
+ })
92
+ })
93
+ })
94
+ ```
95
+
96
+ **Client:**
97
+ ```javascript
98
+ const chat = Odac.ws('/chat')
99
+
100
+ chat.on('message', data => {
101
+ if (data.type === 'message') {
102
+ console.log(`${data.user}: ${data.text}`)
103
+ }
104
+ })
105
+
106
+ chat.send({text: 'Hello everyone!'})
107
+ ```
108
+
109
+ ## Room-Based Chat with URL Parameters
110
+
111
+ Dynamic rooms using URL parameters:
112
+
113
+ ```javascript
114
+ Odac.Route.ws('/room/{roomId}', async Odac => {
115
+ const {roomId} = Odac.Request.data.url
116
+ const user = await Odac.Auth.user()
117
+
118
+ if (!user) {
119
+ Odac.ws.close(4001, 'Unauthorized')
120
+ return
121
+ }
122
+
123
+ Odac.ws.join(roomId)
124
+ Odac.ws.data.roomId = roomId
125
+
126
+ Odac.ws.send({
127
+ type: 'joined',
128
+ room: roomId
129
+ })
130
+
131
+ Odac.ws.on('message', data => {
132
+ Odac.ws.to(roomId).send({
133
+ type: 'message',
134
+ user: user.name,
135
+ text: data.text,
136
+ room: roomId
137
+ })
138
+ })
139
+ })
140
+ ```
141
+
142
+ **Client:**
143
+ ```javascript
144
+ const room = Odac.ws('/room/gaming')
145
+ room.on('message', data => console.log(data))
146
+ room.send({text: 'Hi from gaming room!'})
147
+ ```
148
+
149
+ ## Real-Time Notifications
150
+
151
+ User-specific notification system:
152
+
153
+ ```javascript
154
+ Odac.Route.ws('/notifications', async Odac => {
155
+ const user = await Odac.Auth.user()
156
+
157
+ if (!user) {
158
+ Odac.ws.close(4001, 'Unauthorized')
159
+ return
160
+ }
161
+
162
+ Odac.ws.join(`user-${user.id}`)
163
+ Odac.ws.data.userId = user.id
164
+
165
+ Odac.ws.send({
166
+ type: 'connected',
167
+ unreadCount: await getUnreadCount(user.id)
168
+ })
169
+ })
170
+
171
+ // Send notification from anywhere in your app
172
+ async function notifyUser(userId, notification) {
173
+ const wsServer = Odac.Route.wsServer
174
+ wsServer.toRoom(`user-${userId}`, {
175
+ type: 'notification',
176
+ ...notification
177
+ })
178
+ }
179
+ ```
180
+
181
+ **Client (with cross-tab sharing):**
182
+ ```javascript
183
+ const notifications = Odac.ws('/notifications', {
184
+ shared: true,
185
+ autoReconnect: true
186
+ })
187
+
188
+ notifications.on('message', data => {
189
+ if (data.type === 'notification') {
190
+ showNotification(data.title, data.message)
191
+ }
192
+ })
193
+ ```
194
+
195
+ ## Broadcasting System
196
+
197
+ Broadcast messages to all connected clients:
198
+
199
+ ```javascript
200
+ Odac.Route.ws('/broadcast', Odac => {
201
+ Odac.ws.on('message', data => {
202
+ if (data.type === 'broadcast') {
203
+ Odac.ws.broadcast({
204
+ type: 'announcement',
205
+ message: data.message,
206
+ timestamp: Date.now()
207
+ })
208
+ }
209
+ })
210
+ })
211
+ ```
212
+
213
+ **Client:**
214
+ ```javascript
215
+ const broadcast = Odac.ws('/broadcast')
216
+
217
+ broadcast.on('message', data => {
218
+ if (data.type === 'announcement') {
219
+ alert(data.message)
220
+ }
221
+ })
222
+
223
+ // Send to all clients
224
+ broadcast.send({
225
+ type: 'broadcast',
226
+ message: 'Server maintenance in 5 minutes'
227
+ })
228
+ ```
229
+
230
+ ## Live Dashboard
231
+
232
+ Real-time data updates for dashboards:
233
+
234
+ ```javascript
235
+ Odac.Route.ws('/dashboard', async Odac => {
236
+ const user = await Odac.Auth.user()
237
+
238
+ if (!user || !user.isAdmin) {
239
+ Odac.ws.close(4001, 'Unauthorized')
240
+ return
241
+ }
242
+
243
+ const sendStats = async () => {
244
+ const stats = await getSystemStats()
245
+ Odac.ws.send({type: 'stats', data: stats})
246
+ }
247
+
248
+ sendStats()
249
+ const interval = setInterval(sendStats, 5000)
250
+
251
+ Odac.ws.on('close', () => {
252
+ clearInterval(interval)
253
+ })
254
+ })
255
+ ```
256
+
257
+ **Client:**
258
+ ```javascript
259
+ const dashboard = Odac.ws('/dashboard')
260
+
261
+ dashboard.on('message', data => {
262
+ if (data.type === 'stats') {
263
+ updateDashboard(data.data)
264
+ }
265
+ })
266
+ ```
267
+
268
+ ## WebSocket with Middleware
269
+
270
+ Use middleware for rate limiting, authentication, or custom logic:
271
+
272
+ ```javascript
273
+ // middleware/rate-limit.js
274
+ const connections = new Map()
275
+
276
+ module.exports = async Odac => {
277
+ const ip = Odac.Request.ip
278
+ const now = Date.now()
279
+
280
+ if (connections.has(ip)) {
281
+ const lastConnection = connections.get(ip)
282
+ if (now - lastConnection < 1000) {
283
+ return false // Too many connections
284
+ }
285
+ }
286
+
287
+ connections.set(ip, now)
288
+ return true
289
+ }
290
+
291
+ // route/websocket.js
292
+ Odac.Route.use('rate-limit').ws('/chat', Odac => {
293
+ Odac.ws.send({type: 'connected'})
294
+ })
295
+ ```
296
+
297
+ **Multiple Middleware:**
298
+
299
+ ```javascript
300
+ Odac.Route.use('auth', 'rate-limit', 'log-connection').ws('/secure', Odac => {
301
+ Odac.ws.send({type: 'authenticated'})
302
+ })
303
+ ```
304
+
305
+ ## Multiplayer Game
306
+
307
+ Simple multiplayer game state synchronization:
308
+
309
+ ```javascript
310
+ Odac.Route.ws('/game/{gameId}', async Odac => {
311
+ const {gameId} = Odac.Request.data.url
312
+ const user = await Odac.Auth.user()
313
+
314
+ if (!user) {
315
+ Odac.ws.close(4001, 'Unauthorized')
316
+ return
317
+ }
318
+
319
+ Odac.ws.join(`game-${gameId}`)
320
+ Odac.ws.data.gameId = gameId
321
+ Odac.ws.data.playerId = user.id
322
+
323
+ Odac.ws.to(`game-${gameId}`).send({
324
+ type: 'player_joined',
325
+ playerId: user.id,
326
+ name: user.name
327
+ })
328
+
329
+ Odac.ws.on('message', data => {
330
+ switch (data.type) {
331
+ case 'move':
332
+ Odac.ws.to(`game-${gameId}`).send({
333
+ type: 'player_moved',
334
+ playerId: user.id,
335
+ position: data.position
336
+ })
337
+ break
338
+
339
+ case 'action':
340
+ Odac.ws.to(`game-${gameId}`).send({
341
+ type: 'player_action',
342
+ playerId: user.id,
343
+ action: data.action
344
+ })
345
+ break
346
+ }
347
+ })
348
+
349
+ Odac.ws.on('close', () => {
350
+ Odac.ws.to(`game-${gameId}`).send({
351
+ type: 'player_left',
352
+ playerId: user.id
353
+ })
354
+ })
355
+ })
356
+ ```
357
+
358
+ **Client:**
359
+ ```javascript
360
+ const game = Odac.ws('/game/room-123')
361
+
362
+ game.on('message', data => {
363
+ switch (data.type) {
364
+ case 'player_joined':
365
+ addPlayer(data.playerId, data.name)
366
+ break
367
+ case 'player_moved':
368
+ updatePlayerPosition(data.playerId, data.position)
369
+ break
370
+ case 'player_action':
371
+ handlePlayerAction(data.playerId, data.action)
372
+ break
373
+ }
374
+ })
375
+
376
+ // Send player movement
377
+ game.send({
378
+ type: 'move',
379
+ position: {x: 100, y: 200}
380
+ })
381
+ ```
@@ -0,0 +1,211 @@
1
+ # WebSocket Quick Reference
2
+
3
+ Quick reference for Odac WebSocket API.
4
+
5
+ ## Backend API
6
+
7
+ ### Route Definition
8
+ ```javascript
9
+ Odac.Route.ws('/path', Odac => {
10
+ // Handler - WebSocket client accessible via Odac.ws
11
+ })
12
+ ```
13
+
14
+ ### WebSocket Client Methods (Odac.ws)
15
+
16
+ | Method | Description |
17
+ |--------|-------------|
18
+ | `Odac.ws.send(data)` | Send JSON data to client |
19
+ | `Odac.ws.sendBinary(buffer)` | Send binary data |
20
+ | `Odac.ws.close(code, reason)` | Close connection |
21
+ | `Odac.ws.ping()` | Send ping frame |
22
+ | `Odac.ws.join(room)` | Join a room |
23
+ | `Odac.ws.leave(room)` | Leave a room |
24
+ | `Odac.ws.to(room).send(data)` | Send to room |
25
+ | `Odac.ws.broadcast(data)` | Send to all clients |
26
+ | `Odac.ws.on(event, handler)` | Add event listener |
27
+ | `Odac.ws.off(event, handler)` | Remove event listener |
28
+
29
+ ### WebSocket Client Properties
30
+
31
+ | Property | Description |
32
+ |----------|-------------|
33
+ | `Odac.ws.id` | Unique client ID |
34
+ | `Odac.ws.rooms` | Array of joined rooms |
35
+ | `Odac.ws.data` | Custom data storage |
36
+
37
+ ### Events
38
+
39
+ | Event | Description |
40
+ |-------|-------------|
41
+ | `message` | Incoming message |
42
+ | `close` | Connection closed |
43
+ | `error` | Error occurred |
44
+ | `pong` | Pong received |
45
+
46
+ ### Server Methods
47
+
48
+ | Method | Description |
49
+ |--------|-------------|
50
+ | `Odac.Route.wsServer.clients` | Map of all clients |
51
+ | `Odac.Route.wsServer.clientCount` | Number of clients |
52
+ | `Odac.Route.wsServer.toRoom(room, data)` | Send to room |
53
+ | `Odac.Route.wsServer.broadcast(data)` | Broadcast to all |
54
+
55
+ ## Frontend API
56
+
57
+ ### Connection
58
+ ```javascript
59
+ const ws = Odac.ws('/path', options)
60
+ ```
61
+
62
+ ### Backend Options
63
+
64
+ | Option | Default | Description |
65
+ |--------|---------|-------------|
66
+ | `token` | `true` | Require CSRF token |
67
+
68
+ ### Client Options
69
+
70
+ | Option | Default | Description |
71
+ |--------|---------|-------------|
72
+ | `autoReconnect` | `true` | Auto-reconnect on disconnect |
73
+ | `reconnectDelay` | `3000` | Delay between reconnects (ms) |
74
+ | `maxReconnectAttempts` | `10` | Max reconnect attempts |
75
+ | `shared` | `false` | Share across browser tabs |
76
+ | `token` | `true` | Send CSRF token |
77
+
78
+ ### Client Methods
79
+
80
+ | Method | Description |
81
+ |--------|-------------|
82
+ | `ws.send(data)` | Send data to server |
83
+ | `ws.close()` | Close connection |
84
+ | `ws.on(event, handler)` | Add event listener |
85
+ | `ws.off(event, handler)` | Remove event listener |
86
+
87
+ ### Client Properties
88
+
89
+ | Property | Description |
90
+ |----------|-------------|
91
+ | `ws.connected` | Connection status (boolean) |
92
+ | `ws.state` | WebSocket state |
93
+
94
+ ### Events
95
+
96
+ | Event | Description |
97
+ |-------|-------------|
98
+ | `open` | Connection opened |
99
+ | `message` | Message received |
100
+ | `close` | Connection closed |
101
+ | `error` | Error occurred |
102
+
103
+ ## Common Patterns
104
+
105
+ ### Echo Server
106
+ ```javascript
107
+ // With token (default)
108
+ Odac.Route.ws('/echo', Odac => {
109
+ Odac.ws.on('message', data => Odac.ws.send(data))
110
+ })
111
+
112
+ // Public (no token)
113
+ Odac.Route.ws('/public-echo', Odac => {
114
+ Odac.ws.on('message', data => Odac.ws.send(data))
115
+ }, {token: false})
116
+ ```
117
+
118
+ ### Authenticated Route
119
+ ```javascript
120
+ // Using auth.ws() (recommended)
121
+ Odac.Route.auth.ws('/secure', async Odac => {
122
+ const user = await Odac.Auth.user()
123
+ // Handle connection
124
+ })
125
+
126
+ // Manual check
127
+ Odac.Route.ws('/secure', async Odac => {
128
+ if (!await Odac.Auth.check()) {
129
+ return Odac.ws.close(4001, 'Unauthorized')
130
+ }
131
+ // Handle connection
132
+ })
133
+ ```
134
+
135
+ ### With Middleware
136
+ ```javascript
137
+ Odac.Route.use('auth', 'rate-limit').ws('/chat', Odac => {
138
+ Odac.ws.send({type: 'welcome'})
139
+ })
140
+ ```
141
+
142
+ ### Room Broadcasting
143
+ ```javascript
144
+ Odac.Route.ws('/chat', Odac => {
145
+ Odac.ws.join('room-1')
146
+ Odac.ws.on('message', data => {
147
+ Odac.ws.to('room-1').send(data)
148
+ })
149
+ })
150
+ ```
151
+
152
+ ### URL Parameters
153
+ ```javascript
154
+ Odac.Route.ws('/room/{id}', Odac => {
155
+ const {id} = Odac.Request.data.url
156
+ Odac.ws.join(id)
157
+ })
158
+ ```
159
+
160
+ ### Shared Connection (Client)
161
+ ```javascript
162
+ const ws = Odac.ws('/chat', {shared: true})
163
+ // All tabs share one connection
164
+ ```
165
+
166
+ ## Status Codes
167
+
168
+ | Code | Description |
169
+ |------|-------------|
170
+ | `1000` | Normal closure |
171
+ | `1001` | Going away |
172
+ | `1002` | Protocol error |
173
+ | `1003` | Unsupported data |
174
+ | `1006` | Abnormal closure |
175
+ | `4000` | Middleware rejected |
176
+ | `4001` | Unauthorized |
177
+ | `4002` | Invalid/missing token |
178
+ | `4003` | Forbidden (middleware) |
179
+
180
+ ## Best Practices
181
+
182
+ 1. **Always handle authentication**
183
+ ```javascript
184
+ if (!await Odac.Auth.check()) {
185
+ return Odac.ws.close(4001, 'Unauthorized')
186
+ }
187
+ ```
188
+
189
+ 2. **Use rooms for targeted messaging**
190
+ ```javascript
191
+ Odac.ws.join(`user-${userId}`)
192
+ ```
193
+
194
+ 3. **Clean up on close**
195
+ ```javascript
196
+ Odac.ws.on('close', () => {
197
+ clearInterval(interval)
198
+ Odac.ws.leave('room')
199
+ })
200
+ ```
201
+
202
+ 4. **Store per-connection data**
203
+ ```javascript
204
+ Odac.ws.data.userId = user.id
205
+ Odac.ws.data.joinedAt = Date.now()
206
+ ```
207
+
208
+ 5. **Use shared connections for notifications**
209
+ ```javascript
210
+ const ws = Odac.ws('/notifications', {shared: true})
211
+ ```