PR #7893 (“enable videoOrientExt for tablets”) aims to fix rotated WebRTC screenshots on iPads but is unreliable: modern iPadOS 13+ Safari reports a macOS desktop user-agent, so server-side ua-parser-js cannot distinguish an iPad from a Mac, and the new isTablet() check evaluates to false for exactly the devices the fix targets.
Verified Finding — the fix does not work for modern iPads
Modern iPad (iPadOS 13+, Safari) sends a
Macintosh; Intel Mac OS X ...user-agent.ua-parser-jsv1 parsing that string yieldsdevice.type === undefinedandbrowser.name === 'Safari'. The new expressionisTablet() || !(isSafari() || isMobile())then evaluates tofalse || !(true) = false, sovideoOrientExtstays disabled — the bug is not fixed. The PR only helps when the device’s UA actually parses asdevice.type === 'tablet'(older iPad UA, or a WebView wrapper with a custom UA).
Ticket Context
| Field | Value |
|---|---|
| Ticket | FKITDEV-8533 |
| PR | TechTeamer/vuer_oss #7893 |
| Head → Base | feature/FKITDEV-8533 → customization/generali-atvilagitas |
| Reviewer state | CHANGES_REQUESTED |
| Customer | Generali |
| Affected flow | Customer identification self-service (room 11216) |
Reported Symptom
Generali reported rotated WebRTC screenshots and recordings in the customer identification self-service flow. The commit message frames the root cause as: iPad Pro fails to pre-rotate video when switching cameras. The intended fix is to make Janus negotiate the video orientation RTP extension (videoOrientExt) for tablets, so the receiver corrects orientation.
How videoOrientExt Is Gated
videoOrientExt (the WebRTC urn:3gpp:video-orientation RTP header extension) is currently disabled for Safari and mobile by this expression at 4 call sites:
!((customer.isSafari()) || (customer.isMobile()))Call sites:
server/cv/VuerCVListenerSession.jsserver/socket/events/videochat.jsserver/transport/session/RoomTransportSession.jsserver/transport/session/SelfServiceTransportSession.js
What PR #7893 Changes
PR #7893 adds a new method Customer.prototype.isTablet() (in server/db/model/customer.js) and ORs it into each of the 4 sites:
customer.isTablet() || !((customer.isSafari()) || (customer.isMobile()))isTablet() uses ua-parser-js and returns true when device.type === 'tablet'.
Why the Fix Is Logically Hollow
For Agents
isTablet()is a strict logical subset ofisMobile(). The existingisMobile()already returns true whendevice.typeis'mobile'OR'tablet'. ThereforeisTablet()can never be true unlessisMobile()is also true.
This makes the new expression a clean dichotomy with no useful middle ground:
| Device detected as tablet? | isTablet() | isMobile() | New expr isTablet() || !(isSafari() || isMobile()) | Effect |
|---|---|---|---|---|
| Yes | true | true (already) | true | videoOrientExt enabled — but isMobile() already knew it was a tablet, so isTablet() is redundant |
| No | false | depends | !(isSafari() || isMobile()) — unchanged from before | Fix changes nothing |
So either:
- the device parses as a tablet →
isMobile()was already true, and the OR’disTablet()term is redundant; or - the device does not parse as a tablet →
isTablet()isfalse, and the expression collapses back to the original gate.
The PR adds a method that, by construction, cannot change behaviour beyond what isMobile() already determined.
The Real Problem: iPadOS Spoofs a macOS User-Agent
iPadOS 13+ Safari sends a desktop Mac UA
Since iPadOS 13, Safari on iPad requests sites with a desktop-class user-agent string:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/.... There is noiPadtoken in the string. Apple did this deliberately so iPads receive desktop site layouts.
Consequences for server-side detection:
ua-parser-jsparsing aMacintosh; Intel Mac OS Xstring returnsdevice.type === undefined— it cannot tell an iPad apart from a MacBook by UA string alone.browser.namecomes back as'Safari'.customer.userAgentis populated server-side viasetConnectionInfo(ip, locale, userAgent)— only the raw UA string reaches the server.- Safari does not send
Sec-CH-UAclient-hint headers, so there is no secondary signal the server could fall back to.
For a modern iPad in the Generali flow:
isTablet() = false (device.type undefined)
isMobile() = false (device.type undefined)
isSafari() = true (browser.name === 'Safari')
new expr = isTablet() || !(isSafari() || isMobile())
= false || !(true || false)
= false || !(true)
= false || false
= false
→ videoOrientExt stays disabled → rotated screenshots persist → bug not fixed.
The PR only works when the device’s UA genuinely yields device.type === 'tablet':
- an older iPad UA string (pre-iPadOS 13, or “Request Mobile Website” mode), or
- a WebView wrapper / native SDK that sets a custom UA containing a tablet token.
ua-parser-js Version Note
For Agents
The repo uses
ua-parser-js ^1.0.39(v1). A reviewer linked thewithFeatureCheckAPI — that API is documented on the ua-parser-js v2 docs site and is not available in the v1 the repo actually depends on. Any suggestion built onwithFeatureCheckdoes not apply without a major-version bump.
Correct Fix Direction
Server-side UA parsing alone cannot detect a modern iPad. The reliable signal lives on the client:
const isIPad = navigator.maxTouchPoints > 1 && /Macintosh/.test(navigator.userAgent);navigator.maxTouchPoints > 1 is true on iPads (touch screen) and false on Macs (Mac trackpads do not report touch points), so combined with the Macintosh UA token it positively identifies an iPad masquerading as a Mac.
The fix should:
- Run this detection client-side (in
vuer_css/ the customer browser). - Pass the result to the server (an explicit flag, not derived from the UA).
- Have the server gate
videoOrientExton that explicit flag rather than re-parsing the UA.
Reviewer State
PR #7893 is CHANGES_REQUESTED.
| Reviewer | Position | Assessment |
|---|---|---|
| Pocok256 | Flagged the isTablet() ⊆ isMobile() redundancy and the unreliable UA-based detection | Correct — matches this analysis |
| chrismakaay | Suggested narrowing isMobile() to match 'mobile' only | Does not address the bug; a refactor that risks breaking the other ~7 isMobile() callers |
Do not narrow
isMobile()
isMobile()matching'mobile' OR 'tablet'is relied on by ~7 other call sites. Narrowing it to'mobile'-only is a behavioural change with blast radius far beyond this ticket, and it still does not solve the rotated-screenshot bug (a modern iPad parses as neither'mobile'nor'tablet'anyway).
Takeaways for Future Work
Device detection in vuer_oss
- Never trust server-side UA parsing to detect an iPad. iPadOS 13+ Safari is indistinguishable from macOS Safari by UA string. Use client-side
navigator.maxTouchPointsand pass an explicit flag.customer.isTablet()(if merged as-is) is a logical subset ofcustomer.isMobile()— it adds no detection power. Treat it as a no-op for modern iPads.customer.userAgentis the only client signal the server has for this — no client hints, noSec-CH-UA.- The repo is on
ua-parser-jsv1; v2-only APIs (withFeatureCheck) are not available.- For any WebRTC video-orientation work:
videoOrientExtis gated at 4 sites —VuerCVListenerSession.js,socket/events/videochat.js,RoomTransportSession.js,SelfServiceTransportSession.js. Keep them in sync.
Related
- room-62243-screenshot-failure — another NUSZ WebRTC screenshot failure (browser never responds to
videochat:screenshot:remote) - vuer_oss
- customization-branches —
customization/generali-atvilagitasis the PR base branch - Investigations