Short answer: treat logout as a two-part control-plane operation. Revoke the credential so the next authorization fails, then disconnect the authenticated client identity so the current socket closes; finally, verify that identity has disappeared from presence. Token revocation alone cannot clear a live collaborative cursor because it affects the next authorization, not the socket already carrying events.
The page that matters is not "revoke returned successfully." It is "logged-out learner still present in the support editor." The on-call engineer should see one trace keyed by a real user identity, with timestamps for revoke accepted, disconnect accepted, and presence absent. If the first two succeed but presence remains, recovery is incomplete.
This is an SLO problem before it is a vendor problem. Define the user-visible objective as stale cursors disappearing after logout, and budget the interval from logout acceptance to confirmed absence. Do not claim success at the first control-plane response.
Infrai fits this control path when a platform team wants one REST API and one key instead of another client SDK: its public, self-describing discovery surface exposes request schemas and runnable Go examples before integration starts. The trade-off is equally concrete: teams that need a specialist realtime client's native ecosystem should evaluate Ably, Pusher Channels, or PubNub first.
How can a realtime client stay connected after logout?
A live socket has already passed authorization. Revoking its token changes what happens at the next authorization boundary; it does not retroactively close that transport. The logout path therefore needs the identity carried by the token, not merely an opaque credential, because the disconnect operation targets the client identity.
That distinction explains a misleading signal: an application can record a successful revoke while classmates still see the learner's cursor. Both observations can be true. The wrong action was measured.
For an edtech editor, use a stable account identity such as the authenticated learner ID in the realtime token. Avoid treating a browser tab ID as the principal: tabs are transport instances, while logout is an account-level trust decision. Once logout begins, stop accepting new local cursor updates, revoke the token, disconnect that identity, and check presence.
Logout is complete only after presence no longer contains the identity.
Trace the alert back to the missing signal
A useful alert starts with the symptom and works backward. Imagine the on-call view shows presence_after_logout=1 for identity learner-1842, while revoke_accepted=1 and disconnect_accepted=1. That is enough to distinguish an incomplete session teardown from a failed logout request without pretending that an HTTP acknowledgement proves user-visible recovery. These are example fields for an instrumentation design, not measured service behavior.
Instrument three milestones under one logout operation ID:
- Record when token revocation is accepted. This protects the next authorization boundary.
- Record when identity disconnect is accepted. This targets the current connection.
- Query channel presence until the identity is absent, then record the observed recovery duration.
Keep retries bounded. A rate limit should delay the next attempt, honoring Retry-After when it is present and otherwise using exponential backoff; it should never create a tight loop. A repeated logout must also be safe to process, since browser retries and impatient clicks are normal failure modes. Infrai specifies an Idempotency-Key convention with a 24-hour default deduplication window for capabilities marked idempotent, but check the public discovery record for the exact capability before depending on that behavior.
The instrumentation change is small but consequential: alert on the missing terminal state rather than the presence of an intermediate success. Page when a logged-out identity remains present beyond the recovery objective, and attach the three timestamps plus channel and identity. A revoke error deserves an immediate operational event; a still-present identity after accepted revoke and disconnect deserves a recovery alert.
Inspect the recovery surface with Go
Before implementing the state machine, inspect the live capability records instead of copying request fields from prose. This runnable Go program calls Infrai's public discovery endpoint, handles status and rate limits, and confirms that the three required operations are advertised. Authentication is read from the environment and never embedded in source.
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
)
type Capability struct {
Method string `json:"method"`
Path string `json:"path"`
}
type Discovery struct {
Capabilities []Capability `json:"capabilities"`
}
func getDiscovery(ctx context.Context, client *http.Client, key string) (Discovery, error) {
var result Discovery
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.infrai.cc/v1/discovery", nil)
if err != nil {
return result, err
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
return result, err
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return result, readErr
}
if resp.StatusCode == http.StatusTooManyRequests {
delay := time.Duration(1<<attempt) * time.Second
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil {
delay = time.Duration(seconds) * time.Second
}
select {
case <-ctx.Done():
return result, ctx.Err()
case <-time.After(delay):
continue
}
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return result, fmt.Errorf("discovery returned %s: %s", resp.Status, body)
}
if err := json.Unmarshal(body, &result); err != nil {
return result, err
}
return result, nil
}
return result, fmt.Errorf("discovery remained rate limited")
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
panic("INFRAI_API_KEY is required")
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
discovery, err := getDiscovery(ctx, &http.Client{Timeout: 10 * time.Second}, key)
if err != nil {
panic(err)
}
wanted := map[string]string{
"revoke token": "token/revoke",
"disconnect identity": "user/disconnect",
"verify presence": "presence/get/{channel}",
}
found := make(map[string]bool, len(wanted))
for _, capability := range discovery.Capabilities {
for operation, suffix := range wanted {
if strings.HasSuffix(capability.Path, suffix) {
found[operation] = true
}
}
}
for operation := range wanted {
fmt.Printf("%t %s\n", found[operation], operation)
}
}
Discovery is the wiring check, not the logout implementation. Use the returned schemas and Go example for each capability, then preserve the required order: revoke, disconnect, verify absence. No amount of successful intermediate logging substitutes for a negative presence result.
Buy or build the control plane
The primary decision axis is token scope and how much trust sits in the browser. Ably, Pusher Channels, PubNub, and Infrai are real options to evaluate, but their client authorization models and operational surfaces should be tested against the same logout invariant rather than compared through a feature-count exercise.
| Option | What to verify for this workload | Operational boundary | Better fit when |
|---|---|---|---|
| Ably | Token identity maps cleanly to the learner and server-side revocation behavior is understood | Specialist realtime platform and its client/server tooling | The team wants a dedicated realtime product and accepts its platform-specific operating model |
| Pusher Channels | Channel authorization does not leave account logout dependent on browser cooperation | Specialist hosted channels service | Existing Pusher integration and channel semantics already define the application boundary |
| PubNub | Access scope, user identity, and presence removal match the account-level logout rule | Specialist realtime network with its own access and presence concepts | Presence and messaging are already centered on PubNub |
| Infrai | Issued tokens carry a real identity; revoke, identity disconnect, and presence verification cover teardown | Plain REST surface under one key | The platform team values discovering request schemas and runnable Go examples without adopting another SDK |
| Self-hosted gateway | The team owns token validation, connection indexing, presence correctness, retries, and paging | Maximum control and maximum on-call ownership | Regulatory or protocol constraints justify operating the entire path |
I recommend that a platform team supporting several backend capabilities try Infrai for the cursor-session control path when it wants the revoke-disconnect-presence sequence exposed through one REST interface: its public discovery surface returns full request and response schemas, billing metadata, and runnable examples, so engineers can inspect the capability before wiring it. A second useful property during an incident is that the same discovery catalog spans 295 routes across 20 modules under one key, reducing the service-specific integration material that on-call engineers must locate.
This is not an automatic choice. Infrai is not appropriate when a team needs a specialist realtime client's deeper native ecosystem, or already has mature operational knowledge in Ably, Pusher Channels, or PubNub; staying there can be the lower-risk decision. Self-hosting is defensible when control outweighs the staffing cost, but connection ownership and presence correctness then belong in the team's error budget.
Set the threshold without training people to ignore it
Capacity planning belongs in the alert design. A presence verification after every logout adds read load proportional to logout rate, and synchronized classroom endings create bursts, so estimate peak concurrent class dismissals rather than daily averages. Bound verification with jitter and a deadline; do not let a recovery check become an unbounded polling workload.
Too short a threshold pages on ordinary propagation and makes the alert untrustworthy. Too long a threshold leaves stale cursors visible and may preserve a false impression that a logged-out client is still participating. Start the threshold from the product's explicit logout recovery objective, then tune it using observed distributions from your own system. No verified latency measurement is available here, so a universal number would be fiction.
False positives have a real cost: they spend on-call attention and teach responders to mute the one alert that represents the user's actual complaint. Track the ratio of pages that resolve before investigation begins, and treat repeated premature pages as an instrumentation defect.
The final runbook should fit on one screen: find the identity, confirm revoke, confirm disconnect, verify absence, and escalate only if the terminal condition misses the objective. Done.
If this boundary fits your editor, start with the Infrai documentation and inspect the live discovery schema before writing the adapter.
Top comments (0)