Created
November 27, 2014 15:21
-
-
Save auchenberg/7ae8cefa7a9ac08d4ddf to your computer and use it in GitHub Desktop.
Callstats.js
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
(function(e) { | |
var t = function(e, t, n) { | |
function F(e, t, n, r, i) { | |
function u(u) { | |
var a = (new Date).getTime(), | |
c = {}; | |
c.version = s, c.appID = o, c.conferenceID = encodeURIComponent(n), c.apiTS = a, r !== undefined && (c.ucID = r), f && (c.token = f.token), c.localID = encodeURIComponent(e), c.remoteID = encodeURIComponent(t), c.endpointID = l, c.browser = { | |
name: v.name, | |
ver: v.ver, | |
os: v.os | |
}; | |
var h = {}, | |
p = null; | |
if (navigator.mozGetUserMedia) { | |
p = [], m = 0; | |
for (var d in u) !(u[d] instanceof Function) && d.match("bound") && (p[m] = u[d], m++) | |
} else p = u.result(); | |
for (var m = 0; m < p.length; ++m) { | |
var g = q(p[m]), | |
y = I(JSON.parse(g)); | |
if (!R(y)) { | |
var d = Object.keys(y)[0]; | |
h[d] = y[d] | |
} | |
} | |
c.statistics = h, "token" in c && "ucID" in c && !!V() ? $("rtpStats", T.fabricStats, JSON.stringify(c), i) : A({ | |
type: "rtpStats", | |
action: T.fabricStats, | |
data: c | |
}) | |
} | |
return u | |
} | |
function I(e) { | |
var t = {}; | |
return navigator.mozGetUserMedia ? e.id.match("outbound_rtcp_audio") ? t.localAudioRR = e : e.id.match("outbound_rtcp_video") ? t.localVideoRR = e : e.id.match("inbound_rtp_audio") ? t.remoteAudio = e : e.id.match("inbound_rtp_video") ? t.remoteVideo = e : e.id.match("outbound_rtp_audio") ? t.localAudio = e : e.id.match("outbound_rtp_video") ? t.localVideo = e : e.id.match("inbound_rtcp_audio") ? t.remoteAudioSR = e : e.id.match("inbound_rtcp_video") && (t.remoteVideoSR = e) : (e.googRemoteAddress && (t.Transport = e), e.googFrameWidthReceived && (t.remoteVideo = e), e.googFrameWidthSent && (t.localVideo = e), e.audioInputLevel && (t.localAudio = e), e.audioOutputLevel && (t.remoteAudio = e), e.googTransmitBitrate && (t.rrtcc = e)), t | |
} | |
function q(e) { | |
var t = ""; | |
t += '{"Timestamp":"', e.timestamp instanceof Date ? t += e.timestamp.getTime().toString() : t += e.timestamp, t += '",'; | |
var n = 0; | |
if (e.names) { | |
var r = e.names(); | |
for (n = 0; n < r.length; ++n) t += '"', t += r[n], t += '" : "', t += e.stat(r[n]), t += '"', n + 1 != r.length && (t += ",") | |
} else { | |
var i = Object.keys(e).length; | |
n = 0; | |
for (var s in e) n++, s != "timestamp" && (t += '"', t += s, t += '" : "', t += e[s], t += '"', n < i && (t += ",")) | |
} | |
return t += "}", t | |
} | |
function R(e) { | |
for (var t in e) return !1; | |
return !0 | |
} | |
function U(e, t, n) { | |
var r = !1; | |
if (e !== null && e.appID == t && e.userID == encodeURIComponent(n)) { | |
var i = null; | |
if (navigator.mozGetUserMedia) { | |
var s = e.expires.split(" ").join("T"); | |
i = Date.parse(s) | |
} else i = new Date(e.expires); | |
var o = new Date; | |
o < i && (r = !0) | |
} | |
return r | |
} | |
function X() { | |
h && (c.disconnect(), h = !1) | |
} | |
function V() { | |
return h | |
} | |
function $(e, t, n, r) { | |
U(f, o, u) ? V() && (c.emit(e, n), typeof r != "undefined" && r != null && t !== T.fabricStats && r(N.success, t + " sent to the backend.")) : z(o, a, u, function(i, s) { | |
i !== N.success ? callback(i, s) : V() && (c.emit(e, n), typeof r != "undefined" && r != null && t !== T.fabricStats && r(N.success, t + " sent to the backend.")) | |
}) | |
} | |
function J(t, i, l, c, h) { | |
var d = new n(i, "TEXT"), | |
v = d.getHMAC(t, "TEXT", "SHA-1", "B64"); | |
console.log("Calculated response: " + v + " for challenge: " + i); | |
var m = {}; | |
m.version = s, m.response = v, m.appID = l, m.userID = encodeURIComponent(c); | |
var g = r + "o/challenge"; | |
e.ajax({ | |
type: "POST", | |
url: g, | |
data: JSON.stringify(m), | |
success: function(e) { | |
e.appID = l, window.localStorage.setItem("auth_data", JSON.stringify(e)), o = l, a = t, u = c, f = e, typeof h != "undefined" && h(N.success, "SDK authentication successful."), V() ? typeof h != "undefined" && h(N.success, "WebSocket establishment successful.") : W(r + p, function(e, t) { | |
typeof h != "undefined" && h(e, t) | |
}) | |
}, | |
error: function(e, n, r) { | |
if (typeof h != "undefined") { | |
var s; | |
e.status === 400 ? (s = e.responseText, h("authError", s)) : e.status === 502 || e.status === 0 ? setTimeout(function() { | |
J(t, i, l, c, h) | |
}, 5e3) : (s = "HTTP " + e.status + ", " + e.statusText + ". " + e.responseText, h("httpError", s)) | |
} | |
}, | |
dataType: "json" | |
}) | |
} | |
function K() { | |
var e = navigator.userAgent, | |
t = navigator.appName, | |
n = "" + parseFloat(navigator.appVersion), | |
r; | |
return (r = e.indexOf("Opera")) != -1 ? (t = "Opera", n = e.substring(r + 6), (r = e.indexOf("Version")) != -1 && (n = e.substring(r + 8))) : (r = e.indexOf("MSIE")) != -1 ? (t = "Microsoft Internet Explorer", n = e.substring(r + 5)) : (r = e.indexOf("Chrome")) != -1 ? (t = "Chrome", n = e.substring(r + 7)) : (r = e.indexOf("Safari")) != -1 ? (t = "Safari", n = e.substring(r + 7), (r = e.indexOf("Version")) != -1 && (n = e.substring(r + 8))) : (r = e.indexOf("Firefox")) != -1 && (t = "Firefox", n = e.substring(r + 8)), { | |
name: t, | |
ver: n.toString(), | |
os: navigator.platform | |
} | |
} | |
var r = "https://collector.callstats.io:443/", | |
i = "3000", | |
s = "2.0.0", | |
o = -1, | |
u = "", | |
a = null, | |
f = null, | |
l = null, | |
c = null, | |
h = !1, | |
p = "collectCallStats", | |
d = 1e3, | |
v = K(), | |
m = { | |
length: 0 | |
}, | |
g = null, | |
y = null, | |
b = null, | |
w = "", | |
E = !1, | |
S = [], | |
x = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection, | |
T = { | |
fabricSetup: "fabricSetup", | |
fabricSetupFailed: "fabricSetupFailed", | |
fabricHold: "fabricHold", | |
fabricResume: "fabricResume", | |
audioMute: "audioMute", | |
audioUnmute: "audioUnmute", | |
videoPause: "videoPause", | |
videoResume: "videoResume", | |
fabricUsageEvent: "fabricUsageEvent", | |
fabricStats: "fabricStats", | |
fabricTerminated: "fabricTerminated" | |
}, | |
N = { | |
httpError: "httpError", | |
authError: "authError", | |
wsChannelFailure: "wsChannelFailure", | |
success: "success", | |
csProtoError: "csProtoError", | |
userFeedback: "userFeedback" | |
}, | |
C = { | |
audio: "audio", | |
video: "video", | |
data: "data", | |
multiplex: "multiplex" | |
}, | |
k = { | |
excellent: 5, | |
good: 4, | |
fair: 3, | |
poor: 2, | |
bad: 1 | |
}; | |
window.addEventListener("beforeunload", function(e) { | |
for (var t in m) | |
if (t !== "length") { | |
var n = m[t]; | |
m.length === 1 && O(T.fabricTerminated, t, n.confID, null, null), O("userLeft", null, n.confID, null, null); | |
break | |
} | |
return "Are you sure you want to close the call?" | |
}); | |
var L = function() { | |
while (S.length > 0) { | |
var e = S.shift(); | |
e.data.token = f.token, $(e.type, e.data.action, JSON.stringify(e.data), e.callback) | |
} | |
}, | |
A = function(e) { | |
S.push(e) | |
}, | |
O = function(e, t, n, r, i) { | |
var a = (new Date).getTime(), | |
l = { | |
version: s, | |
apiTS: a, | |
action: e, | |
localID: encodeURIComponent(u), | |
remoteID: encodeURIComponent(t), | |
conferenceID: encodeURIComponent(n), | |
appID: o, | |
value: r | |
}; | |
f && V() ? (l.token = f.token, $("callStatsEvent", e, JSON.stringify(l), i)) : A({ | |
type: "callStatsEvent", | |
data: l, | |
callback: i | |
}) | |
}, | |
M = function(e, t, i, s) { | |
if (typeof e == "undefined" || typeof t == "undefined" || typeof i == "undefined") throw "Argument missing"; | |
typeof e == "string" && (e = parseInt(e)), o = e, a = t, u = i, l = window.localStorage.getItem("endpointID"), console.log("EID: %o", l); | |
if (l === null) { | |
var c = (new Date).getTime(), | |
h = Math.random() * c, | |
d = new n(h.toString(), "TEXT"); | |
l = d.getHash("SHA-1", "B64"), window.localStorage.setItem("endpointID", l) | |
} | |
var v = JSON.parse(window.localStorage.getItem("auth_data")); | |
U(v, e, i) ? (typeof s != "undefined" && s(N.success, "SDK already authenticated."), f = v, W(r + p, s)) : z(e, t, i, s) | |
}, | |
_ = function(e, t, n) { | |
if (typeof e == "undefined" || typeof t == "undefined" || typeof n == "undefined") throw "Argument missing"; | |
if (o === -1 || a === null || u === "" || m.length === 0) throw "SDK is not initialized or no Fabrics added."; | |
if (e instanceof x || t === T.fabricTerminated) { | |
if (t in T) { | |
var r = null, | |
i = null, | |
s = (new Date).getTime(); | |
for (var f in m) | |
if (f !== "length" && m[f].pc === e) { | |
i = f; | |
break | |
} | |
if (i === null) throw new Error("pcObject not found!"); | |
if (t === T.fabricSetup) { | |
var l = m[i].startTime; | |
r = s - l, m[i].pcState = "established" | |
} | |
O(t, i, n, r, m[i].pcCallback); | |
if (t === T.fabricTerminated) { | |
var c = m[i].pcCallback; | |
c(N.userFeedback, "ask user for QoE feedback"), delete m[i], m.length--, console.log("Current fabrics length: " + m.length), m.length === 0 && (console.log("Canceling interval"), clearInterval(b), O("userLeft", null, n, null, null), y = g, g = null) | |
} | |
if (t === T.fabricSetup || t === T.fabricUsageEvent) { | |
var h = m[i].fabricUsage; | |
O("fabricUsageEvent", i, n, h, null) | |
} | |
return | |
} | |
throw "Invalid fabricEvents value" | |
} | |
throw "Invalid RTCPeerConnection object passed" | |
}, | |
D = function(e, t, n, r, i, s, u) { | |
if (typeof e == "undefined" || typeof t == "undefined" || typeof n == "undefined") throw "Argument missing"; | |
if (e === null || t === null || e === "" || t === "") throw "conferenceID or userID MUST not be empty or null"; | |
if (!n in k || !r in k || !i in k) throw "Invalid rating values (accepts 1-5)"; | |
var a = null; | |
g === null ? a = y : a = g; | |
var l = { | |
conferenceID: encodeURIComponent(e), | |
magicKey: a, | |
appID: o, | |
userID: encodeURIComponent(t), | |
"user-qoe": { | |
overall: n, | |
audio: r, | |
video: i, | |
"user-comment": s | |
} | |
}; | |
console.log("Sending user feedback: %o", l), f ? (l.token = f.token, window.localStorage.setItem("feedback", JSON.stringify(l)), $("userFeedbackEvent", "userFeedback", JSON.stringify(l), u)) : A({ | |
data: l, | |
callback: u | |
}) | |
}, | |
P = function(e, t, n, r, i) { | |
if (typeof e == "undefined" || typeof t == "undefined" || typeof n == "undefined" || typeof r == "undefined") throw "Argument missing"; | |
if (o === -1 || a === null || u === "") throw "SDK not initialized."; | |
if (r === null || t === null || r === "" || t === "") throw "conferenceID or remoteUserID MUST not be empty or null"; | |
if (!(e instanceof x)) throw "Invalid RTCPeerConnection object passed"; | |
if (!(n in C) || n instanceof Function) throw "Invalid fabricUsage value"; | |
var s = (new Date).getTime(); | |
g === null && (g = (Math.random() + 1).toString(36), g = g.substring(2, g.length)), m.length === 0 && (b = setInterval(j, d), O("userJoined", null, r, g)); | |
// var f = e.oniceconnectionstatechange, | |
// l = e.onicecandidate; | |
// e.oniceconnectionstatechange = H, | |
// e.onicecandidate = B | |
var f = null; | |
var l = null; | |
e.addEventListener('icecandidate', H, false); | |
e.addEventListener('iceconnectionstatechange', B, false); | |
m[t] = { | |
pc: e, | |
fabricUsage: n, | |
confID: r, | |
magicKey: g, | |
startTime: s, | |
pcCallback: i, | |
pcState: "initializing", | |
appIceCallback: f, | |
appIceCandidateCallback: l | |
}, m.length++ | |
}, | |
H = function(e) { | |
console.log("On ICE connection state change in callstats.js. Event: %o", e); | |
var t = e.srcElement; | |
console.log("Gathering state: %o connection state: %o", t.iceGatheringState, t.iceConnectionState); | |
for (var n in m) | |
if (n !== "length" && m[n].pc === t) { | |
var r = m[n].appIceCallback; | |
r !== null && r(e); | |
break | |
} | |
}, | |
B = function(e) { | |
console.log("ICE candidate found in callstats.js. Event: %o", e); | |
var t = e.srcElement; | |
for (var n in m) | |
if (n !== "length" && m[n].pc === t) { | |
var r = m[n].appIceCandidateCallback; | |
r !== null && r(e); | |
break | |
} | |
}, | |
j = function() { | |
for (var e in m) { | |
console.log("reportStats() called for " + e); | |
if (e !== "length" && m[e].pcState === "established") { | |
var t = m[e].pc, | |
n = m[e].confID, | |
r = m[e].ucID; | |
console.log("pc: ", t); | |
if (t.signalingState === "closed") { | |
console.log("Sending fabric terminated"), O(T.fabricTerminated, e, n, null, null), m.length === 1 && (console.log("Sending userLeft"), O("userLeft", null, n, null, null), clearInterval(b), y = g, g = null), delete m[e], m.length--; | |
continue | |
} | |
navigator.mozGetUserMedia ? t.getStats(null, F(u, e, n, r, m[e].pcCallback), function(t) {}) : t.getStats(F(u, e, n, r, m[e].pcCallback)) | |
} | |
} | |
}, | |
z = function(t, n, i, o) { | |
var u = r + "o/authorize", | |
a = { | |
appID: t, | |
userID: encodeURIComponent(i), | |
version: s | |
}, | |
f = e.ajax({ | |
type: "POST", | |
url: u, | |
data: JSON.stringify(a), | |
success: function(e) { | |
console.log("## sendAuthenticationRequest successfully received challenge: %o", e), J(n, e.challenge, t, i, o) | |
}, | |
error: function(e, r, s) { | |
if (typeof o != "undefined") { | |
var u; | |
e.status === 400 ? (u = e.responseText, o("authError", u)) : e.status === 502 || e.status === 0 ? setTimeout(function() { | |
z(t, n, i, o) | |
}, 5e3) : (u = "HTTP " + e.status + ", " + e.statusText + ". " + e.responseText, o("httpError", u)) | |
} | |
}, | |
dataType: "json" | |
}) | |
}, | |
W = function(e, n) { | |
c = t.connect(e, { | |
port: i, | |
reconnect: !0, | |
"reconnection delay": 500, | |
"max reconnection attempts": 50 | |
}), c.on("connect", function() { | |
h = !0, w = "17"; | |
var e = JSON.parse(window.localStorage.getItem("auth_data")), | |
t = { | |
appID: o, | |
userID: encodeURIComponent(u), | |
token: e.token, | |
version: s, | |
endpoint: { | |
type: "browser", | |
name: v.name, | |
ver: v.ver, | |
os: v.os | |
}, | |
wsID: w | |
}; | |
c.emit("registerPresence", JSON.stringify(t)); | |
var r = window.localStorage.getItem("feedback"); | |
r !== null && (console.log("Submitting cached feedback"), $("userFeedbackEvent", "userFeedback", r)), L(), n(N.success, "WebSocket establishment successful.") | |
}), c.on("disconnect", function() { | |
h = !1, n(N.httpError, "Connection to the server disappeared.") | |
}), c.on("connect_failed", function() { | |
h = !1, n(N.wsChannelFailure, "WebSocket establishment failed.") | |
}), c.on("reconnect", function() { | |
h = !0, L() | |
}), c.on("reconnect_failed", function() { | |
h = !1 | |
}), c.on("response", function(e) { | |
var t = JSON.stringify(e); | |
if (e.status === "Error") e.reason === "Invalid token" && (f = null, console.log("Token expired. Reauthenticating"), z(o, a, u, function(e, t) {})), n(N.csProtoError, e.reason); | |
else if (e.status === "200 OK") | |
if (e.event === "userFeedbackEvent") console.log("Message received:" + t), window.localStorage.removeItem("feedback"), console.log("Removed submitted feedback from the local storage"); | |
else if (e.event === "userJoined") | |
for (var r in m) | |
if (m[r].confID === e.conferenceID) { | |
m[r].ucID = e.ucID; | |
break | |
} | |
}) | |
}; | |
return { | |
version: s, | |
initialize: M, | |
fabricEvent: T, | |
addNewFabric: P, | |
sendFabricEvent: _, | |
sendUserFeedback: D, | |
csError: N, | |
fabricUsage: C, | |
qualityRating: k | |
} | |
}; | |
"function" == typeof define && define.amd ? define("callstats", ["jquery", "socketio", "sha"], t) : e.Callstats = t | |
})(this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment