Skip to content

Instantly share code, notes, and snippets.

@Kr1tos
Created March 17, 2026 13:27
Show Gist options
  • Select an option

  • Save Kr1tos/26585898c08e5222d5edc3d6fad546be to your computer and use it in GitHub Desktop.

Select an option

Save Kr1tos/26585898c08e5222d5edc3d6fad546be to your computer and use it in GitHub Desktop.
webrtc-detector.js
// ==UserScript==
// @name WebRTC detector + selected endpoint + network hooks
// @namespace https://proxyshard.com/
// @version 1.0
// @description WebRTC + network inspection hooks
// @author Kritos
// ==/UserScript==
(function () {
const w = typeof unsafeWindow !== "undefined" ? unsafeWindow : window;
function shortText(text, max = 1200) {
try {
const s = String(text);
return s.length > max ? s.slice(0, max) + "...[truncated]" : s;
} catch (e) {
return text;
}
}
function safeJsonStringify(obj) {
try {
return JSON.stringify(obj);
} catch (e) {
return "[unserializable]";
}
}
function looksLikeSDP(value) {
if (typeof value !== "string") return false;
return value.includes("v=0") &&
(
value.includes("m=audio") ||
value.includes("m=video") ||
value.includes("webrtc-datachannel") ||
value.includes("a=group:BUNDLE")
);
}
function looksLikeICE(value) {
if (typeof value !== "string") return false;
return value.includes("candidate:") ||
value.includes('"candidate"') ||
value.includes('"ice"') ||
value.includes('"sdpMid"') ||
value.includes('"sdpMLineIndex"');
}
function looksWebRTCRelated(value) {
if (typeof value !== "string") return false;
return looksLikeSDP(value) ||
looksLikeICE(value) ||
value.includes('"type":"offer"') ||
value.includes('"type":"answer"') ||
value.includes('"webrtc"') ||
value.includes('"rtc"');
}
async function payloadToText(payload) {
try {
if (payload == null) return null;
if (typeof payload === "string") return payload;
if (payload instanceof Blob) {
return await payload.text();
}
if (payload instanceof ArrayBuffer) {
return new TextDecoder().decode(payload);
}
if (ArrayBuffer.isView(payload)) {
return new TextDecoder().decode(payload);
}
if (typeof URLSearchParams !== "undefined" && payload instanceof URLSearchParams) {
return payload.toString();
}
if (typeof FormData !== "undefined" && payload instanceof FormData) {
const obj = {};
for (const [k, v] of payload.entries()) {
obj[k] = typeof v === "string" ? v : `[binary:${v && v.name ? v.name : "blob"}]`;
}
return safeJsonStringify(obj);
}
if (typeof payload === "object") {
return safeJsonStringify(payload);
}
return String(payload);
} catch (e) {
return "[payload parse failed]";
}
}
function logInterestingNetwork(prefix, url, text) {
if (!text) return;
if (!looksWebRTCRelated(text)) return;
console.group(`${prefix} ${url || ""}`);
if (looksLikeSDP(text)) {
console.warn("[NET] SDP detected");
}
if (looksLikeICE(text)) {
console.warn("[NET] ICE detected");
}
console.log(shortText(text, 4000));
console.groupEnd();
}
function installNetworkHooks() {
if (w.__webrtc_net_hooks_installed__) return;
w.__webrtc_net_hooks_installed__ = true;
const origFetch = w.fetch;
if (origFetch) {
w.fetch = async function (...args) {
try {
const [input, init] = args;
const url = typeof input === "string" ? input : (input && input.url) ? input.url : "";
if (init && "body" in init) {
payloadToText(init.body).then(text => {
logInterestingNetwork("[NET][fetch]", url, text);
}).catch(() => {});
}
} catch (e) {}
return origFetch.apply(this, args);
};
}
const origXHROpen = XMLHttpRequest.prototype.open;
const origXHRSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (method, url, ...rest) {
this.__webrtc_hook_method = method;
this.__webrtc_hook_url = url;
return origXHROpen.call(this, method, url, ...rest);
};
XMLHttpRequest.prototype.send = function (body) {
try {
const url = this.__webrtc_hook_url || "";
payloadToText(body).then(text => {
logInterestingNetwork("[NET][xhr]", url, text);
}).catch(() => {});
} catch (e) {}
return origXHRSend.call(this, body);
};
const OrigWS = w.WebSocket;
if (OrigWS && OrigWS.prototype && OrigWS.prototype.send) {
const origWSSend = OrigWS.prototype.send;
OrigWS.prototype.send = function (data) {
try {
const url = this.url || "";
payloadToText(data).then(text => {
logInterestingNetwork("[NET][ws]", url, text);
}).catch(() => {});
} catch (e) {}
return origWSSend.call(this, data);
};
}
if (navigator.sendBeacon) {
const origBeacon = navigator.sendBeacon.bind(navigator);
navigator.sendBeacon = function (url, data) {
try {
payloadToText(data).then(text => {
logInterestingNetwork("[NET][beacon]", url, text);
}).catch(() => {});
} catch (e) {}
return origBeacon(url, data);
};
}
console.log("[TM] Network hooks installed");
}
function installHook() {
const Orig =
w.RTCPeerConnection ||
w.webkitRTCPeerConnection ||
w.mozRTCPeerConnection;
if (!Orig) {
console.log("[TM] RTCPeerConnection not found yet");
return false;
}
if (w.__webrtc_hook_installed__) {
console.log("[TM] hook already installed");
return true;
}
w.__webrtc_hook_installed__ = true;
const peers = new Set();
function parseCandidate(str) {
if (!str || typeof str !== "string") return null;
const parts = str.trim().split(/\s+/);
if (parts.length < 8) return null;
const out = {
foundation: parts[0].replace(/^candidate:/, ""),
component: parts[1],
protocol: parts[2],
priority: parts[3],
ip: parts[4],
port: parts[5],
type: parts[7],
raw: str
};
const raddrIndex = parts.indexOf("raddr");
const rportIndex = parts.indexOf("rport");
const tcptypeIndex = parts.indexOf("tcptype");
const generationIndex = parts.indexOf("generation");
const networkIdIndex = parts.indexOf("network-id");
const networkCostIndex = parts.indexOf("network-cost");
if (raddrIndex !== -1 && parts[raddrIndex + 1]) out.raddr = parts[raddrIndex + 1];
if (rportIndex !== -1 && parts[rportIndex + 1]) out.rport = parts[rportIndex + 1];
if (tcptypeIndex !== -1 && parts[tcptypeIndex + 1]) out.tcptype = parts[tcptypeIndex + 1];
if (generationIndex !== -1 && parts[generationIndex + 1]) out.generation = parts[generationIndex + 1];
if (networkIdIndex !== -1 && parts[networkIdIndex + 1]) out.networkId = parts[networkIdIndex + 1];
if (networkCostIndex !== -1 && parts[networkCostIndex + 1]) out.networkCost = parts[networkCostIndex + 1];
return out;
}
function summarizeSDP(desc) {
try {
if (!desc || !desc.sdp) return desc;
const sdp = String(desc.sdp);
const summary = {
type: desc.type || null,
hasAudio: sdp.includes("m=audio"),
hasVideo: sdp.includes("m=video"),
hasDataChannel: sdp.includes("webrtc-datachannel"),
recvonlyCount: (sdp.match(/\ba=recvonly\b/g) || []).length,
sendrecvCount: (sdp.match(/\ba=sendrecv\b/g) || []).length,
sendonlyCount: (sdp.match(/\ba=sendonly\b/g) || []).length,
inactiveCount: (sdp.match(/\ba=inactive\b/g) || []).length,
candidatesInSDP: (sdp.match(/\ba=candidate:/g) || []).length,
iceUfrag: (sdp.match(/a=ice-ufrag:([^\r\n]+)/) || [])[1] || null,
bundle: (sdp.match(/a=group:BUNDLE ([^\r\n]+)/) || [])[1] || null
};
return {
summary,
sdp: shortText(sdp, 4000)
};
} catch (e) {
return desc;
}
}
async function dumpSelectedPair(pc, reason) {
try {
const stats = await pc.getStats();
let selectedPair = null;
let localCandidate = null;
let remoteCandidate = null;
stats.forEach((report) => {
if (
report.type === "transport" &&
report.selectedCandidatePairId &&
!selectedPair
) {
selectedPair = stats.get(report.selectedCandidatePairId);
}
});
if (!selectedPair) {
stats.forEach((report) => {
if (
report.type === "candidate-pair" &&
(report.selected === true || report.nominated === true)
) {
selectedPair = report;
}
});
}
if (!selectedPair) {
return;
}
if (selectedPair.localCandidateId) {
localCandidate = stats.get(selectedPair.localCandidateId);
}
if (selectedPair.remoteCandidateId) {
remoteCandidate = stats.get(selectedPair.remoteCandidateId);
}
console.group("[WebRTC] SELECTED PATH" + (reason ? ` (${reason})` : ""));
console.log("candidate-pair:", selectedPair);
if (localCandidate) {
console.log("local-candidate:", {
id: localCandidate.id,
address: localCandidate.address || localCandidate.ip,
ip: localCandidate.ip || localCandidate.address,
port: localCandidate.port,
protocol: localCandidate.protocol,
candidateType: localCandidate.candidateType,
networkType: localCandidate.networkType,
relayProtocol: localCandidate.relayProtocol || null,
url: localCandidate.url || null
});
}
if (remoteCandidate) {
const endpoint = {
id: remoteCandidate.id,
address: remoteCandidate.address || remoteCandidate.ip,
ip: remoteCandidate.ip || remoteCandidate.address,
port: remoteCandidate.port,
protocol: remoteCandidate.protocol,
candidateType: remoteCandidate.candidateType,
relayProtocol: remoteCandidate.relayProtocol || null,
url: remoteCandidate.url || null
};
console.log("remote-candidate:", endpoint);
console.warn("[WebRTC] REAL REMOTE ENDPOINT:", endpoint);
}
console.groupEnd();
} catch (e) {
console.log("[WebRTC] getStats failed:", e);
}
}
function dumpAllStats(pc, reason) {
pc.getStats().then(stats => {
const interesting = [];
stats.forEach(report => {
if (
report.type === "local-candidate" ||
report.type === "remote-candidate" ||
report.type === "candidate-pair" ||
report.type === "transport"
) {
interesting.push(report);
}
});
if (interesting.length) {
console.group("[WebRTC] STATS SNAPSHOT" + (reason ? ` (${reason})` : ""));
for (const item of interesting) {
console.log(item.type, item);
}
console.groupEnd();
}
}).catch(e => {
console.log("[WebRTC] getStats snapshot failed:", e);
});
}
function startStatsWatcher(pc) {
if (pc.__stats_watcher_started__) return;
pc.__stats_watcher_started__ = true;
const interval = setInterval(() => {
if (
pc.connectionState === "closed" ||
pc.iceConnectionState === "closed" ||
pc.signalingState === "closed"
) {
clearInterval(interval);
return;
}
dumpSelectedPair(pc, "periodic");
}, 2000);
pc.__stats_interval__ = interval;
}
function WrappedRTCPeerConnection(...args) {
const config = args[0];
console.group("[WebRTC] created");
console.log("config:", config);
if (config && Array.isArray(config.iceServers)) {
console.log("ICE servers:", config.iceServers);
} else {
console.log("ICE servers: none passed explicitly");
}
console.groupEnd();
const pc = new Orig(...args);
peers.add(pc);
startStatsWatcher(pc);
pc.addEventListener("icecandidate", (e) => {
if (!e.candidate || !e.candidate.candidate) {
console.log("[WebRTC] local candidate gathering finished");
dumpAllStats(pc, "after local gathering finished");
return;
}
console.log("[WebRTC] local candidate raw:", e.candidate.candidate);
const parsed = parseCandidate(e.candidate.candidate);
if (parsed) {
console.log("[WebRTC] local candidate parsed:", parsed);
}
});
pc.addEventListener("icecandidateerror", (e) => {
console.warn("[WebRTC] icecandidateerror:", {
url: e.url,
address: e.address,
port: e.port,
errorCode: e.errorCode,
errorText: e.errorText
});
});
pc.addEventListener("icegatheringstatechange", () => {
console.log("[WebRTC] iceGatheringState:", pc.iceGatheringState);
if (pc.iceGatheringState === "complete") {
dumpAllStats(pc, "iceGatheringState complete");
}
});
pc.addEventListener("connectionstatechange", () => {
console.log("[WebRTC] connectionState:", pc.connectionState);
if (pc.connectionState === "connecting" || pc.connectionState === "connected") {
dumpSelectedPair(pc, "connectionstatechange");
}
});
pc.addEventListener("iceconnectionstatechange", () => {
console.log("[WebRTC] iceConnectionState:", pc.iceConnectionState);
if (
pc.iceConnectionState === "checking" ||
pc.iceConnectionState === "connected" ||
pc.iceConnectionState === "completed" ||
pc.iceConnectionState === "failed"
) {
dumpSelectedPair(pc, "iceconnectionstatechange");
dumpAllStats(pc, "iceconnectionstatechange");
}
});
pc.addEventListener("signalingstatechange", () => {
console.log("[WebRTC] signalingState:", pc.signalingState);
});
const origSetLocalDescription = pc.setLocalDescription;
pc.setLocalDescription = function (...a) {
console.group("[WebRTC] setLocalDescription");
console.log(summarizeSDP(a[0]));
console.groupEnd();
const result = origSetLocalDescription.apply(this, a);
Promise.resolve(result).then(() => {
setTimeout(() => dumpAllStats(pc, "after setLocalDescription"), 300);
}).catch(() => {});
return result;
};
const origSetRemoteDescription = pc.setRemoteDescription;
pc.setRemoteDescription = function (...a) {
console.group("[WebRTC] setRemoteDescription");
console.log(summarizeSDP(a[0]));
console.groupEnd();
const result = origSetRemoteDescription.apply(this, a);
Promise.resolve(result).then(() => {
setTimeout(() => {
dumpSelectedPair(pc, "after setRemoteDescription");
dumpAllStats(pc, "after setRemoteDescription");
}, 500);
}).catch(() => {});
return result;
};
const origAddIceCandidate = pc.addIceCandidate;
pc.addIceCandidate = function (...a) {
if (a[0] && a[0].candidate) {
console.log("[WebRTC] remote candidate raw:", a[0].candidate);
const parsed = parseCandidate(a[0].candidate);
if (parsed) {
console.log("[WebRTC] remote candidate parsed:", parsed);
}
} else {
console.log("[WebRTC] addIceCandidate called:", a[0]);
}
const result = origAddIceCandidate.apply(this, a);
Promise.resolve(result).then(() => {
setTimeout(() => {
dumpSelectedPair(pc, "after addIceCandidate");
dumpAllStats(pc, "after addIceCandidate");
}, 300);
}).catch(() => {});
return result;
};
const origCreateDataChannel = pc.createDataChannel;
if (origCreateDataChannel) {
pc.createDataChannel = function (...a) {
console.log("[WebRTC] createDataChannel:", a[0], a[1] || {});
return origCreateDataChannel.apply(this, a);
};
}
const origCreateOffer = pc.createOffer;
if (origCreateOffer) {
pc.createOffer = function (...a) {
console.log("[WebRTC] createOffer called", a);
return origCreateOffer.apply(this, a).then(desc => {
console.group("[WebRTC] createOffer result");
console.log(summarizeSDP(desc));
console.groupEnd();
return desc;
});
};
}
const origCreateAnswer = pc.createAnswer;
if (origCreateAnswer) {
pc.createAnswer = function (...a) {
console.log("[WebRTC] createAnswer called", a);
return origCreateAnswer.apply(this, a).then(desc => {
console.group("[WebRTC] createAnswer result");
console.log(summarizeSDP(desc));
console.groupEnd();
return desc;
});
};
}
return pc;
}
WrappedRTCPeerConnection.prototype = Orig.prototype;
Object.setPrototypeOf(WrappedRTCPeerConnection, Orig);
w.RTCPeerConnection = WrappedRTCPeerConnection;
if (w.webkitRTCPeerConnection) w.webkitRTCPeerConnection = WrappedRTCPeerConnection;
if (w.mozRTCPeerConnection) w.mozRTCPeerConnection = WrappedRTCPeerConnection;
w.__dumpAllWebRTCSelectedPaths = async function () {
for (const pc of peers) {
await dumpSelectedPair(pc, "manual dump");
}
};
w.__dumpAllWebRTCStats = async function () {
for (const pc of peers) {
dumpAllStats(pc, "manual dump");
}
};
console.log("[TM] WebRTC hook installed");
return true;
}
installNetworkHooks();
if (!installHook()) {
const timer = setInterval(() => {
if (installHook()) clearInterval(timer);
}, 50);
setTimeout(() => clearInterval(timer), 10000);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment