Commit Graph

1094 Commits

Author SHA1 Message Date
Fabian @ Blax Software 2a9986d209 fix(fork): fully isolate inherited Redis connections in the message child
The per-message fork child purged cache/redis manager singletons but never reset
the session bindings, and never actively disconnected the fds it inherited. Once an
app puts session + cache on Redis, the parent's authenticateConnection() opens Redis
sockets before forking; a child touching any un-reset connection interleaves on the
shared fd and desyncs the predis protocol on BOTH sides — flooding
"unserialize(): Error at offset 0 of N bytes" and
"Predis ConnectionException: Error while reading line from the server".

In the child, before purging: disconnect() every configured redis connection (closes
only the child's fd copy — predis disconnect fcloses without sending a command, so
the parent socket is untouched; any later reuse reconnects fresh instead of
desyncing), then also forgetInstance('session') + 'session.store' so a Redis-backed
session store rebuilds lazily too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-14 11:48:26 +02:00
Fabian @ Blax Software b678a25a3c feat(maintenance): per-message choke to freeze non-admins during platform maintenance
Read the shared platform:maintenance DB-cache flag at controll_message (after
auth resolves) and refuse every controller event for non-admins with
{message, maintenance:true}, so a host app can run a deploy / write-locking
migration without clients mutating state. Admins bypass to smoke-test. The key
is hardcoded to match App\Support\Maintenance::CACHE_KEY and dodge the
fork-child package-config-merge gap; cache reads are guarded so a hiccup can
never wedge the socket.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-06 10:42:34 +02:00
Fabian @ Blax Software ed15ef8c23 fix(auth): re-establish auth guard in fork child; suppress EPIPE on peer-gone write
The deferred-message fork path (processDeferredMessages -> forkWithSocketPair)
never re-runs authenticateConnection, so the child inherited $connection->user
(passing the need_auth gate) but a cleared Auth guard — auth()->user() /
User::auth() returned null, crashing handlers that read the guard (learn-atc
GlitchTip #529/#532/#535/#531/#530). Sync the guard from the mock's user per
fork with Auth::setUser() (setUser, not login, to avoid re-firing the Login
event on every message).

Also guard SocketPairIpc::sendToParent with @socket_write: when the WS client
disconnects mid-response the parent tears down its read end, so the child's next
write races a closed pipe (EPIPE / "Broken pipe"). That benign warning was
promoted to a reported ErrorException and flooded GlitchTip (#461). Mirrors the
existing @fwrite in Broadcast/BroadcastClient.php.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-04 10:21:44 +02:00
Fabian @ Blax Software 1bd747835f fix(server): retry port bind on EADDRINUSE during restart handover
A restarting daemon (deploy, supervisor, steer restart-hard) often finds
the predecessor's socket still bound for a few seconds after SIGTERM.
Crashing immediately turned every restart into an EADDRINUSE crash-loop
(146 GlitchTip events on learn-atc prod). Retry the bind for up to 30s
before giving up; a port still taken after that is a real conflict and
rethrows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 08:41:50 +02:00
Fabian @ Blax Software fa32057035 fix(server): reliably reap fork-per-message child processes (zombie leak)
The fork-per-message model spawns a child per inbound message; each child
exit(0)s when done. The only reaper was a single, non-looping
pcntl_waitpid(-1, WNOHANG) inside the IPC onClose callback — which fires on
socket EOF, an event that routinely precedes the child's actual process exit,
so it frequently reaped nothing and left <defunct> zombies parented to the
live serve process (where no PID 1 init can reach them). Under sustained
traffic these accumulated until pcntl_fork() itself failed.

- Handler::onClose now drains ALL exited children in a loop (was a single
  wait). activeChildCount bookkeeping stays per-socket, untouched.
- StartServer adds a SIGCHLD handler + a 10s periodic backstop (catches
  children that crashed before IPC setup, so no onClose fires) + a
  drain-on-shutdown so a deploy restart does not hand orphans to PID 1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 11:06:54 +02:00
Fabian @ Blax Software 32a0a8f4cc docs(agents): add AGENTS.md source-verified reference + fix stale wsSession/install docs
- AGENTS.md: canonical agent/contributor reference — #[Websocket] attribute vs native
  Websocket\Controller, the :progress/:response/:error wire protocol,
  progress/success/error/broadcast/whisper, ws_broadcast/ws_whisper/wsSession globals,
  the fork-per-message execution model, auth()->user() over the bridge, defer() vs Jobs,
  resolver cache/restart, and a footgun checklist with a source map.
- README + docs/advanced-usage/helpers-and-testing: replace the fictional
  wsSession('channel', [...]) "auth payload" example — wsSession() takes no arguments and
  returns a per-connection store — and fix WebsocketService::getAuth($socketId) signature.
- docs/getting-started/installation: beyondcode -> blax-software composer require.
2026-06-30 10:56:25 +02:00
Fabian @ Blax Software a0d4699cdf fix(handler): improve exception reporting to Sentry by honoring application's report policy 2026-06-17 13:39:08 +02:00
Fabian @ Blax Software 1c0eefa160 docs: rewrite README with feature overview, watch command, and star history
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 16:44:26 +02:00
Fabian @ Blax Software cda4d34086 BF validation 2026-05-03 10:33:58 +02:00
Fabian @ Blax Software f40d183271 fix(controller): default $socketIds in whisper() to silence PHP 8.x deprecation
Optional parameter $payload (and $event) declared before required
parameter $socketIds is implicitly treated as required by PHP 8.4+,
emitting a deprecation on every class load:

  PHP Deprecated: Optional parameter $payload declared before
  required parameter $socketIds is implicitly treated as a required
  parameter

Made $socketIds optional (= []) — empty matches no connections via
array_flip lookup, so callers that intentionally omit it get a no-op
which preserves the practical contract (you can't whisper to nobody).
All four parameters are now optional. No call-site changes needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 14:36:12 +02:00
Fabian @ Blax Software f030ff1fbf feat: integrate websocket routes into Laravel's route list; implement RouteListInjector for attribute-tagged methods and legacy controllers; add tests for route injection and collection 2026-04-29 09:26:53 +02:00
Fabian @ Blax Software be3c7400fe feat: enhance websocket attribute handling with prefix and suffix support; add HTTP controller fallback for event resolution; implement comprehensive tests for dispatcher and event registry 2026-04-29 08:29:49 +02:00
Fabian @ Blax Software fb84abb464 feat: websocket attribute 2026-04-28 20:17:37 +02:00
Fabian @ Blax Software dd6be893a6 BF cache-key slug drift on ws_socket_auth_*; resilient User column in watch -v
Handler::cacheAuthenticatedUser() and ::cleanupChannelConnections() were
writing/forgetting ws_socket_auth_<rawSocketId> while
WebsocketService::getAuth() and ::setUserAuthed() have always slugged
("123.456" → "123-456"). Result: the cache write was reachable from the
package's own writer path but not from the service-layer reader, so the
admin tooling (websockets:watch -v) saw cache misses and rendered #<id>
instead of the configured IdentityFormatter output.

Also: WatchStats now batch-loads missing users via the configured auth
provider model in one query per render, so the User column still renders
the full formatter shape even when the per-socket cache blob predates
the writer or got evicted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:56:07 +02:00
Fabian @ Blax Software a46243a706 F IdentityFormatter contract — apps override how the User column renders
websockets:watch -v now delegates User-column rendering to a bound
IdentityFormatter. The package ships DefaultIdentityFormatter which
produces #<id> - <name> | <username> - <email> for typical Eloquent users
(any field absent = that segment dropped). Apps with non-User auth
subjects (Company, ApiClient, multi-tenant blobs) can implement the
contract and either bind it in their service provider or name it in
config/websockets.php as 'identity_formatter'. Resolution order is:
explicit container binding > config class > package default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:50:22 +02:00
Fabian @ Blax Software e45d8dff20 F websockets:watch -v — per-connection rows (socket id, user, duration)
Verbose mode reuses Symfony's built-in -v flag (the short option is
reserved, so a custom one can't be added). Each channel summary is
followed by sub-rows showing the socket id, the authed user (falling
back to "Guest" or the user id if the user blob expired), and how long
the connection has been open.

Connection start times are written by Handler::establishConnection() into
ws_connection_<slug(socketId)> on open and forgotten on close. Also
fixed a latent bug in finalizeConnectionClose() where the forget key was
missing the slug() call that the reader has always used — previously
invisible because nothing wrote the key, now load-bearing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:42:26 +02:00
Fabian @ Blax Software e1abef4194 F websockets:watch — live-updating stats display, refreshes every 1s
Renders the same Live Stats / channels table that `websockets:info` shows
at the bottom, but loops indefinitely so it can be left open as a quick
status pane. 1-second poll against the existing WebsocketService cache
reads — no pub/sub plumbing because there is no "stats changed" event
emitted today and a 1s tick is fast enough for the granularity humans
care about.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:31:25 +02:00
Fabian @ Blax Software 18c58b9be6 F websocket:restart-hard — direct-signal restart that confirms PID swap
The legacy `websockets:restart` and `websocket:steer restart` rely on the
running server polling a cache key every ~5s, then unwinding the loop.
That fails silently when the cache driver differs across processes, the
poll loop stalls, or the deploy script needs to confirm the restart
happened. This adds a command that pgreps the running process, sends
SIGTERM directly (the existing PCNTL handler in StartServer already
catches it), then waits for supervisord's autorestart to bring up a new
PID before returning. Designed to be invoked from deploy scripts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:19:38 +02:00
Fabian @ Blax Software a3194247c4 BF nullable deprecations 2026-04-23 11:56:50 +02:00
Fabian @ Blax Software 90e51c1bdf I open presence channel test 2026-04-21 16:52:44 +02:00
Fabian @ Blax Software 2ad8d490b7 fix: harden IPC callbacks and decouple auth lookup via configurable resolver 2026-04-17 11:03:02 +02:00
Fabian @ Blax Software ed371ac051 fix: catch Throwable in fork child, protect class_exists in scanning 2026-04-16 09:24:11 +02:00
Fabian @ Blax Software 9e7c2575f6 feat: WS introspection - list controllers and methods for debugging
- Send 'websocket' to list all controllers with methods and metadata
- Send 'auth' to list all methods on AuthController
- Shows need_auth, lifecycle hooks (boot/booted/unboot) per controller
- Only enabled in local env or via WEBSOCKET_INTROSPECTION=true
- Never active in production unless explicitly enabled
2026-04-16 09:02:25 +02:00
Fabian @ Blax Software 3d41c81a48 docs: add copilot instructions 2026-04-16 08:27:28 +02:00
Fabian @ Blax Software 859fcb6f89 feat: verbose logging, file persistence, auto websocket log channel
- WebSocketHandler: log connection rejections, unknown app keys, message drops
- Logger: persist all output to file via Laravel Log facade
- ServiceProvider: auto-register 'websocket' daily log channel
2026-04-16 08:17:39 +02:00
Fabian @ Blax Software 781e329601 feat: add websockets:info command with live stats 2026-04-15 10:07:59 +02:00
Fabian @ Blax Software 488f068140 C linting 2026-04-02 12:44:31 +02:00
Fabian @ Blax Software 093bbe3a44 A tests, documentation for helpers and lifecycle
- Introduced `helpers-and-testing.md` to document global helpers and WebsocketService class usage.
- Created `HandlerLifecycleTest.php` to test the full WebSocket handler lifecycle, including connection management, channel subscriptions, and message routing.
- Added `WebsocketServiceTest.php` to validate state tracking methods in WebsocketService, covering user authentication, channel tracking, and broadcast functionality.
2026-04-02 12:44:16 +02:00
Fabian @ Blax Software 77e12db729 BF long lasting subscription 2026-03-27 09:38:07 +01:00
Fabian @ Blax Software 7dd2df48d1 I DB connecting & auth handling 2026-03-23 14:13:30 +01:00
Fabian @ Blax Software c77eec57c1 I sentry reporting 2026-03-23 10:15:45 +01:00
Fabian @ Blax Software bdcfcd00f1 I pint 2026-03-21 12:57:54 +01:00
Fabian @ Blax Software 0757e6d9f9 I stability 2026-03-21 12:56:50 +01:00
Fabian @ Blax Software f191c44634 BF pong 2026-03-21 10:27:03 +01:00
Fabian @ Blax Software d091ebbd82 I last ponged timestamp 2026-03-21 10:01:10 +01:00
Fabian @ Blax Software 35b2731349 I max concurrency handling, BF restart 2026-03-20 13:44:51 +01:00
Fabian @ Blax Software a2a5524637 A steer 2026-03-14 09:32:24 +01:00
Fabian @ Blax Software 636d9eccb8 I parent connection states 2026-03-05 12:26:56 +01:00
Fabian @ Blax Software 463e633713 BF error 2026-02-10 14:05:36 +01:00
Fabian @ Blax Software 2a47591907 R cache, connection session, performance 2026-02-09 13:50:42 +01:00
Fabian @ Blax Software 7c1fca5c38 R performance improvements 2026-02-09 13:19:09 +01:00
Fabian @ Blax Software 1eafa237a0 R removed polling, transitioned to kernel events 2026-02-09 13:04:22 +01:00
Fabian @ Blax Software cc78a9b4d5 IBF broadcasting socket 2026-02-03 16:30:59 +01:00
Fabian @ Blax Software 0f54c414a2 BF broadcasting server 2026-02-03 15:45:27 +01:00
Fabian @ Blax Software b621a69015 BF broadcasting server 2026-02-03 15:38:57 +01:00
Fabian @ Blax Software 826c198a77 IA helpers 2026-02-03 15:03:50 +01:00
Fabian @ Blax Software 986ce76fb7 IA hotreloads 2026-02-02 13:20:39 +01:00
Fabian @ Blax Software f7e08a337b BF whisper, broadcast 2026-02-02 11:24:54 +01:00
Fabian @ Blax Software 61d4eb282e BH hrm 2026-01-26 11:52:53 +01:00
Fabian @ Blax Software 935bfb28d3 A hotreload 2026-01-26 10:51:13 +01:00