Repository navigation
feat(web): no-install mode, the dashboard tab drains the delivery queue - #8
Merged
Merged
Conversation
The browser bridge forwarded each poll snapshot in parallel, so a backlog went out of order, a refresh could double-send, and a stopped local server burned every event's attempts. It was also unreachable for signed-in users: the account dashboard took over /dashboard and the bridge page was only rendered in self-host mode. QueueDrainer walks ?pending=1 oldest first with a keyset cursor, claims each event (heartbeat while localhost thinks), forwards, reports, and stops at the first event when localhost is down or blocks CORS, so order holds and outages cost no attempts. A poison event is skipped after three real failures. forwardForDrain tells the cases apart with two probes, since every fetch failure is the same TypeError. LocalhostBanner says the server is down with the queued count, or shows the CORS line for eight frameworks with this origin filled in. The page is back at /dashboard/bridge, in the nav and in the empty Channels card. Replays go to the channel's own URL.
Reviewer's GuideThe PR turns the authenticated dashboard tab into a no-install webhook executor by adding an ordered, claim-based delivery queue with outage/CORS-aware retry behavior, exposing the bridge route and diagnostics UI, and routing replays through each channel’s webhook URL. Sequence diagram for ordered browser-tab webhook drainingsequenceDiagram
participant Dashboard
participant QueueDrainer
participant Relay
participant Localhost
Dashboard->>QueueDrainer: wake()
QueueDrainer->>Relay: getPendingEvents(channelId, after)
Relay-->>QueueDrainer: oldest events and nextCursor
QueueDrainer->>Relay: claimEvent(eventId, clientId)
Relay-->>QueueDrainer: claim granted
QueueDrainer->>Localhost: forwardForDrain(event, port)
alt Localhost responds
Localhost-->>QueueDrainer: response
QueueDrainer->>Relay: sendResponse(channelId, eventId, response)
QueueDrainer->>Dashboard: onDelivered(event, response)
else Localhost is down or CORS blocked
Localhost-->>QueueDrainer: down or cors
QueueDrainer->>Dashboard: onLocalhost(state, message)
Note over QueueDrainer: Stop at this event and retry later
else Real forwarding failure
Localhost-->>QueueDrainer: failed
QueueDrainer->>QueueDrainer: retry up to MAX_ATTEMPTS
QueueDrainer->>Relay: sendResponse(channelId, eventId, status 0)
end
QueueDrainer->>Relay: getPendingEvents(channelId, nextCursor)
Flow diagram for localhost failure classificationflowchart TD
A[forwardForDrain] --> B{Webhook request succeeds}
B -->|Yes| C[Return response]
B -->|No| D[GET localhost root with CORS]
D -->|Succeeds| E[Return failed]
D -->|Fails| F[GET localhost root with no-cors]
F -->|Succeeds| G[Return cors]
F -->|Fails| H[Return down]
G --> I[QueueDrainer stops and retries without consuming attempts]
H --> I
E --> J[Retry event and skip after MAX_ATTEMPTS]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
⛔ Files ignored due to path filters (1)
📒 Files selected for processing (10)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Review follow-ups on no-install mode: - While localhost is down or blocks CORS (or after a failed attempt), probe before claiming, so a tab that cannot deliver stops refreshing its claim and the extension takes the event over after 60s. - The readability probe GETs the event's path with the event's header names, so it needs the same CORS preflight as the real forward; a server that only answers simple requests now gets the CORS guide instead of having every webhook dropped as status 0. - A claim held elsewhere stops the pass (order holds) and the hook re-wakes in 15s, so an executor that died mid-forward is taken over. - sendResponse throws when the relay refuses the answer and shrinks oversize bodies/headers on 413/431; the drainer re-sends an owed answer instead of calling the handler again. - Forward and report no longer use the bridge's abort signal, so leaving the page mid-forward does not re-run the handler on return. - A 5-minute timeout is recorded once (status 0), not retried while the handler may still be on a breakpoint. - A heartbeat that finds the claim lost skips the report. - connect resets the localhost banner; a stopped drainer and its probes (now abortable) cannot write state; a pass blocked by another executor no longer clears a down state.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes the browser tab a real executor, so BridgeHook works with nothing installed.
What changes
QueueDrainer(apps/web/src/lib/drain.ts) drainsGET /api/channels/:id/events?pending=1oldest first with a keyset cursor: claim (20 s heartbeat), forward, report. It stops at the first event when localhost is down or blocks CORS, so order holds and outages never use up attempts. A poison event is skipped after 3 real failures.forwardForDrainsorts out what went wrong with two probes, since a page sees every failure as the same TypeError: a CORS GET succeeds → a real failure; a no-cors GET succeeds → CORS is blocked; neither → the server is down. Ano-corsdelivery fallback was rejected: it drops headers and rewrites the content type.LocalhostBannershows "can't reach localhost:N, X webhooks queued", or a CORS guide with copyable snippets for Express, Hono, Next.js, FastAPI, Flask, Django, Rails and Go, with this origin filled in./dashboard/bridge. It was only rendered in self-host mode after the account dashboard took/dashboard. Links added in the nav and in the empty Channels card.Verified
vitest: 7/7 drainer tests (multi-page backlog in order exactly once, outage resume, CORS hold, poison skip, claims held elsewhere, wake coalescing).vite buildclean.Closes HAL-2371
Summary by Sourcery
Make the dashboard browser tab a reliable no-install webhook executor for local development.
New Features:
/dashboard/bridgeand expose it through dashboard navigation and empty-channel onboarding.Bug Fixes:
Enhancements:
Tests:
Summary by CodeRabbit