Skip to content

Instantly share code, notes, and snippets.

@auchenberg
Created November 27, 2014 15:21
Show Gist options
  • Save auchenberg/7ae8cefa7a9ac08d4ddf to your computer and use it in GitHub Desktop.
Save auchenberg/7ae8cefa7a9ac08d4ddf to your computer and use it in GitHub Desktop.
Callstats.js
(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