@bakery-framework/plugin-dashboard 2.0.0-alpha.14 → 2.0.0-alpha.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bakery-framework/plugin-dashboard",
3
- "version": "2.0.0-alpha.14",
3
+ "version": "2.0.0-alpha.15",
4
4
  "description": "Bakery dashboard plugin.",
5
5
  "keywords": [
6
6
  "bakery",
@@ -33,11 +33,11 @@
33
33
  "!src/tests"
34
34
  ],
35
35
  "dependencies": {
36
- "@bakery-framework/core": "^2.0.0-alpha.14",
37
- "@bakery-framework/plugin-analytics": "^2.0.0-alpha.14"
36
+ "@bakery-framework/core": "^2.0.0-alpha.15",
37
+ "@bakery-framework/plugin-analytics": "^2.0.0-alpha.15"
38
38
  },
39
39
  "devDependencies": {
40
- "@bakery-framework/orm": "^2.0.0-alpha.14"
40
+ "@bakery-framework/orm": "^2.0.0-alpha.15"
41
41
  },
42
42
  "engines": {
43
43
  "bun": ">=1.4.0"
@@ -77,12 +77,19 @@ export function initLogsWebSocket() {
77
77
  '<div style="color: var(--text-muted);">Connecting to server log stream...</div>'
78
78
 
79
79
  try {
80
- logsWs = new WebSocket(getWebSocketUrl('/_livereload'))
80
+ // `/_dashboard/logs`, not `/_livereload`. The live-reload socket is
81
+ // registered only under `DEV`, so this panel answered 400 on every
82
+ // production server and sat on "Connecting..." for ever. The console has
83
+ // its own socket now, behind the console's own door.
84
+ logsWs = new WebSocket(getWebSocketUrl('/_dashboard/logs'))
81
85
 
82
86
  logsWs.onopen = () => {
83
87
  consoleEl.innerHTML =
84
88
  '<div style="color: var(--ok); display: flex; align-items: center; gap: 0.25rem;"><span>Connected to logs pipeline. Listening for events...</span></div>'
85
- logsWs?.send(JSON.stringify({ type: 'subscribe_logger' }))
89
+ // No `subscribe_logger` frame: membership is the handler's `open`, so
90
+ // there is nothing to ask for. That message was `LiveReloadHandler`'s
91
+ // protocol, and it is what an app page still sends to forward its own
92
+ // console in development.
86
93
  }
87
94
 
88
95
  logsWs.onmessage = event => {
@@ -0,0 +1,72 @@
1
+ import { WebSocketHandler } from '@bakery-framework/core/handlers'
2
+ import { connectedLoggers } from '@bakery-framework/core/logger'
3
+ import { isAnalyticsAuthorized } from '@bakery-framework/plugin-analytics/stats'
4
+ import type { ServerWebSocket } from 'bun'
5
+
6
+ /**
7
+ * The console's log stream.
8
+ *
9
+ * **The Logs panel was dead in production**, and only in production. Its client
10
+ * connected to `/_livereload`, which `LiveReloadHandler` serves — and that
11
+ * handler is registered only under `DEV` and refuses in `canHandle` besides.
12
+ * So a production server answered 400 on the upgrade, nothing ever joined
13
+ * `connectedLoggers`, and the panel sat on "Connecting to server log
14
+ * stream..." for the life of the process. Measured against `bun run serve`
15
+ * before this existed: `/_livereload` → 400, console → 200.
16
+ *
17
+ * Same shape as the bundle ETag bug and the timescale import before it: a dev
18
+ * server takes a different branch, so the thing works on the machine where it
19
+ * is written and nowhere else.
20
+ *
21
+ * **Not fixed by registering `LiveReloadHandler` in production.** That socket
22
+ * is the *watcher's*: it publishes `force_reload` to every subscriber and
23
+ * echoes `client_log` frames between browsers. Shipping it to production would
24
+ * be adding a surface, not repairing one. The console owns its own panel, so
25
+ * it owns its own socket, which is the same split analytics already has with
26
+ * `/_analytics_ws`.
27
+ *
28
+ * Membership is the only thing this does. Core owns the registry
29
+ * (`logger/clients.ts`) and the dashboard's `setLogCallback` does the
30
+ * broadcasting; in development `LiveReloadHandler` adds app pages to the same
31
+ * Set when they forward their console, which is why those lines show up here
32
+ * too.
33
+ */
34
+ export class DashboardLogsHandler extends WebSocketHandler {
35
+ static override readonly namespace = '/_dashboard'
36
+
37
+ /**
38
+ * **The door is checked here because there is nowhere later.**
39
+ *
40
+ * `router.ts` attempts the upgrade before `onRoute`, before `onRequest` and
41
+ * before any fetch handler resolves, so a plugin hook cannot gate a socket.
42
+ * `AnalyticsWSHandler` carries the same note for the same reason.
43
+ *
44
+ * It is the console's own door: `isAnalyticsAuthorized` is what guards
45
+ * `/_dashboard` itself, so a stream of every server log line is admitted by
46
+ * exactly what admits the page that renders it. Anything weaker would hand
47
+ * out log lines to someone who cannot open the console.
48
+ *
49
+ * Async because `authorize` may be a predicate the application supplies; the
50
+ * registry awaits a promise-returning `canHandle`.
51
+ *
52
+ * The path sits inside `/_dashboard`, which `DashboardHandler` claims for
53
+ * *fetch* at priority 120. That is not a collision: the upgrade is dispatched
54
+ * from a separate registry and runs first, so the fetch handler never sees
55
+ * this request.
56
+ */
57
+ static override async canHandle(
58
+ path: string,
59
+ req?: Request,
60
+ ): Promise<boolean> {
61
+ if (path !== '/_dashboard/logs') return false
62
+ return req ? await isAnalyticsAuthorized(req) : false
63
+ }
64
+
65
+ static override open(ws: ServerWebSocket<any>) {
66
+ connectedLoggers.add(ws)
67
+ }
68
+
69
+ static override close(ws: ServerWebSocket<any>) {
70
+ connectedLoggers.delete(ws)
71
+ }
72
+ }
package/src/setup.ts CHANGED
@@ -31,6 +31,7 @@ import {
31
31
  handleGetSessions,
32
32
  handleUpdateSession,
33
33
  } from './endpoints/sessions'
34
+ import { DashboardLogsHandler } from './endpoints/logs-socket'
34
35
  import { pluginPath } from './paths'
35
36
  import renderDashboardShell from './shell'
36
37
 
@@ -129,6 +130,12 @@ export function setupDashboard(
129
130
  // plugin's own directory — no bespoke asset route, no hand-rolled caching.
130
131
  mountRoutes('/_dashboard', pluginPath('../public'))
131
132
 
133
+ // The console's log stream, and it is registered in every mode. The Logs
134
+ // panel used to ride on `/_livereload`, which exists only under `DEV` — so
135
+ // the panel worked on the machine it was written on and answered 400
136
+ // everywhere else. See `endpoints/logs-socket.ts`.
137
+ Bakery.handlers.websocket.set(DashboardLogsHandler)
138
+
132
139
  Bakery.handlers.fetch.set(DashboardHandler, 120)
133
140
  }
134
141