Skip to content

fix: harden dialer reconnect lifecycle + unblock Next build#342

Merged
coygg merged 7 commits into
masterfrom
fix/dialer-reconnect-lifecycle
Apr 8, 2026
Merged

fix: harden dialer reconnect lifecycle + unblock Next build#342
coygg merged 7 commits into
masterfrom
fix/dialer-reconnect-lifecycle

Conversation

@coygg
Copy link
Copy Markdown
Collaborator

@coygg coygg commented Apr 7, 2026

Summaryn- harden Telnyx reconnect lifecycle on the dialer pathn- add a functional src/middleware shim so Next 16 build stops failing on ./src/middleware.tsnn## Validationn- npm run build ?n- npx vitest run src/tests/telnyx-lifecycle.test.ts ?n- npm test ?n- npm run test:coverage ?`n- npm run lint ?? blocked by pre-existing repo-wide eslint issues unrelated to this change

Summary by CodeRabbit

  • New Features

    • Adaptive connection recovery with event-specific delays and interaction throttling.
    • Dynamic registration health checks with shorter cadence for hidden tabs.
    • Interaction- and focus-driven recovery plus immediate event-triggered recovery.
    • Presence leadership triggers immediate heartbeat behavior.
    • Per-request nonce propagated to theming; centralized middleware activated.
    • Call initiation now awaits readiness and returns success/failure.
  • Bug Fixes

    • Improved reconnection handling for socket disconnects and SDK errors.
  • Tests

    • Added suites validating lifecycle timing, interaction throttling, and presence heartbeat logic.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
policyjar Ready Ready Preview, Comment Apr 8, 2026 5:08am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 7, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Telnyx lifecycle utility module and tests, integrates lifecycle-driven recovery and visibility-aware health checks into useTelnyx (including async makeCall), gates presence heartbeats to the elected leader with tests, converts RootLayout to async to pass a per-request nonce, and enables Next.js middleware with a matcher.

Changes

Cohort / File(s) Summary
Telnyx lifecycle module & tests
src/lib/telnyx-lifecycle.ts, src/__tests__/telnyx-lifecycle.test.ts
New lifecycle utilities and types (TelnyxRecoveryReason, getLifecycleRecoveryDelay, getRegistrationHealthCheckIntervalMs, shouldRunInteractionRecovery) plus tests validating delay mapping, visibility-based interval selection, and interaction-throttle behavior.
useTelnyx hook integration
src/hooks/use-telnyx.ts
Integrates lifecycle utilities for recovery delays and registration-check cadence; adds refs to avoid stale closures; expands recovery triggers (socket-close-active/idle, sdk-error, pageshow, focus, throttled pointerdown/keydown); replaces fixed interval with visibility-aware self-rescheduling timeout; makeCall is now async and returns Promise<boolean>.
Presence heartbeat & tests
src/hooks/usePresence.ts, src/__tests__/use-presence-heartbeat.test.ts
Adds shouldSendPresenceHeartbeat(isLeader) export; heartbeats are gated to the elected leader, trigger immediate heartbeat on leadership acquisition, and tests assert leader vs non-leader behavior.
Middleware activation
src/middleware.ts
Replaces placeholder with active Next.js middleware(request: NextRequest) delegating to root middleware and exports config.matcher excluding api, _next static/image, favicon.ico, and common image extensions.
Root layout nonce plumbing
src/app/layout.tsx
Converts RootLayout to an async server component, reads the x-nonce request header, and passes nonce into ThemeProvider.
UI call flows updated to await makeCall
src/app/(app)/dialer/page.tsx, src/components/leads/lead-table.tsx, src/components/phone/softphone.tsx
Dial/Call handlers updated to async and await makeCall(...); they handle falsy return values (abort/mark failed) and conditionally clear inputs only on success.

Sequence Diagram

sequenceDiagram
    participant App as Client App
    participant Hook as useTelnyx Hook
    participant Lib as Telnyx Lifecycle Lib
    participant SDK as Telnyx SDK
    participant State as Connection State

    App->>Hook: user interaction / pageshow / visibility / socket close / sdk-error
    Hook->>Lib: shouldRunInteractionRecovery(now,lastAt)?
    Lib-->>Hook: allow / reject

    alt allow
        Hook->>Lib: getLifecycleRecoveryDelay(reason)
        Lib-->>Hook: delay(ms)
        Hook->>Hook: schedule runLifecycleRecovery (timeout)
        Hook->>SDK: runLifecycleRecovery() (immediate + scheduled)
        SDK->>State: attempt reconnect / re-register
        State-->>Hook: update connection/registration status
        Hook->>Hook: ensureRegistered('makeCall') (await)
        alt success
            Hook-->>App: makeCall resolves true
        else failure
            Hook-->>App: show error toast / return false
        end
    else throttled
        Hook-->>App: skip recovery (cooldown)
    end

    App->>Hook: periodic health-check loop
    Hook->>Lib: getRegistrationHealthCheckIntervalMs(visibilityState,...)
    Lib-->>Hook: interval(ms)
    Hook->>Hook: schedule next health-check (rescheduled timeout)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I twitch my whiskers at a socket sigh,
Timers set, cool-downs counted by my eye,
Heartbeats hop when I wear the crown,
Pages wake and I dance the reconnect-down,
A tiny rabbit fixes calls that fly.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: harden dialer reconnect lifecycle + unblock Next build' directly maps to the PR objectives: hardening Telnyx reconnect lifecycle and unblocking the Next.js 16 build failure by adding middleware.ts.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dialer-reconnect-lifecycle

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/hooks/use-telnyx.ts`:
- Around line 878-883: The visibilitychange handler should clear and re-arm the
periodic registrationCheckTimer so hidden/visible cadence updates take effect
immediately: in useTelnyx move registrationCheckTimer declaration to an outer
scope accessible by handleVisibilityChange, then inside handleVisibilityChange
clear any existing registrationCheckTimer and set a new timeout using
getLifecycleRecoveryDelay('visibilitychange') (but only call
runLifecycleRecovery immediately when visibilityState === 'visible'); ensure you
reference and update registrationCheckTimer, handleVisibilityChange,
runLifecycleRecovery, and getLifecycleRecoveryDelay so the timer is always
rescheduled on every visibilitychange.
- Around line 1134-1157: The makeCall callback currently fires an inner async
IIFE and returns void, so callers can't know whether a call was actually
started; change makeCall to be an async function that returns a Promise<boolean>
(or Promise<Call | null> if you prefer the call object) and resolve true when
clientRef.current.newCall succeeds (and set callRef.current), or resolve false
when recovery fails or an exception occurs (and still call showPresenceToast on
failure). Update the public type UseTelnyxReturn.makeCall to match the new
Promise return type, adjust the implementation to await
ensureRegisteredRef.current/runLifecycleRecoveryRef.current before creating the
call and then return the appropriate boolean/result, and update all callers
(e.g., dialer/page.tsx, softphone.tsx, lead-table.tsx) to await the result and
roll back UI/persistence when it returns false.

In `@src/middleware.ts`:
- Around line 8-11: The middleware matcher currently applies to all routes
(export const config.matcher) causing unauthenticated API calls to be redirected
by updateSession; change the matcher to explicitly exclude API routes (i.e.,
ensure any path beginning with /api is omitted from the matcher along with the
existing _next/static, _next/image, favicon.ico and asset extensions) so
middleware won't run for /api/* and individual API route handlers can enforce
authentication themselves.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 722a28e1-b5dd-46e2-9b0c-a5ea6ef864a1

📥 Commits

Reviewing files that changed from the base of the PR and between 7298852 and 8cdd746.

📒 Files selected for processing (4)
  • src/__tests__/telnyx-lifecycle.test.ts
  • src/hooks/use-telnyx.ts
  • src/lib/telnyx-lifecycle.ts
  • src/middleware.ts

Comment thread src/hooks/use-telnyx.ts
Comment thread src/hooks/use-telnyx.ts Outdated
Comment thread src/middleware.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/hooks/usePresence.ts`:
- Around line 18-20: The exported function shouldSendPresenceHeartbeat is
trivial and only used internally (called with presenceLeaderRef.current) while
external modules like AgentDiagnostic and TelnyxWrapper consume isLeader
directly; either remove the export and make shouldSendPresenceHeartbeat a
private/internal helper inside usePresence.ts (so only the hook uses it), or
keep the export but add a brief comment stating it exists solely for unit
testing; update references to use presenceLeaderRef.current where needed and
ensure no external code depends on shouldSendPresenceHeartbeat before removing
the export.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3e87a04d-79f1-4914-b702-22e3725151e6

📥 Commits

Reviewing files that changed from the base of the PR and between 8cdd746 and 9f4708f.

📒 Files selected for processing (2)
  • src/__tests__/use-presence-heartbeat.test.ts
  • src/hooks/usePresence.ts

Comment thread src/hooks/usePresence.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 02c8c8e5bc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/middleware.ts Outdated

export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep API routes in middleware matcher

The shim now excludes all /api/* paths from middleware execution, which means those requests no longer go through updateSession and the auth gate implemented in middleware.ts (that previously exempted only specific API prefixes like /api/webhooks, /api/auth, etc.). In practice this can let unauthenticated API calls bypass middleware-based protections and skip session refresh behavior for API traffic; the shim matcher should mirror the root matcher instead of globally excluding api.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/hooks/usePresence.ts`:
- Around line 412-413: The BroadcastChannel leader-win path sets
staleCheckNeededRef.current = true and calls sendHeartbeat(), but the
localStorage fallback leader-acquisition path does not, causing up-to-30s delay;
extract or add a shared "became leader" handler (e.g., create a
becameLeader/handleBecameLeader function) and invoke it from both the
BroadcastChannel leader branch and the localStorage fallback branch so that
staleCheckNeededRef.current is set and sendHeartbeat() is called immediately
when a tab becomes leader (respect existing gating logic around
isLeader/isLeaderRef).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ec65c47d-1c54-473a-aa77-2a9d9b47e542

📥 Commits

Reviewing files that changed from the base of the PR and between 0286ce5 and 02c8c8e.

📒 Files selected for processing (6)
  • src/app/(app)/dialer/page.tsx
  • src/components/leads/lead-table.tsx
  • src/components/phone/softphone.tsx
  • src/hooks/use-telnyx.ts
  • src/hooks/usePresence.ts
  • src/middleware.ts

Comment thread src/hooks/usePresence.ts Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 210c48c798

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/hooks/use-telnyx.ts
Comment on lines +895 to +897
void ensureRegistered('periodic').finally(() => {
scheduleRegistrationHealthCheck()
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prevent duplicate health-check timers after visibility flips

This reschedules the periodic registration timeout unconditionally after ensureRegistered('periodic') finishes, but handleVisibilityChange also starts a new timer whenever the tab visibility changes. If visibility changes while a periodic check is already in flight, you can end up with two independent timeout chains, which then keep running and cause redundant ensureRegistered calls/reconnect attempts. This is reproducible under slow network checks plus quick hide/show tab transitions.

Useful? React with 👍 / 👎.

@coygg coygg merged commit b6ca900 into master Apr 8, 2026
5 checks passed
@coygg coygg deleted the fix/dialer-reconnect-lifecycle branch June 1, 2026 07:04
coygg added a commit that referenced this pull request Jun 1, 2026
Merges PR #342 after CodeRabbit approval.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant