pollsockets/pollsockets-laravel
Official Laravel integration for PollSockets, making it easy to add real-time polls, live voting, and websocket-powered results to Laravel apps. Includes configuration, service provider wiring, and helpers for broadcasting poll updates, keeping results in sync, and getting started quickly.
Package: pollsockets/pollsockets-laravel — Laravel package providing HTTP-polling-based realtime subscriptions (a "sockets without WebSockets" transport), with server-side channel/subscription management backing the PollSockets client SDKs.
Release under review: 2.0.0 (from 1.4.0). Changelog content is a single line: "v2: millisecond cursor and client origin" (PR #23). The major-version bump, not the prose, is the primary signal — we should treat this as a breaking release until proven otherwise.
Architecture fit. The package occupies the realtime-notification layer of a Laravel app: a poll endpoint the browser hits on an interval, a cursor marking "how far the client has read," and channel authorization reusing Laravel's auth/guard model. That remains a sound fit for our use case (low-to-moderate fan-out notifications, no sticky-session/WebSocket infrastructure, works behind standard HTTP load balancers and CDNs with the poll route excluded from cache). Two dimensions change in 2.0.0:
Integration feasibility. Low-to-moderate effort, but it is a coordinated upgrade, not a drop-in. The package must be upgraded in lockstep with every PollSockets client SDK (JS/browser, and any mobile SDK) because the cursor is produced by the server and round-tripped by the client. We control the browser client, so feasibility is good; the constraint is that we cannot do a partial rollout — a v1 client talking to a v2 server will send a v1 cursor and will either error or silently re-receive the backlog.
Technical risk. Medium-high, concentrated in three places:
localStorage, in a since query parameter, in a mobile app's local DB, or in a resumed session — is a v1-format value. On first poll after deploy it will be rejected, coerced, or interpreted as epoch-0, producing either a recoverable full backlog replay or an unrecoverable error loop. Replay storms are the realistic failure: every open tab reconnects and requests "everything since the beginning."Origin header (proxy, CDN, mobile webview) could silently route a client into an empty or wrong channel stream — hard to detect in monitoring because the request still returns 200.Key questions for the maintainer / spike (blocking the estimate):
Origin header, a client-generated ID, or connection provenance? Does it participate in authorization or only in telemetry/routing?Stack fit. Laravel-native package, no new infrastructure (no Redis pub/sub requirement introduced by this release, no socket server). It slots into the existing HTTP tier. The two things it touches that we care about are (a) the client bundle, which we version and ship ourselves, and (b) our runtime matrix (PHP/Laravel), which must be re-verified against 2.0.0's composer constraints before commitment.
Migration path. Recommended: server-side dual-accept shim, then client rollout, then shim removal. Concretely — deploy a thin adapter in front of the package's poll handler that inspects the incoming cursor and, if it is in v1 format, translates it to the v2 millisecond cursor (or, if translation is not reliably possible, responds with an explicit "resync required" signal the v1 client already knows how to handle by resetting to now). This converts a hard cutover into an ordinary rolling deploy. If the maintainer offers a config-level compatibility mode, use that instead — it is cheaper and better supported.
Compatibility matrix to validate before scheduling:
| Surface | v1 client + v1 server | v1 client + v2 server | v2 client + v2 server |
|---|---|---|---|
| Cursor | works | at risk — must be shimmed or forced-reset | works |
| Payload ("origin") | works | additive field, likely tolerated | works |
| Auth/channel | works | re-verify if origin participates in authz | re-verify |
The only cell that must be engineered away is the middle one (v1 client, v2 server), and it exists for exactly as long as it takes open tabs to reload.
Sequencing.
/poll.Do not bundle this with an unrelated release. The skew window is the whole risk, and it needs a clean change log of its own.
Maintenance. Net positive if the team is small: millisecond cursors remove a class of duplicate/dropped-message bugs we would otherwise carry as application-level workarounds, and the "origin" dimension likely removes bespoke correlation code from our consumer. Cost is one forced client upgrade and a permanent, documented dependency: our client bundle version is now coupled to the server package version. That coupling must be recorded in the runbook — future minor bumps may again touch the cursor contract, and "it's only a minor" will not be a safe assumption.
Support. Expect a short tail of user-visible weirdness during the skew window: stale tabs that never reloaded, mobile webviews with cached JS, and users resuming a backgrounded tab. The support-facing symptom is "notifications stopped" or "I got a flood of old notifications" — both map to cursor mismatch. Prepare a one-line remedy for the help desk (hard reload / clear site data) and a dashboard that separates poll requests returning errors from poll requests returning an unexpectedly large batch (the latter is the replay-storm signature and is invisible in a pure error-rate view).
Scaling. Millisecond cursors do not change request volume — polling is polling. Two things to watch: (1) a replay storm is a genuine load event, since each reconnecting client requests the full backlog at once; the poll endpoint should have a per-response batch cap and a rate limit configured before rollout, not after; (2) if "client origin" causes the server to track state per origin rather than per subscription, fan-out memory grows with tab count — verify whether that state is in-process or in a shared store, because in-process state breaks horizontal scaling and requires sticky routing we do not currently have.
Failure modes.
| Failure | Trigger | Detection | Blast radius |
|---|---|---|---|
| Cursor parse rejection loop | v1 client, v2 server, no shim | 4xx spike on /poll |
all open sessions at deploy |
| Backlog replay storm | same, if unparseable cursor means "from the beginning" | batch-size anomaly, DB read latency | whole app, not just realtime |
| Silent empty stream | origin mismatch via proxy/CDN/webview | no errors — only absence of deliveries | subset of users, undetectable by error rate |
| Client bundle skew | cached v1 JS after server upgrade | client version telemetry (must exist) | long-lived tabs, mobile |
| Runtime incompatibility | PHP/Laravel below 2.0.0 floor | deploy-time composer resolution | blocked release, caught early |
The silent-empty-stream row is the one to design against: it produces no error signal, so it needs an active health check — a synthetic client that subscribes and asserts receipt of a server-emitted heartbeat within N seconds.
Ramp-up. Half a day for a Laravel-fluent engineer who has not touched the package, given: the v1→v2 cursor note, the shim (or the decision not to have one), the "origin" semantics, and the resync runbook. The concepts (channel, subscription, cursor, poll loop) are simple; the difficulty is entirely in the version-skew choreography, which is why step 2 of the sequencing — an explicit written compatibility decision — is the deliverable that matters. Absent that, the estimate for this upgrade is not credible and should not be committed.
How can I help you explore Laravel packages today?