mirror of
https://github.com/signalwire/freeswitch.git
synced 2025-08-13 09:36:46 +00:00
check in raw verto js files and directory structure for development
This commit is contained in:
15
html5/verto/js/Makefile
Normal file
15
html5/verto/js/Makefile
Normal file
@@ -0,0 +1,15 @@
|
||||
JSFILES=src/jquery.FSRTC.js src/jquery.jsonrpcclient.js src/jquery.verto.js
|
||||
|
||||
all: jsmin verto-min.js
|
||||
|
||||
jsmin: jsmin.c
|
||||
$(CC) $< -o $@
|
||||
|
||||
verto-min.js: jsmin $(JSFILES)
|
||||
cat $(JSFILES) | ./jsmin > $@
|
||||
|
||||
clean:
|
||||
rm -f verto-min.js jsmin *~
|
||||
|
||||
install-demo: all
|
||||
cp verto-min.js ../demo/js
|
10
html5/verto/js/README
Normal file
10
html5/verto/js/README
Normal file
@@ -0,0 +1,10 @@
|
||||
This file needs to say more.
|
||||
Documentation for the api needs to be developed with jsdoc-toolkit http://pulkitgoyal.in/documenting-jquery-plugins-jsdoc-toolkit/
|
||||
|
||||
Dependancies for DEMO
|
||||
jquery-2.0.3.min.js
|
||||
jquery-2.0.3.min.map
|
||||
jquery.cookie.js
|
||||
jquery.dataTables.js
|
||||
jquery.json-2.4.min.js
|
||||
jquery.mobile-1.3.2.min.js
|
306
html5/verto/js/jsmin.c
Normal file
306
html5/verto/js/jsmin.c
Normal file
@@ -0,0 +1,306 @@
|
||||
/* jsmin.c
|
||||
2013-03-29
|
||||
|
||||
Copyright (c) 2002 Douglas Crockford (www.crockford.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
The Software shall be used for Good, not Evil.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static int theA;
|
||||
static int theB;
|
||||
static int theLookahead = EOF;
|
||||
static int theX = EOF;
|
||||
static int theY = EOF;
|
||||
|
||||
|
||||
static void
|
||||
error(char* s)
|
||||
{
|
||||
fputs("JSMIN Error: ", stderr);
|
||||
fputs(s, stderr);
|
||||
fputc('\n', stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* isAlphanum -- return true if the character is a letter, digit, underscore,
|
||||
dollar sign, or non-ASCII character.
|
||||
*/
|
||||
|
||||
static int
|
||||
isAlphanum(int c)
|
||||
{
|
||||
return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
|
||||
(c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' ||
|
||||
c > 126);
|
||||
}
|
||||
|
||||
|
||||
/* get -- return the next character from stdin. Watch out for lookahead. If
|
||||
the character is a control character, translate it to a space or
|
||||
linefeed.
|
||||
*/
|
||||
|
||||
static int
|
||||
get()
|
||||
{
|
||||
int c = theLookahead;
|
||||
theLookahead = EOF;
|
||||
if (c == EOF) {
|
||||
c = getc(stdin);
|
||||
}
|
||||
if (c >= ' ' || c == '\n' || c == EOF) {
|
||||
return c;
|
||||
}
|
||||
if (c == '\r') {
|
||||
return '\n';
|
||||
}
|
||||
return ' ';
|
||||
}
|
||||
|
||||
|
||||
/* peek -- get the next character without getting it.
|
||||
*/
|
||||
|
||||
static int
|
||||
peek()
|
||||
{
|
||||
theLookahead = get();
|
||||
return theLookahead;
|
||||
}
|
||||
|
||||
|
||||
/* next -- get the next character, excluding comments. peek() is used to see
|
||||
if a '/' is followed by a '/' or '*'.
|
||||
*/
|
||||
|
||||
static int
|
||||
next()
|
||||
{
|
||||
int c = get();
|
||||
if (c == '/') {
|
||||
switch (peek()) {
|
||||
case '/':
|
||||
for (;;) {
|
||||
c = get();
|
||||
if (c <= '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '*':
|
||||
get();
|
||||
while (c != ' ') {
|
||||
switch (get()) {
|
||||
case '*':
|
||||
if (peek() == '/') {
|
||||
get();
|
||||
c = ' ';
|
||||
}
|
||||
break;
|
||||
case EOF:
|
||||
error("Unterminated comment.");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
theY = theX;
|
||||
theX = c;
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
/* action -- do something! What you do is determined by the argument:
|
||||
1 Output A. Copy B to A. Get the next B.
|
||||
2 Copy B to A. Get the next B. (Delete A).
|
||||
3 Get the next B. (Delete B).
|
||||
action treats a string as a single character. Wow!
|
||||
action recognizes a regular expression if it is preceded by ( or , or =.
|
||||
*/
|
||||
|
||||
static void
|
||||
action(int d)
|
||||
{
|
||||
switch (d) {
|
||||
case 1:
|
||||
putc(theA, stdout);
|
||||
if (
|
||||
(theY == '\n' || theY == ' ') &&
|
||||
(theA == '+' || theA == '-' || theA == '*' || theA == '/') &&
|
||||
(theB == '+' || theB == '-' || theB == '*' || theB == '/')
|
||||
) {
|
||||
putc(theY, stdout);
|
||||
}
|
||||
case 2:
|
||||
theA = theB;
|
||||
if (theA == '\'' || theA == '"' || theA == '`') {
|
||||
for (;;) {
|
||||
putc(theA, stdout);
|
||||
theA = get();
|
||||
if (theA == theB) {
|
||||
break;
|
||||
}
|
||||
if (theA == '\\') {
|
||||
putc(theA, stdout);
|
||||
theA = get();
|
||||
}
|
||||
if (theA == EOF) {
|
||||
error("Unterminated string literal.");
|
||||
}
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
theB = next();
|
||||
if (theB == '/' && (
|
||||
theA == '(' || theA == ',' || theA == '=' || theA == ':' ||
|
||||
theA == '[' || theA == '!' || theA == '&' || theA == '|' ||
|
||||
theA == '?' || theA == '+' || theA == '-' || theA == '~' ||
|
||||
theA == '*' || theA == '/' || theA == '{' || theA == '\n'
|
||||
)) {
|
||||
putc(theA, stdout);
|
||||
if (theA == '/' || theA == '*') {
|
||||
putc(' ', stdout);
|
||||
}
|
||||
putc(theB, stdout);
|
||||
for (;;) {
|
||||
theA = get();
|
||||
if (theA == '[') {
|
||||
for (;;) {
|
||||
putc(theA, stdout);
|
||||
theA = get();
|
||||
if (theA == ']') {
|
||||
break;
|
||||
}
|
||||
if (theA == '\\') {
|
||||
putc(theA, stdout);
|
||||
theA = get();
|
||||
}
|
||||
if (theA == EOF) {
|
||||
error("Unterminated set in Regular Expression literal.");
|
||||
}
|
||||
}
|
||||
} else if (theA == '/') {
|
||||
switch (peek()) {
|
||||
case '/':
|
||||
case '*':
|
||||
error("Unterminated set in Regular Expression literal.");
|
||||
}
|
||||
break;
|
||||
} else if (theA =='\\') {
|
||||
putc(theA, stdout);
|
||||
theA = get();
|
||||
}
|
||||
if (theA == EOF) {
|
||||
error("Unterminated Regular Expression literal.");
|
||||
}
|
||||
putc(theA, stdout);
|
||||
}
|
||||
theB = next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* jsmin -- Copy the input to the output, deleting the characters which are
|
||||
insignificant to JavaScript. Comments will be removed. Tabs will be
|
||||
replaced with spaces. Carriage returns will be replaced with linefeeds.
|
||||
Most spaces and linefeeds will be removed.
|
||||
*/
|
||||
|
||||
static void
|
||||
jsmin()
|
||||
{
|
||||
if (peek() == 0xEF) {
|
||||
get();
|
||||
get();
|
||||
get();
|
||||
}
|
||||
theA = '\n';
|
||||
action(3);
|
||||
while (theA != EOF) {
|
||||
switch (theA) {
|
||||
case ' ':
|
||||
action(isAlphanum(theB) ? 1 : 2);
|
||||
break;
|
||||
case '\n':
|
||||
switch (theB) {
|
||||
case '{':
|
||||
case '[':
|
||||
case '(':
|
||||
case '+':
|
||||
case '-':
|
||||
case '!':
|
||||
case '~':
|
||||
action(1);
|
||||
break;
|
||||
case ' ':
|
||||
action(3);
|
||||
break;
|
||||
default:
|
||||
action(isAlphanum(theB) ? 1 : 2);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
switch (theB) {
|
||||
case ' ':
|
||||
action(isAlphanum(theA) ? 1 : 3);
|
||||
break;
|
||||
case '\n':
|
||||
switch (theA) {
|
||||
case '}':
|
||||
case ']':
|
||||
case ')':
|
||||
case '+':
|
||||
case '-':
|
||||
case '"':
|
||||
case '\'':
|
||||
case '`':
|
||||
action(1);
|
||||
break;
|
||||
default:
|
||||
action(isAlphanum(theA) ? 1 : 3);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
action(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* main -- Output any command line arguments as comments
|
||||
and then minify the input.
|
||||
*/
|
||||
extern int
|
||||
main(int argc, char* argv[])
|
||||
{
|
||||
int i;
|
||||
for (i = 1; i < argc; i += 1) {
|
||||
fprintf(stdout, "// %s\n", argv[i]);
|
||||
}
|
||||
jsmin();
|
||||
return 0;
|
||||
}
|
758
html5/verto/js/src/jquery.FSRTC.js
Normal file
758
html5/verto/js/src/jquery.FSRTC.js
Normal file
@@ -0,0 +1,758 @@
|
||||
/*
|
||||
* Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
|
||||
* Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
|
||||
*
|
||||
* Version: MPL 1.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Anthony Minessale II <anthm@freeswitch.org>
|
||||
* Portions created by the Initial Developer are Copyright (C)
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Anthony Minessale II <anthm@freeswitch.org>
|
||||
*
|
||||
* jquery.FSRTC.js - WebRTC Glue code
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
|
||||
// Find the line in sdpLines that starts with |prefix|, and, if specified,
|
||||
// contains |substr| (case-insensitive search).
|
||||
function findLine(sdpLines, prefix, substr) {
|
||||
return findLineInRange(sdpLines, 0, -1, prefix, substr);
|
||||
}
|
||||
|
||||
// Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix|
|
||||
// and, if specified, contains |substr| (case-insensitive search).
|
||||
function findLineInRange(sdpLines, startLine, endLine, prefix, substr) {
|
||||
var realEndLine = (endLine != -1) ? endLine : sdpLines.length;
|
||||
for (var i = startLine; i < realEndLine; ++i) {
|
||||
if (sdpLines[i].indexOf(prefix) === 0) {
|
||||
if (!substr || sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Gets the codec payload type from an a=rtpmap:X line.
|
||||
function getCodecPayloadType(sdpLine) {
|
||||
var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+');
|
||||
var result = sdpLine.match(pattern);
|
||||
return (result && result.length == 2) ? result[1] : null;
|
||||
}
|
||||
|
||||
// Returns a new m= line with the specified codec as the first one.
|
||||
function setDefaultCodec(mLine, payload) {
|
||||
var elements = mLine.split(' ');
|
||||
var newLine = [];
|
||||
var index = 0;
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
if (index === 3) // Format of media starts from the fourth.
|
||||
newLine[index++] = payload; // Put target payload to the first.
|
||||
if (elements[i] !== payload) newLine[index++] = elements[i];
|
||||
}
|
||||
return newLine.join(' ');
|
||||
}
|
||||
|
||||
$.FSRTC = function(options) {
|
||||
this.options = $.extend({
|
||||
useVideo: null,
|
||||
useStereo: false,
|
||||
userData: null,
|
||||
videoParams: {},
|
||||
callbacks: {
|
||||
onICEComplete: function() {},
|
||||
onICE: function() {},
|
||||
onOfferSDP: function() {}
|
||||
}
|
||||
},
|
||||
options);
|
||||
|
||||
this.mediaData = {
|
||||
SDP: null,
|
||||
profile: {},
|
||||
candidateList: []
|
||||
};
|
||||
|
||||
this.constraints = {
|
||||
optional: [{
|
||||
'DtlsSrtpKeyAgreement': 'true'
|
||||
}],
|
||||
mandatory: {
|
||||
OfferToReceiveAudio: true,
|
||||
OfferToReceiveVideo: this.options.useVideo ? true : false,
|
||||
}
|
||||
};
|
||||
|
||||
if (self.options.useVideo) {
|
||||
self.options.useVideo.style.display = 'none';
|
||||
}
|
||||
|
||||
setCompat();
|
||||
checkCompat();
|
||||
};
|
||||
|
||||
$.FSRTC.prototype.useVideo = function(obj) {
|
||||
var self = this;
|
||||
|
||||
if (obj) {
|
||||
self.options.useVideo = obj;
|
||||
self.constraints.mandatory.OfferToReceiveVideo = true;
|
||||
} else {
|
||||
self.options.useVideo = null;
|
||||
self.constraints.mandatory.OfferToReceiveVideo = false;
|
||||
}
|
||||
|
||||
if (self.options.useVideo) {
|
||||
self.options.useVideo.style.display = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
$.FSRTC.prototype.useStereo = function(on) {
|
||||
var self = this;
|
||||
self.options.useStereo = on;
|
||||
};
|
||||
|
||||
// Sets Opus in stereo if stereo is enabled, by adding the stereo=1 fmtp param.
|
||||
$.FSRTC.prototype.stereoHack = function(sdp) {
|
||||
var self = this;
|
||||
|
||||
if (!self.options.useStereo) {
|
||||
return sdp;
|
||||
}
|
||||
|
||||
var sdpLines = sdp.split('\r\n');
|
||||
|
||||
// Find opus payload.
|
||||
var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000'),
|
||||
opusPayload;
|
||||
if (opusIndex) {
|
||||
opusPayload = getCodecPayloadType(sdpLines[opusIndex]);
|
||||
}
|
||||
|
||||
// Find the payload in fmtp line.
|
||||
var fmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString());
|
||||
if (fmtpLineIndex === null) return sdp;
|
||||
|
||||
// Append stereo=1 to fmtp line.
|
||||
sdpLines[fmtpLineIndex] = sdpLines[fmtpLineIndex].concat('; stereo=1');
|
||||
|
||||
sdp = sdpLines.join('\r\n');
|
||||
return sdp;
|
||||
};
|
||||
|
||||
function setCompat() {
|
||||
$.FSRTC.moz = !!navigator.mozGetUserMedia;
|
||||
//navigator.getUserMedia || (navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia);
|
||||
if (!navigator.getUserMedia) {
|
||||
navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia;
|
||||
}
|
||||
}
|
||||
|
||||
function checkCompat() {
|
||||
if (!navigator.getUserMedia) {
|
||||
alert('This application cannot function in this browser.');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function onStreamError(self) {
|
||||
console.log('There has been a problem retrieving the streams - did you allow access?');
|
||||
|
||||
}
|
||||
|
||||
function onStreamSuccess(self) {
|
||||
console.log("Stream Success");
|
||||
}
|
||||
|
||||
function onICE(self, candidate) {
|
||||
self.mediaData.candidate = candidate;
|
||||
self.mediaData.candidateList.push(self.mediaData.candidate);
|
||||
|
||||
doCallback(self, "onICE");
|
||||
}
|
||||
|
||||
function doCallback(self, func, arg) {
|
||||
if (func in self.options.callbacks) {
|
||||
self.options.callbacks[func](self, arg);
|
||||
}
|
||||
}
|
||||
|
||||
function onICEComplete(self, candidate) {
|
||||
console.log("ICE Complete");
|
||||
doCallback(self, "onICEComplete");
|
||||
}
|
||||
|
||||
function onChannelError(self, e) {
|
||||
console.error("Channel Error", e);
|
||||
doCallback(self, "onError", e);
|
||||
}
|
||||
|
||||
function onICESDP(self, sdp) {
|
||||
self.mediaData.SDP = self.stereoHack(sdp.sdp);
|
||||
console.log("ICE SDP");
|
||||
doCallback(self, "onICESDP");
|
||||
}
|
||||
|
||||
function onAnswerSDP(self, sdp) {
|
||||
self.answer.SDP = self.stereoHack(sdp.sdp);
|
||||
console.log("ICE ANSWER SDP");
|
||||
doCallback(self, "onAnswerSDP", self.answer.SDP);
|
||||
}
|
||||
|
||||
function onMessage(self, msg) {
|
||||
console.log("Message");
|
||||
doCallback(self, "onICESDP", msg);
|
||||
}
|
||||
|
||||
function onRemoteStream(self, stream) {
|
||||
if (self.options.useVideo) {
|
||||
self.options.useVideo.style.display = 'block';
|
||||
}
|
||||
|
||||
var element = self.options.useAudio;
|
||||
console.log("REMOTE STREAM", stream, element);
|
||||
|
||||
if (typeof element.srcObject !== 'undefined') {
|
||||
element.srcObject = stream;
|
||||
} else if (typeof element.mozSrcObject !== 'undefined') {
|
||||
element.mozSrcObject = stream;
|
||||
} else if (typeof element.src !== 'undefined') {
|
||||
element.src = URL.createObjectURL(stream);
|
||||
} else {
|
||||
console.error('Error attaching stream to element.');
|
||||
}
|
||||
|
||||
self.options.useAudio.play();
|
||||
self.remoteStream = stream;
|
||||
}
|
||||
|
||||
function onOfferSDP(self, sdp) {
|
||||
self.mediaData.SDP = self.stereoHack(sdp.sdp);
|
||||
console.log("Offer SDP");
|
||||
doCallback(self, "onOfferSDP");
|
||||
}
|
||||
|
||||
$.FSRTC.prototype.answer = function(sdp, onSuccess, onError) {
|
||||
this.peer.addAnswerSDP({
|
||||
type: "answer",
|
||||
sdp: sdp
|
||||
},
|
||||
onSuccess, onError);
|
||||
};
|
||||
|
||||
$.FSRTC.prototype.stop = function() {
|
||||
var self = this;
|
||||
|
||||
if (self.options.useVideo) {
|
||||
self.options.useVideo.style.display = 'none';
|
||||
}
|
||||
|
||||
if (self.localStream) {
|
||||
self.localStream.stop();
|
||||
self.localStream = null;
|
||||
}
|
||||
|
||||
if (self.peer) {
|
||||
console.log("stopping peer");
|
||||
self.peer.stop();
|
||||
}
|
||||
};
|
||||
|
||||
$.FSRTC.prototype.createAnswer = function(sdp) {
|
||||
var self = this;
|
||||
self.type = "answer";
|
||||
self.remoteSDP = sdp;
|
||||
console.debug("inbound sdp: ", sdp);
|
||||
|
||||
function onSuccess(stream) {
|
||||
self.localStream = stream;
|
||||
|
||||
self.peer = RTCPeerConnection({
|
||||
type: self.type,
|
||||
attachStream: self.localStream,
|
||||
onICE: function(candidate) {
|
||||
return onICE(self, candidate);
|
||||
},
|
||||
onICEComplete: function() {
|
||||
return onICEComplete(self);
|
||||
},
|
||||
onRemoteStream: function(stream) {
|
||||
return onRemoteStream(self, stream);
|
||||
},
|
||||
onICESDP: function(sdp) {
|
||||
return onICESDP(self, sdp);
|
||||
},
|
||||
onChannelError: function(e) {
|
||||
return onChannelError(self, e);
|
||||
},
|
||||
constraints: self.constraints,
|
||||
offerSDP: {
|
||||
type: "offer",
|
||||
sdp: self.remoteSDP
|
||||
}
|
||||
});
|
||||
|
||||
onStreamSuccess(self);
|
||||
}
|
||||
|
||||
function onError() {
|
||||
onStreamError(self);
|
||||
}
|
||||
|
||||
getUserMedia({
|
||||
constraints: {
|
||||
audio: true,
|
||||
video: this.options.useVideo ? {
|
||||
mandatory: this.options.videoParams,
|
||||
optional: []
|
||||
} : null
|
||||
},
|
||||
video: this.options.useVideo ? true : false,
|
||||
onsuccess: onSuccess,
|
||||
onerror: onError
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$.FSRTC.prototype.call = function(profile) {
|
||||
checkCompat();
|
||||
|
||||
var self = this;
|
||||
|
||||
self.type = "offer";
|
||||
|
||||
function onSuccess(stream) {
|
||||
self.localStream = stream;
|
||||
|
||||
self.peer = RTCPeerConnection({
|
||||
type: self.type,
|
||||
attachStream: self.localStream,
|
||||
onICE: function(candidate) {
|
||||
return onICE(self, candidate);
|
||||
},
|
||||
onICEComplete: function() {
|
||||
return onICEComplete(self);
|
||||
},
|
||||
onRemoteStream: function(stream) {
|
||||
return onRemoteStream(self, stream);
|
||||
},
|
||||
onOfferSDP: function(sdp) {
|
||||
return onOfferSDP(self, sdp);
|
||||
},
|
||||
onICESDP: function(sdp) {
|
||||
return onICESDP(self, sdp);
|
||||
},
|
||||
onChannelError: function(e) {
|
||||
return onChannelError(self, e);
|
||||
},
|
||||
constraints: self.constraints
|
||||
});
|
||||
|
||||
onStreamSuccess(self);
|
||||
}
|
||||
|
||||
function onError() {
|
||||
onStreamError(self);
|
||||
}
|
||||
|
||||
getUserMedia({
|
||||
constraints: {
|
||||
audio: true,
|
||||
video: this.options.useVideo ? {
|
||||
mandatory: this.options.videoParams,
|
||||
optional: []
|
||||
} : null
|
||||
},
|
||||
video: this.options.useVideo ? true : false,
|
||||
onsuccess: onSuccess,
|
||||
onerror: onError
|
||||
});
|
||||
|
||||
/*
|
||||
navigator.getUserMedia({
|
||||
video: this.options.useVideo,
|
||||
audio: true
|
||||
}, onSuccess, onError);
|
||||
*/
|
||||
|
||||
};
|
||||
|
||||
// DERIVED from RTCPeerConnection-v1.5
|
||||
// 2013, @muazkh - github.com/muaz-khan
|
||||
// MIT License - https://www.webrtc-experiment.com/licence/
|
||||
// Documentation - https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RTCPeerConnection
|
||||
window.moz = !!navigator.mozGetUserMedia;
|
||||
|
||||
function RTCPeerConnection(options) {
|
||||
|
||||
var w = window,
|
||||
PeerConnection = w.mozRTCPeerConnection || w.webkitRTCPeerConnection,
|
||||
SessionDescription = w.mozRTCSessionDescription || w.RTCSessionDescription,
|
||||
IceCandidate = w.mozRTCIceCandidate || w.RTCIceCandidate;
|
||||
|
||||
var STUN = {
|
||||
url: !moz ? 'stun:stun.l.google.com:19302' : 'stun:23.21.150.121'
|
||||
};
|
||||
|
||||
var TURN = {
|
||||
url: 'turn:homeo@turn.bistri.com:80',
|
||||
credential: 'homeo'
|
||||
};
|
||||
|
||||
var iceServers = {
|
||||
iceServers: options.iceServers || [STUN]
|
||||
};
|
||||
|
||||
if (!moz && !options.iceServers) {
|
||||
if (parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2]) >= 28) TURN = {
|
||||
url: 'turn:turn.bistri.com:80',
|
||||
credential: 'homeo',
|
||||
username: 'homeo'
|
||||
};
|
||||
|
||||
iceServers.iceServers = [STUN];
|
||||
}
|
||||
|
||||
var optional = {
|
||||
optional: []
|
||||
};
|
||||
|
||||
if (!moz) {
|
||||
optional.optional = [{
|
||||
DtlsSrtpKeyAgreement: true
|
||||
},
|
||||
{
|
||||
RtpDataChannels: options.onChannelMessage ? true : false
|
||||
}];
|
||||
}
|
||||
|
||||
var peer = new PeerConnection(iceServers, optional);
|
||||
|
||||
openOffererChannel();
|
||||
var x = 0;
|
||||
|
||||
peer.onicecandidate = function(event) {
|
||||
console.log("WTF ICE", event);
|
||||
|
||||
if (event.candidate) {
|
||||
options.onICE(event.candidate);
|
||||
} else {
|
||||
if (options.onICEComplete) {
|
||||
options.onICEComplete();
|
||||
}
|
||||
|
||||
if (options.type == "offer") {
|
||||
if (!moz && !x && options.onICESDP) {
|
||||
options.onICESDP(peer.localDescription);
|
||||
//x = 1;
|
||||
/*
|
||||
x = 1;
|
||||
peer.createOffer(function(sessionDescription) {
|
||||
sessionDescription.sdp = serializeSdp(sessionDescription.sdp);
|
||||
peer.setLocalDescription(sessionDescription);
|
||||
if (options.onICESDP) {
|
||||
options.onICESDP(sessionDescription);
|
||||
}
|
||||
}, onSdpError, constraints);
|
||||
*/
|
||||
}
|
||||
} else {
|
||||
if (!x && options.onICESDP) {
|
||||
options.onICESDP(peer.localDescription);
|
||||
//x = 1;
|
||||
/*
|
||||
x = 1;
|
||||
peer.createAnswer(function(sessionDescription) {
|
||||
sessionDescription.sdp = serializeSdp(sessionDescription.sdp);
|
||||
peer.setLocalDescription(sessionDescription);
|
||||
if (options.onICESDP) {
|
||||
options.onICESDP(sessionDescription);
|
||||
}
|
||||
}, onSdpError, constraints);
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// attachStream = MediaStream;
|
||||
if (options.attachStream) peer.addStream(options.attachStream);
|
||||
|
||||
// attachStreams[0] = audio-stream;
|
||||
// attachStreams[1] = video-stream;
|
||||
// attachStreams[2] = screen-capturing-stream;
|
||||
if (options.attachStreams && options.attachStream.length) {
|
||||
var streams = options.attachStreams;
|
||||
for (var i = 0; i < streams.length; i++) {
|
||||
peer.addStream(streams[i]);
|
||||
}
|
||||
}
|
||||
|
||||
peer.onaddstream = function(event) {
|
||||
var remoteMediaStream = event.stream;
|
||||
|
||||
// onRemoteStreamEnded(MediaStream)
|
||||
remoteMediaStream.onended = function() {
|
||||
if (options.onRemoteStreamEnded) options.onRemoteStreamEnded(remoteMediaStream);
|
||||
};
|
||||
|
||||
// onRemoteStream(MediaStream)
|
||||
if (options.onRemoteStream) options.onRemoteStream(remoteMediaStream);
|
||||
|
||||
//console.debug('on:add:stream', remoteMediaStream);
|
||||
};
|
||||
|
||||
var constraints = options.constraints || {
|
||||
optional: [],
|
||||
mandatory: {
|
||||
OfferToReceiveAudio: true,
|
||||
OfferToReceiveVideo: true
|
||||
}
|
||||
};
|
||||
|
||||
// onOfferSDP(RTCSessionDescription)
|
||||
function createOffer() {
|
||||
if (!options.onOfferSDP) return;
|
||||
|
||||
peer.createOffer(function(sessionDescription) {
|
||||
sessionDescription.sdp = serializeSdp(sessionDescription.sdp);
|
||||
peer.setLocalDescription(sessionDescription);
|
||||
options.onOfferSDP(sessionDescription);
|
||||
if (moz && options.onICESDP) {
|
||||
options.onICESDP(sessionDescription);
|
||||
}
|
||||
},
|
||||
onSdpError, constraints);
|
||||
}
|
||||
|
||||
// onAnswerSDP(RTCSessionDescription)
|
||||
function createAnswer() {
|
||||
if (options.type != "answer") return;
|
||||
|
||||
//options.offerSDP.sdp = addStereo(options.offerSDP.sdp);
|
||||
peer.setRemoteDescription(new SessionDescription(options.offerSDP), onSdpSuccess, onSdpError);
|
||||
peer.createAnswer(function(sessionDescription) {
|
||||
sessionDescription.sdp = serializeSdp(sessionDescription.sdp);
|
||||
peer.setLocalDescription(sessionDescription);
|
||||
if (options.onAnswerSDP) {
|
||||
options.onAnswerSDP(sessionDescription);
|
||||
}
|
||||
},
|
||||
onSdpError, constraints);
|
||||
}
|
||||
|
||||
// if Mozilla Firefox & DataChannel; offer/answer will be created later
|
||||
if ((options.onChannelMessage && !moz) || !options.onChannelMessage) {
|
||||
createOffer();
|
||||
createAnswer();
|
||||
}
|
||||
|
||||
// DataChannel Bandwidth
|
||||
function setBandwidth(sdp) {
|
||||
// remove existing bandwidth lines
|
||||
sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, '');
|
||||
sdp = sdp.replace(/a=mid:data\r\n/g, 'a=mid:data\r\nb=AS:1638400\r\n');
|
||||
|
||||
return sdp;
|
||||
}
|
||||
|
||||
// old: FF<>Chrome interoperability management
|
||||
function getInteropSDP(sdp) {
|
||||
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
|
||||
extractedChars = '';
|
||||
|
||||
function getChars() {
|
||||
extractedChars += chars[parseInt(Math.random() * 40)] || '';
|
||||
if (extractedChars.length < 40) getChars();
|
||||
|
||||
return extractedChars;
|
||||
}
|
||||
|
||||
// usually audio-only streaming failure occurs out of audio-specific crypto line
|
||||
// a=crypto:1 AES_CM_128_HMAC_SHA1_32 --------- kAttributeCryptoVoice
|
||||
if (options.onAnswerSDP) sdp = sdp.replace(/(a=crypto:0 AES_CM_128_HMAC_SHA1_32)(.*?)(\r\n)/g, '');
|
||||
|
||||
// video-specific crypto line i.e. SHA1_80
|
||||
// a=crypto:1 AES_CM_128_HMAC_SHA1_80 --------- kAttributeCryptoVideo
|
||||
var inline = getChars() + '\r\n' + (extractedChars = '');
|
||||
sdp = sdp.indexOf('a=crypto') == -1 ? sdp.replace(/c=IN/g, 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:' + inline + 'c=IN') : sdp;
|
||||
|
||||
return sdp;
|
||||
}
|
||||
|
||||
function serializeSdp(sdp) {
|
||||
//if (!moz) sdp = setBandwidth(sdp);
|
||||
//sdp = getInteropSDP(sdp);
|
||||
//console.debug(sdp);
|
||||
return sdp;
|
||||
}
|
||||
|
||||
// DataChannel management
|
||||
var channel;
|
||||
|
||||
function openOffererChannel() {
|
||||
if (!options.onChannelMessage || (moz && !options.onOfferSDP)) return;
|
||||
|
||||
_openOffererChannel();
|
||||
|
||||
if (!moz) return;
|
||||
navigator.mozGetUserMedia({
|
||||
audio: true,
|
||||
fake: true
|
||||
},
|
||||
function(stream) {
|
||||
peer.addStream(stream);
|
||||
createOffer();
|
||||
},
|
||||
useless);
|
||||
}
|
||||
|
||||
function _openOffererChannel() {
|
||||
channel = peer.createDataChannel(options.channel || 'RTCDataChannel', moz ? {} : {
|
||||
reliable: false
|
||||
});
|
||||
|
||||
if (moz) channel.binaryType = 'blob';
|
||||
|
||||
setChannelEvents();
|
||||
}
|
||||
|
||||
function setChannelEvents() {
|
||||
channel.onmessage = function(event) {
|
||||
if (options.onChannelMessage) options.onChannelMessage(event);
|
||||
};
|
||||
|
||||
channel.onopen = function() {
|
||||
if (options.onChannelOpened) options.onChannelOpened(channel);
|
||||
};
|
||||
channel.onclose = function(event) {
|
||||
if (options.onChannelClosed) options.onChannelClosed(event);
|
||||
|
||||
console.warn('WebRTC DataChannel closed', event);
|
||||
};
|
||||
channel.onerror = function(event) {
|
||||
if (options.onChannelError) options.onChannelError(event);
|
||||
|
||||
console.error('WebRTC DataChannel error', event);
|
||||
};
|
||||
}
|
||||
|
||||
if (options.onAnswerSDP && moz && options.onChannelMessage) openAnswererChannel();
|
||||
|
||||
function openAnswererChannel() {
|
||||
peer.ondatachannel = function(event) {
|
||||
channel = event.channel;
|
||||
channel.binaryType = 'blob';
|
||||
setChannelEvents();
|
||||
};
|
||||
|
||||
if (!moz) return;
|
||||
navigator.mozGetUserMedia({
|
||||
audio: true,
|
||||
fake: true
|
||||
},
|
||||
function(stream) {
|
||||
peer.addStream(stream);
|
||||
createAnswer();
|
||||
},
|
||||
useless);
|
||||
}
|
||||
|
||||
// fake:true is also available on chrome under a flag!
|
||||
function useless() {
|
||||
log('Error in fake:true');
|
||||
}
|
||||
|
||||
function onSdpSuccess() {}
|
||||
|
||||
function onSdpError(e) {
|
||||
if (options.onChannelError) {
|
||||
options.onChannelError(e);
|
||||
}
|
||||
console.error('sdp error:', e);
|
||||
}
|
||||
|
||||
return {
|
||||
addAnswerSDP: function(sdp, cbSuccess, cbError) {
|
||||
|
||||
peer.setRemoteDescription(new SessionDescription(sdp), cbSuccess ? cbSuccess : onSdpSuccess, cbError ? cbError : onSdpError);
|
||||
},
|
||||
addICE: function(candidate) {
|
||||
peer.addIceCandidate(new IceCandidate({
|
||||
sdpMLineIndex: candidate.sdpMLineIndex,
|
||||
candidate: candidate.candidate
|
||||
}));
|
||||
},
|
||||
|
||||
peer: peer,
|
||||
channel: channel,
|
||||
sendData: function(message) {
|
||||
if (channel) {
|
||||
channel.send(message);
|
||||
}
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
peer.close();
|
||||
if (options.attachStream) {
|
||||
options.attachStream.stop();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
// getUserMedia
|
||||
var video_constraints = {
|
||||
mandatory: {},
|
||||
optional: []
|
||||
};
|
||||
|
||||
function getUserMedia(options) {
|
||||
var n = navigator,
|
||||
media;
|
||||
n.getMedia = n.webkitGetUserMedia || n.mozGetUserMedia;
|
||||
n.getMedia(options.constraints || {
|
||||
audio: true,
|
||||
video: video_constraints
|
||||
},
|
||||
streaming, options.onerror ||
|
||||
function(e) {
|
||||
console.error(e);
|
||||
});
|
||||
|
||||
function streaming(stream) {
|
||||
var video = options.video;
|
||||
if (video) {
|
||||
video[moz ? 'mozSrcObject' : 'src'] = moz ? stream : window.webkitURL.createObjectURL(stream);
|
||||
//video.play();
|
||||
}
|
||||
if (options.onsuccess) {
|
||||
options.onsuccess(stream);
|
||||
}
|
||||
media = stream;
|
||||
}
|
||||
|
||||
return media;
|
||||
}
|
||||
|
||||
})(jQuery);
|
653
html5/verto/js/src/jquery.jsonrpcclient.js
Normal file
653
html5/verto/js/src/jquery.jsonrpcclient.js
Normal file
@@ -0,0 +1,653 @@
|
||||
/*
|
||||
* Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
|
||||
* Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
|
||||
*
|
||||
* Version: MPL 1.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is jquery.jsonrpclient.js modified for Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Textalk AB http://textalk.se/
|
||||
* Portions created by the Initial Developer are Copyright (C)
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Anthony Minessale II <anthm@freeswitch.org>
|
||||
*
|
||||
* jquery.jsonrpclient.js - JSON RPC client code
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* This plugin requires jquery.json.js to be available, or at least the methods $.toJSON and
|
||||
* $.parseJSON.
|
||||
*
|
||||
* The plan is to make use of websockets if they are available, but work just as well with only
|
||||
* http if not.
|
||||
*
|
||||
* Usage example:
|
||||
*
|
||||
* var foo = new $.JsonRpcClient({ ajaxUrl: '/backend/jsonrpc' });
|
||||
* foo.call(
|
||||
* 'bar', [ 'A parameter', 'B parameter' ],
|
||||
* function(result) { alert('Foo bar answered: ' + result.my_answer); },
|
||||
* function(error) { console.log('There was an error', error); }
|
||||
* );
|
||||
*
|
||||
* More examples are available in README.md
|
||||
*/
|
||||
(function($) {
|
||||
/**
|
||||
* @fn new
|
||||
* @memberof $.JsonRpcClient
|
||||
*
|
||||
* @param options An object stating the backends:
|
||||
* ajaxUrl A url (relative or absolute) to a http(s) backend.
|
||||
* socketUrl A url (relative of absolute) to a ws(s) backend.
|
||||
* onmessage A socket message handler for other messages (non-responses).
|
||||
* getSocket A function returning a WebSocket or null.
|
||||
* It must take an onmessage_cb and bind it to the onmessage event
|
||||
* (or chain it before/after some other onmessage handler).
|
||||
* Or, it could return null if no socket is available.
|
||||
* The returned instance must have readyState <= 1, and if less than 1,
|
||||
* react to onopen binding.
|
||||
*/
|
||||
$.JsonRpcClient = function(options) {
|
||||
var self = this;
|
||||
this.options = $.extend({
|
||||
ajaxUrl : null,
|
||||
socketUrl : null, ///< The ws-url for default getSocket.
|
||||
onmessage : null, ///< Other onmessage-handler.
|
||||
login : null, /// auth login
|
||||
passwd : null, /// auth passwd
|
||||
sessid : null,
|
||||
getSocket : function(onmessage_cb) { return self._getSocket(onmessage_cb); }
|
||||
}, options);
|
||||
|
||||
// Declare an instance version of the onmessage callback to wrap 'this'.
|
||||
this.wsOnMessage = function(event) { self._wsOnMessage(event); };
|
||||
};
|
||||
|
||||
/// Holding the WebSocket on default getsocket.
|
||||
$.JsonRpcClient.prototype._ws_socket = null;
|
||||
|
||||
/// Object <id>: { success_cb: cb, error_cb: cb }
|
||||
$.JsonRpcClient.prototype._ws_callbacks = {};
|
||||
|
||||
/// The next JSON-RPC request id.
|
||||
$.JsonRpcClient.prototype._current_id = 1;
|
||||
|
||||
/**
|
||||
* @fn call
|
||||
* @memberof $.JsonRpcClient
|
||||
*
|
||||
* @param method The method to run on JSON-RPC server.
|
||||
* @param params The params; an array or object.
|
||||
* @param success_cb A callback for successful request.
|
||||
* @param error_cb A callback for error.
|
||||
*/
|
||||
$.JsonRpcClient.prototype.call = function(method, params, success_cb, error_cb) {
|
||||
// Construct the JSON-RPC 2.0 request.
|
||||
|
||||
if (!params) {
|
||||
params = {};
|
||||
}
|
||||
|
||||
if (this.options.sessid) {
|
||||
params.sessid = this.options.sessid;
|
||||
}
|
||||
|
||||
var request = {
|
||||
jsonrpc : '2.0',
|
||||
method : method,
|
||||
params : params,
|
||||
id : this._current_id++ // Increase the id counter to match request/response
|
||||
};
|
||||
|
||||
if (!success_cb) {
|
||||
success_cb = function(e){console.log("Success: ", e);};
|
||||
}
|
||||
|
||||
if (!error_cb) {
|
||||
error_cb = function(e){console.log("Error: ", e);};
|
||||
}
|
||||
|
||||
// Try making a WebSocket call.
|
||||
var socket = this.options.getSocket(this.wsOnMessage);
|
||||
if (socket !== null) {
|
||||
this._wsCall(socket, request, success_cb, error_cb);
|
||||
return;
|
||||
}
|
||||
|
||||
// No WebSocket, and no HTTP backend? This won't work.
|
||||
if (this.options.ajaxUrl === null) {
|
||||
throw "$.JsonRpcClient.call used with no websocket and no http endpoint.";
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : this.options.ajaxUrl,
|
||||
data : $.toJSON(request),
|
||||
dataType : 'json',
|
||||
cache : false,
|
||||
|
||||
success : function(data) {
|
||||
if ('error' in data) error_cb(data.error, this);
|
||||
success_cb(data.result, this);
|
||||
},
|
||||
|
||||
// JSON-RPC Server could return non-200 on error
|
||||
error : function(jqXHR, textStatus, errorThrown) {
|
||||
try {
|
||||
var response = $.parseJSON(jqXHR.responseText);
|
||||
if ('console' in window) console.log(response);
|
||||
error_cb(response.error, this);
|
||||
}
|
||||
catch (err) {
|
||||
// Perhaps the responseText wasn't really a jsonrpc-error.
|
||||
error_cb({ error: jqXHR.responseText }, this);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify sends a command to the server that won't need a response. In http, there is probably
|
||||
* an empty response - that will be dropped, but in ws there should be no response at all.
|
||||
*
|
||||
* This is very similar to call, but has no id and no handling of callbacks.
|
||||
*
|
||||
* @fn notify
|
||||
* @memberof $.JsonRpcClient
|
||||
*
|
||||
* @param method The method to run on JSON-RPC server.
|
||||
* @param params The params; an array or object.
|
||||
*/
|
||||
$.JsonRpcClient.prototype.notify = function(method, params) {
|
||||
// Construct the JSON-RPC 2.0 request.
|
||||
|
||||
if (this.options.sessid) {
|
||||
params.sessid = this.options.sessid;
|
||||
}
|
||||
|
||||
var request = {
|
||||
jsonrpc: '2.0',
|
||||
method: method,
|
||||
params: params
|
||||
};
|
||||
|
||||
// Try making a WebSocket call.
|
||||
var socket = this.options.getSocket(this.wsOnMessage);
|
||||
if (socket !== null) {
|
||||
this._wsCall(socket, request);
|
||||
return;
|
||||
}
|
||||
|
||||
// No WebSocket, and no HTTP backend? This won't work.
|
||||
if (this.options.ajaxUrl === null) {
|
||||
throw "$.JsonRpcClient.notify used with no websocket and no http endpoint.";
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : this.options.ajaxUrl,
|
||||
data : $.toJSON(request),
|
||||
dataType : 'json',
|
||||
cache : false
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a batch-call by using a callback.
|
||||
*
|
||||
* The callback will get an object "batch" as only argument. On batch, you can call the methods
|
||||
* "call" and "notify" just as if it was a normal $.JsonRpcClient object, and all calls will be
|
||||
* sent as a batch call then the callback is done.
|
||||
*
|
||||
* @fn batch
|
||||
* @memberof $.JsonRpcClient
|
||||
*
|
||||
* @param callback The main function which will get a batch handler to run call and notify on.
|
||||
* @param all_done_cb A callback function to call after all results have been handled.
|
||||
* @param error_cb A callback function to call if there is an error from the server.
|
||||
* Note, that batch calls should always get an overall success, and the
|
||||
* only error
|
||||
*/
|
||||
$.JsonRpcClient.prototype.batch = function(callback, all_done_cb, error_cb) {
|
||||
var batch = new $.JsonRpcClient._batchObject(this, all_done_cb, error_cb);
|
||||
callback(batch);
|
||||
batch._execute();
|
||||
};
|
||||
|
||||
/**
|
||||
* The default getSocket handler.
|
||||
*
|
||||
* @param onmessage_cb The callback to be bound to onmessage events on the socket.
|
||||
*
|
||||
* @fn _getSocket
|
||||
* @memberof $.JsonRpcClient
|
||||
*/
|
||||
|
||||
$.JsonRpcClient.prototype.socketReady = function() {
|
||||
if (this._ws_socket === null || this._ws_socket.readyState > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$.JsonRpcClient.prototype.closeSocket = function() {
|
||||
if (self.socketReady()) {
|
||||
this._ws_socket.onclose = function (w) {console.log("Closing Socket")}
|
||||
this._ws_socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
$.JsonRpcClient.prototype.loginData = function(params) {
|
||||
self.options.login = params.login;
|
||||
self.options.passwd = params.passwd;
|
||||
}
|
||||
|
||||
$.JsonRpcClient.prototype.connectSocket = function(onmessage_cb) {
|
||||
var self = this;
|
||||
|
||||
if (!self.socketReady()) {
|
||||
self.authing = false;
|
||||
// No socket, or dying socket, let's get a new one.
|
||||
this._ws_socket = new WebSocket(this.options.socketUrl);
|
||||
|
||||
if (this._ws_socket) {
|
||||
// Set up onmessage handler.
|
||||
this._ws_socket.onmessage = onmessage_cb;
|
||||
this._ws_socket.onclose = function (w) {
|
||||
if (!self.ws_sleep) {
|
||||
self.ws_sleep = 2;
|
||||
}
|
||||
|
||||
self.ws_cnt = 0;
|
||||
|
||||
if (self.options.onWSClose) {
|
||||
self.options.onWSClose(self);
|
||||
}
|
||||
|
||||
console.error("Websocket Lost sleep: " + self.ws_sleep + "sec");
|
||||
|
||||
setTimeout(function() {
|
||||
console.log("Attempting Reconnection....");
|
||||
self.connectSocket(onmessage_cb);
|
||||
}, self.ws_sleep * 1000);
|
||||
|
||||
|
||||
if (++self.ws_cnt >= 150) {
|
||||
self.ws_sleep = 30;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up sending of message for when the socket is open.
|
||||
this._ws_socket.onopen = function() {
|
||||
this.ws_sleep = 2;
|
||||
this.ws_cnt = 0;
|
||||
if (self.options.onWSConnect) {
|
||||
self.options.onWSConnect(self);
|
||||
}
|
||||
|
||||
var req;
|
||||
// Send the requests.
|
||||
while (req = $.JsonRpcClient.q.pop()) {
|
||||
self._ws_socket.send(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this._ws_socket ? true : false;
|
||||
}
|
||||
|
||||
$.JsonRpcClient.prototype._getSocket = function(onmessage_cb) {
|
||||
// If there is no ws url set, we don't have a socket.
|
||||
// Likewise, if there is no window.WebSocket.
|
||||
if (this.options.socketUrl === null || !("WebSocket" in window)) return null;
|
||||
|
||||
this.connectSocket(onmessage_cb);
|
||||
|
||||
return this._ws_socket;
|
||||
};
|
||||
|
||||
/**
|
||||
* Queue to save messages delivered when websocket is not ready
|
||||
*/
|
||||
$.JsonRpcClient.q = [];
|
||||
|
||||
/**
|
||||
* Internal handler to dispatch a JRON-RPC request through a websocket.
|
||||
*
|
||||
* @fn _wsCall
|
||||
* @memberof $.JsonRpcClient
|
||||
*/
|
||||
$.JsonRpcClient.prototype._wsCall = function(socket, request, success_cb, error_cb) {
|
||||
var request_json = $.toJSON(request);
|
||||
|
||||
if (socket.readyState < 1) {
|
||||
// The websocket is not open yet; we have to set sending of the message in onopen.
|
||||
self = this; // In closure below, this is set to the WebSocket. Use self instead.
|
||||
|
||||
$.JsonRpcClient.q.push(request_json);
|
||||
|
||||
|
||||
}
|
||||
else {
|
||||
// We have a socket and it should be ready to send on.
|
||||
socket.send(request_json);
|
||||
}
|
||||
|
||||
// Setup callbacks. If there is an id, this is a call and not a notify.
|
||||
if ('id' in request && typeof success_cb !== 'undefined') {
|
||||
this._ws_callbacks[request.id] = { request: request_json, request_obj: request, success_cb: success_cb, error_cb: error_cb };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal handler for the websocket messages. It determines if the message is a JSON-RPC
|
||||
* response, and if so, tries to couple it with a given callback. Otherwise, it falls back to
|
||||
* given external onmessage-handler, if any.
|
||||
*
|
||||
* @param event The websocket onmessage-event.
|
||||
*/
|
||||
$.JsonRpcClient.prototype._wsOnMessage = function(event) {
|
||||
// Check if this could be a JSON RPC message.
|
||||
var response;
|
||||
try {
|
||||
response = $.parseJSON(event.data);
|
||||
|
||||
/// @todo Make using the jsonrcp 2.0 check optional, to use this on JSON-RPC 1 backends.
|
||||
|
||||
if (typeof response === 'object'
|
||||
&& 'jsonrpc' in response
|
||||
&& response.jsonrpc === '2.0') {
|
||||
|
||||
/// @todo Handle bad response (without id).
|
||||
|
||||
// If this is an object with result, it is a response.
|
||||
if ('result' in response && this._ws_callbacks[response.id]) {
|
||||
// Get the success callback.
|
||||
var success_cb = this._ws_callbacks[response.id].success_cb;
|
||||
|
||||
/*
|
||||
// set the sessid if present
|
||||
if ('sessid' in response.result && !this.options.sessid || (this.options.sessid != response.result.sessid)) {
|
||||
this.options.sessid = response.result.sessid;
|
||||
if (this.options.sessid) {
|
||||
console.log("setting session UUID to: " + this.options.sessid);
|
||||
}
|
||||
}
|
||||
*/
|
||||
// Delete the callback from the storage.
|
||||
delete this._ws_callbacks[response.id];
|
||||
|
||||
// Run callback with result as parameter.
|
||||
success_cb(response.result, this);
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is an object with error, it is an error response.
|
||||
else if ('error' in response && this._ws_callbacks[response.id]) {
|
||||
|
||||
// Get the error callback.
|
||||
var error_cb = this._ws_callbacks[response.id].error_cb;
|
||||
var orig_req = this._ws_callbacks[response.id].request;
|
||||
|
||||
// if this is an auth request, send the credentials and resend the failed request
|
||||
if (!self.authing && response.error.code == -32000 && self.options.login && self.options.passwd) {
|
||||
self.authing = true;
|
||||
|
||||
this.call("login", { login: self.options.login, passwd: self.options.passwd},
|
||||
this._ws_callbacks[response.id].request_obj.method == "login"
|
||||
?
|
||||
function(e) {
|
||||
self.authing = false;
|
||||
console.log("logged in");
|
||||
delete self._ws_callbacks[response.id];
|
||||
|
||||
if (self.options.onWSLogin) {
|
||||
self.options.onWSLogin(true, self);
|
||||
}
|
||||
}
|
||||
|
||||
:
|
||||
|
||||
function(e) {
|
||||
self.authing = false;
|
||||
console.log("logged in, resending request id: " + response.id);
|
||||
var socket = self.options.getSocket(self.wsOnMessage);
|
||||
if (socket !== null) {
|
||||
socket.send(orig_req);
|
||||
}
|
||||
if (self.options.onWSLogin) {
|
||||
self.options.onWSLogin(true, self);
|
||||
}
|
||||
},
|
||||
|
||||
function(e) {
|
||||
console.log("error logging in, request id:", response.id);
|
||||
delete self._ws_callbacks[response.id];
|
||||
error_cb(response.error, this);
|
||||
if (self.options.onWSLogin) {
|
||||
self.options.onWSLogin(false, self);
|
||||
}
|
||||
}
|
||||
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete the callback from the storage.
|
||||
delete this._ws_callbacks[response.id];
|
||||
|
||||
// Run callback with the error object as parameter.
|
||||
error_cb(response.error, this);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
// Probably an error while parsing a non json-string as json. All real JSON-RPC cases are
|
||||
// handled above, and the fallback method is called below.
|
||||
console.log("ERROR: "+ err);
|
||||
return;
|
||||
}
|
||||
|
||||
// This is not a JSON-RPC response. Call the fallback message handler, if given.
|
||||
if (typeof this.options.onmessage === 'function') {
|
||||
event.eventData = response;
|
||||
if (!event.eventData) {
|
||||
event.eventData = {};
|
||||
}
|
||||
|
||||
var reply = this.options.onmessage(event);
|
||||
|
||||
if (reply && typeof reply === "object" && event.eventData.id) {
|
||||
var msg = {
|
||||
jsonrpc: "2.0",
|
||||
id: event.eventData.id,
|
||||
result: reply
|
||||
};
|
||||
|
||||
var socket = self.options.getSocket(self.wsOnMessage);
|
||||
if (socket !== null) {
|
||||
socket.send($.toJSON(msg));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/************************************************************************************************
|
||||
* Batch object with methods
|
||||
************************************************************************************************/
|
||||
|
||||
/**
|
||||
* Handling object for batch calls.
|
||||
*/
|
||||
$.JsonRpcClient._batchObject = function(jsonrpcclient, all_done_cb, error_cb) {
|
||||
// Array of objects to hold the call and notify requests. Each objects will have the request
|
||||
// object, and unless it is a notify, success_cb and error_cb.
|
||||
this._requests = [];
|
||||
|
||||
this.jsonrpcclient = jsonrpcclient;
|
||||
this.all_done_cb = all_done_cb;
|
||||
this.error_cb = typeof error_cb === 'function' ? error_cb : function() {};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @sa $.JsonRpcClient.prototype.call
|
||||
*/
|
||||
$.JsonRpcClient._batchObject.prototype.call = function(method, params, success_cb, error_cb) {
|
||||
|
||||
if (!params) {
|
||||
params = {};
|
||||
}
|
||||
|
||||
if (this.options.sessid) {
|
||||
params.sessid = this.options.sessid;
|
||||
}
|
||||
|
||||
if (!success_cb) {
|
||||
success_cb = function(e){console.log("Success: ", e);};
|
||||
}
|
||||
|
||||
if (!error_cb) {
|
||||
error_cb = function(e){console.log("Error: ", e);};
|
||||
}
|
||||
|
||||
this._requests.push({
|
||||
request : {
|
||||
jsonrpc : '2.0',
|
||||
method : method,
|
||||
params : params,
|
||||
id : this.jsonrpcclient._current_id++ // Use the client's id series.
|
||||
},
|
||||
success_cb : success_cb,
|
||||
error_cb : error_cb
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @sa $.JsonRpcClient.prototype.notify
|
||||
*/
|
||||
$.JsonRpcClient._batchObject.prototype.notify = function(method, params) {
|
||||
if (this.options.sessid) {
|
||||
params.sessid = this.options.sessid;
|
||||
}
|
||||
|
||||
this._requests.push({
|
||||
request : {
|
||||
jsonrpc : '2.0',
|
||||
method : method,
|
||||
params : params
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes the batched up calls.
|
||||
*/
|
||||
$.JsonRpcClient._batchObject.prototype._execute = function() {
|
||||
var self = this;
|
||||
|
||||
if (this._requests.length === 0) return; // All done :P
|
||||
|
||||
// Collect all request data and sort handlers by request id.
|
||||
var batch_request = [];
|
||||
var handlers = {};
|
||||
|
||||
// If we have a WebSocket, just send the requests individually like normal calls.
|
||||
var socket = self.jsonrpcclient.options.getSocket(self.jsonrpcclient.wsOnMessage);
|
||||
if (socket !== null) {
|
||||
for (var i = 0; i < this._requests.length; i++) {
|
||||
var call = this._requests[i];
|
||||
var success_cb = ('success_cb' in call) ? call.success_cb : undefined;
|
||||
var error_cb = ('error_cb' in call) ? call.error_cb : undefined;
|
||||
self.jsonrpcclient._wsCall(socket, call.request, success_cb, error_cb);
|
||||
}
|
||||
if (typeof all_done_cb === 'function') all_done_cb(result);
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < this._requests.length; i++) {
|
||||
var call = this._requests[i];
|
||||
batch_request.push(call.request);
|
||||
|
||||
// If the request has an id, it should handle returns (otherwise it's a notify).
|
||||
if ('id' in call.request) {
|
||||
handlers[call.request.id] = {
|
||||
success_cb : call.success_cb,
|
||||
error_cb : call.error_cb
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var success_cb = function(data) { self._batchCb(data, handlers, self.all_done_cb); };
|
||||
|
||||
// No WebSocket, and no HTTP backend? This won't work.
|
||||
if (self.jsonrpcclient.options.ajaxUrl === null) {
|
||||
throw "$.JsonRpcClient.batch used with no websocket and no http endpoint.";
|
||||
}
|
||||
|
||||
// Send request
|
||||
$.ajax({
|
||||
url : self.jsonrpcclient.options.ajaxUrl,
|
||||
data : $.toJSON(batch_request),
|
||||
dataType : 'json',
|
||||
cache : false,
|
||||
type : 'POST',
|
||||
|
||||
// Batch-requests should always return 200
|
||||
error : function(jqXHR, textStatus, errorThrown) {
|
||||
self.error_cb(jqXHR, textStatus, errorThrown);
|
||||
},
|
||||
success : success_cb
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal helper to match the result array from a batch call to their respective callbacks.
|
||||
*
|
||||
* @fn _batchCb
|
||||
* @memberof $.JsonRpcClient
|
||||
*/
|
||||
$.JsonRpcClient._batchObject.prototype._batchCb = function(result, handlers, all_done_cb) {
|
||||
for (var i = 0; i < result.length; i++) {
|
||||
var response = result[i];
|
||||
|
||||
// Handle error
|
||||
if ('error' in response) {
|
||||
if (response.id === null || !(response.id in handlers)) {
|
||||
// An error on a notify? Just log it to the console.
|
||||
if ('console' in window) console.log(response);
|
||||
}
|
||||
else handlers[response.id].error_cb(response.error, this);
|
||||
}
|
||||
else {
|
||||
// Here we should always have a correct id and no error.
|
||||
if (!(response.id in handlers) && 'console' in window) console.log(response);
|
||||
else handlers[response.id].success_cb(response.result, this);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof all_done_cb === 'function') all_done_cb(result);
|
||||
};
|
||||
|
||||
})(jQuery);
|
1661
html5/verto/js/src/jquery.verto.js
Normal file
1661
html5/verto/js/src/jquery.verto.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user