Created
August 22, 2025 21:12
-
-
Save rksm/737b167d43b6fdd36126acebe9efc7d2 to your computer and use it in GitHub Desktop.
Debug getUserMedia and device permission requests with Tampermonkey
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
// ==UserScript== | |
// @name Navigator Permissions Debug Logger | |
// @namespace http://tampermonkey.net/ | |
// @version 1.0 | |
// @description Debug navigator.permissions.query and navigator.mediaDevices.getUserMedia calls | |
// @author You | |
// @match http://localhost:*/* | |
// @match https://localhost:*/* | |
// @match http://127.0.0.1:*/* | |
// @match https://127.0.0.1:*/* | |
// @match https://*.hyper.video/* | |
// @match https://latest.dev.hyper.video/* | |
// @match https://hyper-lite.dev/* | |
// @grant GM_log | |
// @run-at document-start | |
// ==/UserScript== | |
;(function () { | |
'use strict' | |
let callSequence = 0 | |
const logPrefix = '[Navigator Debug]' | |
const logStyle = 'color: #007ACC; font-weight: bold;' | |
// Store original functions | |
const originalPermissionsQuery = navigator.permissions?.query?.bind( | |
navigator.permissions | |
) | |
const originalGetUserMedia = navigator.mediaDevices?.getUserMedia?.bind( | |
navigator.mediaDevices | |
) | |
function formatTimestamp() { | |
const now = new Date() | |
return `${now.toISOString().split('T')[1]}` | |
} | |
function getCallStack() { | |
const stack = new Error().stack | |
// Remove the first few lines which are from this wrapper | |
const lines = stack.split('\n').slice(3, 8) | |
return lines.join('\n') | |
} | |
// Wrap navigator.permissions.query | |
if (navigator.permissions && navigator.permissions.query) { | |
navigator.permissions.query = async function (permissionDesc) { | |
const sequence = ++callSequence | |
const timestamp = formatTimestamp() | |
console.group( | |
`%c${logPrefix} [${sequence}] permissions.query CALLED @ ${timestamp}`, | |
logStyle | |
) | |
console.log('Permission:', permissionDesc) | |
console.log('Call Stack:\n', getCallStack()) | |
console.groupEnd() | |
try { | |
const result = await originalPermissionsQuery(permissionDesc) | |
console.group( | |
`%c${logPrefix} [${sequence}] permissions.query RESOLVED @ ${formatTimestamp()}`, | |
'color: #28a745; font-weight: bold;' | |
) | |
console.log('Permission:', permissionDesc) | |
console.log('State:', result.state) | |
console.log('Full Result:', result) | |
console.groupEnd() | |
return result | |
} catch (error) { | |
console.group( | |
`%c${logPrefix} [${sequence}] permissions.query REJECTED @ ${formatTimestamp()}`, | |
'color: #dc3545; font-weight: bold;' | |
) | |
console.log('Permission:', permissionDesc) | |
console.error('Error:', error) | |
console.groupEnd() | |
throw error | |
} | |
} | |
} | |
// Wrap navigator.mediaDevices.getUserMedia | |
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { | |
navigator.mediaDevices.getUserMedia = async function (constraints) { | |
const sequence = ++callSequence | |
const timestamp = formatTimestamp() | |
console.group( | |
`%c${logPrefix} [${sequence}] getUserMedia CALLED @ ${timestamp}`, | |
logStyle | |
) | |
console.log('Constraints:', JSON.parse(JSON.stringify(constraints || {}))) | |
console.log('Call Stack:\n', getCallStack()) | |
console.groupEnd() | |
try { | |
const stream = await originalGetUserMedia(constraints) | |
const audioTracks = stream.getAudioTracks().map(t => ({ | |
id: t.id, | |
label: t.label, | |
kind: t.kind, | |
enabled: t.enabled, | |
muted: t.muted, | |
readyState: t.readyState, | |
settings: t.getSettings ? t.getSettings() : {}, | |
})) | |
const videoTracks = stream.getVideoTracks().map(t => ({ | |
id: t.id, | |
label: t.label, | |
kind: t.kind, | |
enabled: t.enabled, | |
muted: t.muted, | |
readyState: t.readyState, | |
settings: t.getSettings ? t.getSettings() : {}, | |
})) | |
console.group( | |
`%c${logPrefix} [${sequence}] getUserMedia RESOLVED @ ${formatTimestamp()}`, | |
'color: #28a745; font-weight: bold;' | |
) | |
console.log( | |
'Constraints:', | |
JSON.parse(JSON.stringify(constraints || {})) | |
) | |
console.log('Stream ID:', stream.id) | |
console.log('Audio Tracks:', audioTracks) | |
console.log('Video Tracks:', videoTracks) | |
console.groupEnd() | |
return stream | |
} catch (error) { | |
console.group( | |
`%c${logPrefix} [${sequence}] getUserMedia REJECTED @ ${formatTimestamp()}`, | |
'color: #dc3545; font-weight: bold;' | |
) | |
console.log( | |
'Constraints:', | |
JSON.parse(JSON.stringify(constraints || {})) | |
) | |
console.error('Error:', { | |
name: error.name, | |
message: error.message, | |
constraintName: error.constraintName || null, | |
}) | |
console.groupEnd() | |
throw error | |
} | |
} | |
} | |
// Also wrap enumerateDevices for completeness | |
if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) { | |
const originalEnumerateDevices = | |
navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices) | |
navigator.mediaDevices.enumerateDevices = async function () { | |
const sequence = ++callSequence | |
const timestamp = formatTimestamp() | |
console.group( | |
`%c${logPrefix} [${sequence}] enumerateDevices CALLED @ ${timestamp}`, | |
logStyle | |
) | |
console.log('Call Stack:\n', getCallStack()) | |
console.groupEnd() | |
try { | |
const devices = await originalEnumerateDevices() | |
const deviceSummary = devices.map(d => ({ | |
kind: d.kind, | |
label: d.label || '(no label)', | |
deviceId: d.deviceId ? `${d.deviceId.substring(0, 8)}...` : '(no id)', | |
groupId: d.groupId ? `${d.groupId.substring(0, 8)}...` : '(no group)', | |
})) | |
console.group( | |
`%c${logPrefix} [${sequence}] enumerateDevices RESOLVED @ ${formatTimestamp()}`, | |
'color: #28a745; font-weight: bold;' | |
) | |
console.table(deviceSummary) | |
console.groupEnd() | |
return devices | |
} catch (error) { | |
console.group( | |
`%c${logPrefix} [${sequence}] enumerateDevices REJECTED @ ${formatTimestamp()}`, | |
'color: #dc3545; font-weight: bold;' | |
) | |
console.error('Error:', error) | |
console.groupEnd() | |
throw error | |
} | |
} | |
} | |
console.log( | |
`%c${logPrefix} Script loaded! Monitoring navigator.permissions.query and navigator.mediaDevices.getUserMedia`, | |
'color: #6f42c1; font-weight: bold;' | |
) | |
// Add a global function to check status | |
window.navigatorDebugStatus = function () { | |
console.log(`%c${logPrefix} Status:`, 'color: #6f42c1; font-weight: bold;') | |
console.log(`- Total calls intercepted: ${callSequence}`) | |
console.log('- Wrapped APIs:') | |
console.log( | |
' • navigator.permissions.query:', | |
!!navigator.permissions?.query | |
) | |
console.log( | |
' • navigator.mediaDevices.getUserMedia:', | |
!!navigator.mediaDevices?.getUserMedia | |
) | |
console.log( | |
' • navigator.mediaDevices.enumerateDevices:', | |
!!navigator.mediaDevices?.enumerateDevices | |
) | |
} | |
// Add helper to reset counter | |
window.navigatorDebugReset = function () { | |
callSequence = 0 | |
console.log( | |
`%c${logPrefix} Call sequence counter reset`, | |
'color: #6f42c1; font-weight: bold;' | |
) | |
} | |
console.log(`%c${logPrefix} Helper functions available:`, 'color: #6f42c1;') | |
console.log(' - window.navigatorDebugStatus() : Check wrapper status') | |
console.log(' - window.navigatorDebugReset() : Reset call counter') | |
})() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment