Commit 5f80694f authored by Sven Franck's avatar Sven Franck

initial commit

parents
/*global window, RSVP, FileReader */
/*jslint indent: 2, maxerr: 3, unparam: true */
(function (window, RSVP, FileReader) {
"use strict";
window.loopEventListener = function (target, type, useCapture, callback,
allowDefault) {
//////////////////////////
// Infinite event listener (promise is never resolved)
// eventListener is removed when promise is cancelled/rejected
//////////////////////////
var handle_event_callback,
callback_promise;
function cancelResolver() {
if ((callback_promise !== undefined) &&
(typeof callback_promise.cancel === "function")) {
callback_promise.cancel();
}
}
function canceller() {
if (handle_event_callback !== undefined) {
target.removeEventListener(type, handle_event_callback, useCapture);
}
cancelResolver();
}
function itsANonResolvableTrap(resolve, reject) {
handle_event_callback = function (evt) {
evt.stopPropagation();
if (allowDefault !== true) {
evt.preventDefault();
}
cancelResolver();
callback_promise = new RSVP.Queue()
.push(function () {
return callback(evt);
})
.push(undefined, function (error) {
if (!(error instanceof RSVP.CancellationError)) {
canceller();
reject(error);
}
});
};
target.addEventListener(type, handle_event_callback, useCapture);
}
return new RSVP.Promise(itsANonResolvableTrap, canceller);
};
window.promiseEventListener = function (target, type, useCapture) {
//////////////////////////
// Resolve the promise as soon as the event is triggered
// eventListener is removed when promise is cancelled/resolved/rejected
//////////////////////////
var handle_event_callback;
function canceller() {
target.removeEventListener(type, handle_event_callback, useCapture);
}
function resolver(resolve) {
handle_event_callback = function (evt) {
canceller();
evt.stopPropagation();
evt.preventDefault();
resolve(evt);
return false;
};
target.addEventListener(type, handle_event_callback, useCapture);
}
return new RSVP.Promise(resolver, canceller);
};
window.promiseReadAsText = function (file) {
return new RSVP.Promise(function (resolve, reject) {
var reader = new FileReader();
reader.onload = function (evt) {
resolve(evt.target.result);
};
reader.onerror = function (evt) {
reject(evt);
};
reader.readAsText(file);
});
};
}(window, RSVP, FileReader));
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title>Jio Gadget</title>
<!-- renderjs -->
<script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<script src="jiodev.js" type="text/javascript"></script>
<!-- custom script -->
<script src="gadget_jio.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
\ No newline at end of file
/*global window, rJS, jIO, FormData */
/*jslint indent: 2, maxerr: 3 */
(function (window, rJS, jIO) {
"use strict";
rJS(window)
.ready(function (gadget) {
// Initialize the gadget local parameters
gadget.state_parameter_dict = {};
})
.declareMethod('createJio', function (jio_options) {
this.state_parameter_dict.jio_storage = jIO.createJIO(jio_options);
})
.declareMethod('allDocs', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.allDocs.apply(storage, arguments);
})
.declareMethod('allAttachments', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.allAttachments.apply(storage, arguments);
})
.declareMethod('get', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.get.apply(storage, arguments);
})
.declareMethod('put', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.put.apply(storage, arguments);
})
.declareMethod('post', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.post.apply(storage, arguments);
})
.declareMethod('remove', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.remove.apply(storage, arguments);
})
.declareMethod('getAttachment', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.getAttachment.apply(storage, arguments);
})
.declareMethod('putAttachment', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.putAttachment.apply(storage, arguments);
})
.declareMethod('removeAttachment', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.removeAttachment.apply(storage, arguments);
})
.declareMethod('repair', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.repair.apply(storage, arguments);
});
}(window, rJS, jIO));
\ No newline at end of file
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Share jIO</title>
<script src="rsvp.js"></script>
<script src="renderjs.js"></script>
<script src="gadget_officejs_page_share_webrtc_jio.js"></script>
</head>
<body>
<div class="ui-grid-b ui-responsive">
<div class="ui-block-a"></div>
<div class="ui-block-b">
<p data-i18n="This is a renderJS gadget which shares a jIO storage via WebRTC.">This is a renderJS gadget which shares a jIO storage via WebRTC.</p>
<h2 data-i18n="Peer list">Peer list</h2>
<p><span data-i18n="Connected peers:">Connected peers:</span> <span class="peer_count">0</span></p>
<pre class="info"></pre>
</div>
<div class="ui-block-c"></div>
</div>
<div class="gadget_websocket">
<div data-gadget-url="gadget_websocket.html" data-gadget-scope="gadget_websocket.html">
</div>
</div>
<div class="gadget_webrtc_datachannel"></div>
</body>
</html>
/*global window, rJS, document, RSVP, console, DOMException */
/*jslint indent: 2, maxerr: 3 */
(function (window, rJS, document, RSVP, console, DOMException) {
"use strict";
function dropSubGadget(gadget, scope) {
return gadget.getDeclaredGadget(scope)
.push(function (result) {
return result.getElement();
})
.push(function (element) {
if (element.parentElement) {
element.parentElement.removeChild(element);
}
delete gadget.state_parameter_dict.scope_ip[scope];
return gadget.dropGadget(scope);
});
}
function getWebRTCScopeList(gadget) {
var result_list = [],
element_list = gadget.state_parameter_dict.element.querySelector(".gadget_webrtc_datachannel")
.childNodes,
i;
for (i = 0; i < element_list.length; i += 1) {
result_list.push(element_list[i].getAttribute("data-gadget-scope"));
}
return result_list;
}
function updateInfo(gadget) {
var scope_list = getWebRTCScopeList(gadget),
i,
result = "";
for (i = 0; i < scope_list.length; i += 1) {
result += gadget.state_parameter_dict.scope_ip[scope_list[i]] + "\n";
}
gadget.state_parameter_dict.element.querySelector(".info").textContent = result;
gadget.state_parameter_dict.element.querySelector(".peer_count").textContent = i;
}
function sendWebRTC(gadget, rtc_gadget, scope, message) {
return rtc_gadget.send(message)
.push(undefined, function (error) {
if ((error instanceof DOMException) && (error.name === 'InvalidStateError')) {
return dropSubGadget(gadget, scope)
.push(function () {
return updateInfo(gadget);
}, function (error) {
console.log("-- Can not drop remote subgadget " + scope);
console.log(error);
return;
});
}
throw error;
});
}
rJS(window)
.ready(function (gadget) {
// Initialize the gadget local parameters
gadget.state_parameter_dict = {
websocket_initialized: false,
counter: 0,
connecting: false,
scope_ip: {}
};
return gadget.getElement()
.push(function (element) {
gadget.state_parameter_dict.element = element;
})
.push(function () {
return updateInfo(gadget);
});
})
.declareAcquiredMethod("jio_allDocs", "jio_allDocs")
.declareAcquiredMethod("jio_post", "jio_post")
.declareAcquiredMethod("jio_put", "jio_put")
.declareAcquiredMethod("jio_get", "jio_get")
.declareAcquiredMethod("jio_repair", "jio_repair")
.allowPublicAcquisition('notifyDataChannelClosed', function (argument_list, scope) {
/*jslint unparam:true*/
var gadget = this;
return dropSubGadget(this, scope)
.push(function () {
return updateInfo(gadget);
});
})
.allowPublicAcquisition("notifyDataChannelMessage", function (argument_list, scope) {
var json = JSON.parse(argument_list[0]),
rtc_gadget,
context = this;
return context.getDeclaredGadget(scope)
.push(function (g) {
rtc_gadget = g;
// Call jio API
return context["jio_" + json.method_name].apply(context, json.argument_list);
})
.push(function (result) {
return sendWebRTC(context, rtc_gadget, scope, JSON.stringify({
id: json.id,
result: result,
type: "jio_response"
}));
}, function (error) {
return sendWebRTC(context, rtc_gadget, scope, JSON.stringify({
id: json.id,
result: error,
type: "error"
}));
});
})
/*
.allowPublicAcquisition("notifyWebSocketClosed", function () {
if (this.state_parameter_dict.user_type !== "user") {
throw new Error("Unexpected Web Socket connection close");
}
})
*/
.allowPublicAcquisition("notifyWebSocketMessage", function (argument_list) {
var json = JSON.parse(argument_list[0]),
scope,
rtc_gadget,
socket_gadget,
gadget = this;
if (json.action === "offer") {
// XXX https://github.com/diafygi/webrtc-ips
return gadget.getDeclaredGadget("gadget_websocket.html")
.push(function (gg) {
gadget.state_parameter_dict.connecting = true;
gadget.state_parameter_dict.counter += 1;
socket_gadget = gg;
var new_element = document.createElement("div");
gadget.state_parameter_dict.element.querySelector(".gadget_webrtc_datachannel").appendChild(new_element);
scope = "webrtc" + gadget.state_parameter_dict.counter;
return gadget.declareGadget("gadget_webrtc_datachannel.html", {
scope: scope,
element: new_element
});
})
.push(function (gg) {
rtc_gadget = gg;
// https://github.com/diafygi/webrtc-ips
var ip_regex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/,
ip_list = [],
ip_dict = {},
ip_addr,
line_list = JSON.parse(json.data).sdp.split('\n'),
i;
for (i = 0; i < line_list.length; i += 1) {
if (line_list[i].indexOf('a=candidate:') === 0) {
ip_addr = ip_regex.exec(line_list[i])[1];
if (!ip_addr.match(/^[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7}$/)) {
// Hide ipv6
if (!ip_dict[ip_addr]) {
ip_list.push(ip_addr);
ip_dict[ip_addr] = true;
}
}
}
}
gadget.state_parameter_dict.scope_ip[scope] = ip_list;
return rtc_gadget.createAnswer(json.from, json.data);
})
.push(function (local_connection) {
return socket_gadget.send(JSON.stringify({to: json.from, action: "answer", data: local_connection}));
})
.push(function () {
return RSVP.any([
RSVP.Queue()
.push(function () {
return RSVP.delay(10000);
})
.push(function () {
console.info('-- webrtc client disappears...');
return dropSubGadget(gadget, scope);
}),
rtc_gadget.waitForConnection()
]);
})
.push(function () {
gadget.state_parameter_dict.connecting = false;
return updateInfo(gadget);
});
}
})
.declareService(function () {
var sgadget,
gadget = this;
return this.getDeclaredGadget('gadget_websocket.html')
.push(function (socket_gadget) {
sgadget = socket_gadget;
return socket_gadget.createSocket("ws://127.0.0.1:9999/");
})
.push(function () {
// Wait for the gadget to be dropped from the page
// and close the socket/rtc connections
return RSVP.defer().promise;
})
.push(undefined, function (error) {
if (sgadget === undefined) {
return;
}
return sgadget.close()
.push(function () {
var scope_list = getWebRTCScopeList(gadget),
i,
promise_list = [];
function close(scope) {
return gadget.getDeclaredGadget(scope)
.push(function (rtc_gadget) {
return rtc_gadget.close();
});
}
for (i = 0; i < scope_list.length; i += 1) {
promise_list.push(close(scope_list[i]));
}
return RSVP.all(promise_list);
})
.push(function () {
throw error;
});
});
});
}(window, rJS, document, RSVP, console, DOMException));
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title>Jio Gadget</title>
<!-- renderjs -->
<script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<!-- custom script -->
<script src="gadget_officejs_webrtc_jio.js" type="text/javascript"></script>
</head>
<body>
<div data-gadget-url="gadget_jio.html"
data-gadget-scope="jio_gadget"
data-gadget-sandbox="public"></div>
<div class="gadget_websocket"></div>
<div class="gadget_webrtc_datachannel"></div>
</body>
</html>
\ No newline at end of file
/*global window, rJS, document, RSVP */
/*jslint indent: 2, maxerr: 3 */
(function (window, rJS, document, RSVP) {
"use strict";
var timeout = 60000;
function S4() {
return ('0000' + Math.floor(
Math.random() * 0x10000 /* 65536 */
).toString(16)).slice(-4);
}
function UUID() {
return S4() + S4() + "-" +
S4() + "-" +
S4() + "-" +
S4() + "-" +
S4() + S4() + S4();
}
function wrapJioAccess(gadget, method_name, argument_list) {
return gadget.getDeclaredGadget('gadget_webrtc_datachannel.html')
.push(function (rtc_gadget) {
gadget.state_parameter_dict.message_count += 1;
gadget.state_parameter_dict.message_dict[gadget.state_parameter_dict.message_count] = RSVP.defer();
return RSVP.all([
rtc_gadget.send(JSON.stringify({
id: gadget.state_parameter_dict.message_count,
type: "jio_query",
method_name: method_name,
argument_list: Array.prototype.slice.call(argument_list)
})),
RSVP.any([
RSVP.timeout(timeout),
gadget.state_parameter_dict.message_dict[gadget.state_parameter_dict.message_count].promise
])
]);
})
.push(function (result_list) {
return result_list[1];
});
}
function declareSubGadget(gadget, url) {
var container_element = gadget.state_parameter_dict.element.querySelector("." + url.split(".")[0]),
element = document.createElement("div");
container_element.innerHTML = "";
container_element.appendChild(element);
return gadget.declareGadget(url, {
element: element,
scope: url,
sandbox: "public"
});
}
rJS(window)
.ready(function (gadget) {
// Initialize the gadget local parameters
gadget.state_parameter_dict = {};
return gadget.getElement()
.push(function (element) {
gadget.state_parameter_dict.element = element;
});
})
.allowPublicAcquisition("notifyDataChannelMessage", function (argument_list) {
var json = JSON.parse(argument_list[0]),
context = this;
if (json.type === "jio_response") {
context.state_parameter_dict.message_dict[json.id].resolve(json.result);
} else if (json.type === "error") {
context.state_parameter_dict.message_dict[json.id].reject(json.result);
}
// Drop all other kind of messages
})
.allowPublicAcquisition("notifyWebSocketMessage", function (argument_list) {
var json = JSON.parse(argument_list[0]),
gadget = this;
if (json.action === "answer") {
if (json.to === gadget.state_parameter_dict.uuid) {
gadget.state_parameter_dict.answer_defer.resolve(json.data);
}
}
})
.allowPublicAcquisition("notifyWebSocketClosed", function () {
// WebSocket get closed as soon as webrtc connection is created
return;
})
.declareMethod('createJio', function (options) {
var context = this,
socket_gadget,
rtc_gadget;
return declareSubGadget(context, 'gadget_websocket.html')
.push(function (gadget) {
socket_gadget = gadget;
context.state_parameter_dict.uuid = UUID();
context.state_parameter_dict.answer_defer = RSVP.defer();
context.state_parameter_dict.message_count = 0;
context.state_parameter_dict.message_dict = {};
return declareSubGadget(context, 'gadget_websocket.html')
.push(function (gadget) {
socket_gadget = gadget;
// XXX Drop hardcoded URL
return socket_gadget.createSocket(options.socket_url);
})
.push(function () {
return declareSubGadget(context, 'gadget_webrtc_datachannel.html');
})
.push(function (gadget) {
rtc_gadget = gadget;
return rtc_gadget.createOffer(context.state_parameter_dict.uuid);
})
.push(function (description) {
// Send offer and expect answer in less than XXXms (arbitrary value...)
return RSVP.any([
RSVP.Queue()
.push(function () {
return RSVP.timeout(timeout);
})
.push(undefined, function () {
throw new Error("No remote WebRTC connection available");
}),
RSVP.all([
socket_gadget.send(JSON.stringify({from: context.state_parameter_dict.uuid, action: "offer", data: description})),
context.state_parameter_dict.answer_defer.promise
])
]);
})
.push(function (response_list) {
return rtc_gadget.registerAnswer(response_list[1]);
})
.push(function () {
return socket_gadget.close();
});
});
})
.declareMethod('allDocs', function () {
return wrapJioAccess(this, 'allDocs', arguments);
})
.declareMethod('get', function () {
return wrapJioAccess(this, 'get', arguments);
})
.declareMethod('put', function () {
return wrapJioAccess(this, 'put', arguments);
})
.declareMethod('post', function () {
return wrapJioAccess(this, 'post', arguments);
})
.declareMethod('remove', function () {
return wrapJioAccess(this, 'remove', arguments);
});
}(window, rJS, document, RSVP));
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title>WebRTC Gadget</title>
<script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<script src="gadget_webrtc.js"></script>
</head>
<body>
</body>
</html>
/*jslint indent: 2*/
/*global rJS, RSVP, window*/
(function (rJS, RSVP, window) {
"use strict";
var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection ||
window.webkitRTCPeerConnection || window.msRTCPeerConnection,
RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription ||
window.webkitRTCSessionDescription || window.msRTCSessionDescription;
function enqueueDefer(gadget, callback) {
var deferred = gadget.props.current_deferred;
// Unblock queue
if (deferred !== undefined) {
deferred.resolve("Another event added");
}
// Add next callback
try {
gadget.props.service_queue.push(callback);
} catch (error) {
throw new Error("Connection gadget already crashed... " +
gadget.props.service_queue.rejectedReason.toString());
}
// Block the queue
deferred = RSVP.defer();
gadget.props.current_deferred = deferred;
gadget.props.service_queue.push(function () {
return deferred.promise;
});
}
function deferOnIceCandidate(candidate) {
var gadget = this;
enqueueDefer(gadget, function () {
// Firing this callback with a null candidate indicates that
// trickle ICE gathering has finished, and all the candidates
// are now present in pc.localDescription. Waiting until now
// to create the answer saves us from having to send offer +
// answer + iceCandidates separately.
if (candidate.candidate === null) {
return gadget.notifyDescriptionCalculated(JSON.stringify(gadget.props.connection.localDescription));
}
});
}
function deferDataChannelOnOpen() {
var gadget = this;
enqueueDefer(gadget, function () {
return gadget.notifyDataChannelOpened();
});
}
function deferDataChannelOnClose() {
var gadget = this;
enqueueDefer(gadget, function () {
return gadget.notifyDataChannelClosed();
});
}
function deferDataChannelOnMessage(evt) {
var gadget = this;
enqueueDefer(gadget, function () {
return gadget.notifyDataChannelMessage(evt.data);
// var data = JSON.parse(evt.data);
// console.log(data.message);
});
}
function deferServerDisconnection(gadget) {
enqueueDefer(gadget, function () {
// Try to auto connection
if (gadget.props.connection !== undefined) {
gadget.props.connection.disconnect();
delete gadget.props.connection;
}
});
}
// function deferOfferSuccessCallback(description) {
// var gadget = this;
// enqueueDefer(gadget, function () {
// gadget.props.connection.setLocalDescription(description);
// });
// }
function deferErrorHandler(error) {
enqueueDefer(this, function () {
throw error;
});
}
function deferServerConnection(gadget) {
deferServerDisconnection(gadget);
}
function listenToChannelEvents(gadget) {
gadget.props.channel.onopen = deferDataChannelOnOpen.bind(gadget);
gadget.props.channel.onclose = deferDataChannelOnClose.bind(gadget);
gadget.props.channel.onmessage = deferDataChannelOnMessage.bind(gadget);
gadget.props.channel.onerror = deferErrorHandler.bind(gadget);
}
rJS(window)
.ready(function (g) {
g.props = {};
})
.declareAcquiredMethod('notifyDescriptionCalculated',
'notifyDescriptionCalculated')
.declareAcquiredMethod('notifyDataChannelOpened',
'notifyDataChannelOpened')
.declareAcquiredMethod('notifyDataChannelMessage',
'notifyDataChannelMessage')
.declareAcquiredMethod('notifyDataChannelClosed',
'notifyDataChannelClosed')
.declareService(function () {
/////////////////////////
// Handle WebRTC connection
/////////////////////////
var context = this;
context.props.service_queue = new RSVP.Queue();
deferServerConnection(context);
return new RSVP.Queue()
.push(function () {
return context.props.service_queue;
})
.push(function () {
// XXX Handle cancellation
throw new Error("Service should not have been stopped!");
})
.push(undefined, function (error) {
// Always disconnect in case of error
if (context.props.connection !== undefined) {
context.props.connection.close();
}
throw error;
});
})
.declareMethod('createConnection', function (configuration, constraints) {
this.props.connection = new RTCPeerConnection(configuration, constraints);
this.props.connection.onicecandidate = deferOnIceCandidate.bind(this);
var context = this;
this.props.connection.ondatachannel = function (evt) {
context.props.channel = evt.channel;
listenToChannelEvents(context);
};
})
.declareMethod('createDataChannel', function (title, options) {
// XXX Improve to support multiple data channel
this.props.channel = this.props.connection.createDataChannel(title, options);
listenToChannelEvents(this);
// console.log("Channel type: " + this.props.channel.binarytype);
})
.declareMethod('createOffer', function (constraints) {
var gadget = this;
return new RSVP.Promise(function (resolve, reject) {
gadget.props.connection.createOffer(
resolve,
reject,
constraints
);
});
})
.declareMethod('setRemoteDescription', function (description) {
var gadget = this;
return new RSVP.Promise(function (resolve, reject) {
gadget.props.connection.setRemoteDescription(
new RTCSessionDescription(JSON.parse(description)),
resolve,
reject
);
});
})
.declareMethod('setLocalDescription', function (description) {
var gadget = this;
return new RSVP.Promise(function (resolve, reject) {
gadget.props.connection.setLocalDescription(
new RTCSessionDescription(description),
resolve,
reject
);
});
})
.declareMethod('createAnswer', function (constraints) {
var gadget = this;
return new RSVP.Promise(function (resolve, reject) {
gadget.props.connection.createAnswer(
resolve,
reject,
constraints
);
});
})
.declareMethod('send', function (message) {
this.props.channel.send(message);
})
.declareMethod('close', function () {
// XXX Of course, this will fail if connection is not open yet...
this.props.channel.close();
this.props.connection.close();
delete this.props.channel;
delete this.props.connection;
});
}(rJS, RSVP, window));
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title>WebRTC DataChannel Gadget</title>
<script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<script src="gadget_webrtc_datachannel.js"></script>
</head>
<body>
<div data-gadget-url="gadget_webrtc.html" data-gadget-scope="webrtc"></div>
</body>
</html>
/*jslint indent: 2*/
/*global rJS, RSVP, window*/
(function (rJS, window, RSVP) {
"use strict";
var connection_options = [
{
// No need for stun server on the same lan / ipv6
iceServers: []
},
{
'optional': [{DtlsSrtpKeyAgreement: true}]
}
],
data_channel_options = {reliable: true},
offer_contraints = {
mandatory: {
OfferToReceiveAudio: false,
OfferToReceiveVideo: false
}
};
rJS(window)
.ready(function (g) {
g.props = {};
return g.getDeclaredGadget('webrtc')
.push(function (gadget) {
g.props.webrtc = gadget;
g.props.description_defer = RSVP.defer();
g.props.channel_defer = RSVP.defer();
});
})
.allowPublicAcquisition("notifyDescriptionCalculated", function (args) {
this.props.description_defer.resolve(args[0]);
})
.allowPublicAcquisition("notifyDataChannelOpened", function () {
this.props.channel_defer.resolve();
})
.declareMethod('createOffer', function (title) {
var gadget = this,
webrtc = this.props.webrtc;
return webrtc.createConnection.apply(webrtc, connection_options)
.push(function () {
return webrtc.createDataChannel(title, data_channel_options);
})
.push(function () {
return webrtc.createOffer(offer_contraints);
})
.push(function (local_description) {
return webrtc.setLocalDescription(local_description);
})
.push(function () {
return gadget.props.description_defer.promise;
});
})
.declareMethod('registerAnswer', function (description) {
var gadget = this;
return gadget.props.webrtc.setRemoteDescription(description)
.push(function () {
return gadget.props.channel_defer.promise;
});
})
.declareMethod('createAnswer', function (title, description) {
var gadget = this,
webrtc = this.props.webrtc;
return webrtc.createConnection.apply(webrtc, connection_options)
.push(function () {
return webrtc.setRemoteDescription(description);
})
.push(function () {
return webrtc.createAnswer(offer_contraints);
})
.push(function (local_description) {
return webrtc.setLocalDescription(local_description);
})
.push(function () {
return gadget.props.description_defer.promise;
});
})
.declareMethod('waitForConnection', function () {
return this.props.channel_defer.promise;
})
.declareMethod('send', function () {
var webrtc = this.props.webrtc;
return webrtc.send.apply(
webrtc,
arguments
);
});
}(rJS, window, RSVP));
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title>WebSocket Gadget</title>
<script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<script src="gadget_websocket.js"></script>
</head>
<body>
</body>
</html>
/*jslint indent: 2*/
/*global rJS, window, WebSocket, RSVP*/
(function (rJS, window, WebSocket, RSVP) {
"use strict";
function enqueueDefer(gadget, callback) {
var deferred = gadget.props.current_deferred;
// Unblock queue
if (deferred !== undefined) {
deferred.resolve("Another event added");
}
// Add next callback
try {
gadget.props.service_queue.push(callback);
} catch (error) {
throw new Error("Connection gadget already crashed... " +
gadget.props.service_queue.rejectedReason.toString());
}
// Block the queue
deferred = RSVP.defer();
gadget.props.current_deferred = deferred;
gadget.props.service_queue.push(function () {
return deferred.promise;
});
}
function deferOnClose() {
var gadget = this;
enqueueDefer(gadget, function () {
return gadget.notifyWebSocketClosed();
});
}
function deferOnOpen() {
var gadget = this;
enqueueDefer(gadget, function () {
gadget.props.socket_defer.resolve();
// return gadget.notifyWebSocketOpened();
});
}
function deferOnMessage(evt) {
var gadget = this;
enqueueDefer(gadget, function () {
return gadget.notifyWebSocketMessage(evt.data);
});
}
function deferServerDisconnection(gadget) {
enqueueDefer(gadget, function () {
// Try to auto connection
if (gadget.props.connection !== undefined) {
gadget.props.connection.disconnect();
delete gadget.props.connection;
}
});
}
function deferErrorHandler(error) {
if ((!this.props.socket_defer.isFulfilled) && (!this.props.socket_defer.isRejected)) {
this.props.socket_defer.reject(error);
} else {
enqueueDefer(this, function () {
throw error;
});
}
}
function deferServerConnection(gadget) {
deferServerDisconnection(gadget);
}
rJS(window)
.ready(function (g) {
g.props = {};
})
.declareAcquiredMethod('notifyWebSocketClosed',
'notifyWebSocketClosed')
.declareAcquiredMethod('notifyWebSocketMessage',
'notifyWebSocketMessage')
.declareService(function () {
/////////////////////////
// Handle WebSocket connection
/////////////////////////
var context = this;
context.props.service_queue = new RSVP.Queue();
deferServerConnection(context);
return new RSVP.Queue()
.push(function () {
return context.props.service_queue;
})
.push(function () {
// XXX Handle cancellation
throw new Error("Service should not have been stopped!");
})
.push(undefined, function (error) {
// Always disconnect in case of error
if (context.props.connection !== undefined) {
context.props.connection.close();
}
throw error;
});
})
.declareMethod('createSocket', function (address) {
// Improve to support multiple sockets?
this.props.socket = new WebSocket(address);
this.props.socket_defer = RSVP.defer();
this.props.socket.addEventListener('open', deferOnOpen.bind(this));
this.props.socket.addEventListener('close', deferOnClose.bind(this));
this.props.socket.addEventListener('message', deferOnMessage.bind(this));
this.props.socket.addEventListener('error', deferErrorHandler.bind(this));
return this.props.socket_defer.promise;
})
.declareMethod('send', function (message) {
this.props.socket.send(message);
})
.declareMethod('close', function () {
if (this.props.socket === undefined) {
return;
}
this.props.socket.close();
delete this.props.socket;
});
}(rJS, window, WebSocket, RSVP));
This diff is collapsed.
CACHE MANIFEST
# generated on Tue, 08 Sep 2015 15:31:33 +0000
CACHE:
master.html
master.js
renderjs.js
rsvp.js
jiodev.js
gadget_officejs_page_share_webrtc_jio.html
gadget_officejs_page_share_webrtc_jio.js
gadget_jio.html
gadget_jio.js
gadget_websocket.html
gadget_websocket.js
gadget_webrtc_datachannel.html
gadget_webrtc_datachannel.js
gadget_webrtc.html
gadget_webrtc.js
NETWORK:
*
#https://nexedi.erp5.net/rsvp.js
#https://nexedi.erp5.net/renderjs.js
<!DOCTYPE html>
<html manifest="master.appcache">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Serverless WebRTC - Master</title>
<style type="text/css">
body {
background-color: #f9f9f9;
color: #2C3E50;
font-family: Arial;
}
.ui-content, .ui-responsive {
background-color: white;
border: 1px solid #2C3E50;
border-radius: .375em;
padding: 1em;
width: 50%;
margin: 0 auto 1em;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.ui-responsive div p:first-child {
display: none;
}
@media (max-width: 48em) {
.ui-content, .ui-responsive {
width: 100%;
}
}
h2, h3 {
padding: 0;
margin: 0;
font-size: 1.17em;
text-align: center;
}
</style>
<script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<!-- custom -->
<script src="master.js" type="text/javascript"></script>
</head>
<body>
<!-- MASTER -->
<!-- NOTE: must have BroadCaster running on same device to work -->
<div class="ui-content">
<h3>Master</h3>
<ul>
<li>The master gadget contains a jIO storage indexedDB.</li>
<li>Slaves connect to master over WebSockets URL set by the Broadcaster</li>
<li>Once webRTC is active the slave can access the storage over webRTC</li>
</div>
<!-- socket broadcaster -->
<div data-gadget-url="gadget_officejs_page_share_webrtc_jio.html"
data-gadget-scope="share_storage_via_webrtc"
data-gadget-sandbox="public"></div>
<!-- storage -->
<div data-gadget-url="gadget_jio.html"
data-gadget-scope="jio_gadget"
data-gadget-sandbox="public"></div>
</body>
</html>
/*global window, rJS, document, RSVP, console, DOMException */
/*jslint indent: 2, maxerr: 3 */
(function (window, rJS, document, RSVP, console, DOMException) {
"use strict";
/////////////////////////////////////////////////////////////////
// Some functions
/////////////////////////////////////////////////////////////////
function createJio(gadget) {
return gadget.getDeclaredGadget("jio_gadget")
.push(function (jio_gadget) {
return jio_gadget.createJio({
type: "query",
sub_storage: {
type: "uuid",
sub_storage: {
"type": "indexeddb",
"database": "serverless"
}
}
});
});
}
/////////////////////////////////////////////////////////////////
// Gadget behaviour
/////////////////////////////////////////////////////////////////
rJS(window)
/////////////////////////////////////////////////////////////////
// ready
/////////////////////////////////////////////////////////////////
.ready(function (gadget) {
gadget.state_parameter_dict = {};
return gadget.getElement()
.push(function (element) {
gadget.state_parameter_dict.element = element;
});
})
// Configure jIO to indexeddb
.ready(function (gadget) {
return createJio(gadget);
})
/////////////////////////////////////////////////////////////////
// acquired methods
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
// published methods
/////////////////////////////////////////////////////////////////
.allowPublicAcquisition("jio_post", function (my_param_list) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("jio_gadget");
})
.push(function (my_gadget) {
return my_gadget.post.apply(my_gadget, my_param_list);
});
})
.allowPublicAcquisition("jio_get", function (my_param_list) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("jio_gadget");
})
.push(function (my_gadget) {
return my_gadget.get.apply(my_gadget, my_param_list);
});
})
.allowPublicAcquisition("jio_allDocs", function (my_param_list) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("jio_gadget");
})
.push(function (my_gadget) {
return my_gadget.allDocs.apply(my_gadget, my_param_list);
});
});
/////////////////////////////////////////////////////////////////
// declared methods
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
// declared services
/////////////////////////////////////////////////////////////////
}(window, rJS, document, RSVP, console, DOMException));
This diff is collapsed.
This diff is collapsed.
CACHE MANIFEST
# generated on Tue, 08 Sep 2015 15:31:33 +0000
CACHE:
slave.html
slave.js
gadget_officejs_webrtc_jio.html
gadget_officejs_webrtc_jio.js
gadget_jio.html
gadget_jio.js
jiodev.js
gadget_websocket.html
gadget_websocket.js
gadget_webrtc_datachannel.html
gadget_webrtc_datachannel.js
gadget_webrtc.html
gadget_webrtc.js
NETWORK:
*
#https://nexedi.erp5.net/rsvp.js
#https://nexedi.erp5.net/renderjs.js
<!DOCTYPE html>
<html manifest="slave.appcache">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Serverless WebRTC - Slave</title>
<style type="text/css">
body {
background-color: #f9f9f9;
color: #2C3E50;
font-family: Arial;
}
.ui-content, .ui-responsive {
background-color: white;
border: 1px solid #2C3E50;
border-radius: .375em;
padding: 1em;
width: 50%;
margin: 0 auto 1em;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
@media (max-width: 48em) {
.ui-content, .ui-responsive {
width: 100%;
}
}
h2, h3 {
padding: 0;
margin: 0;
font-size: 1.17em;
text-align: center;
}
label {
width: 60%;
display: inline-block;
padding: .5em 0;
}
label span {
display: inline-block;
width: 40%;
padding-right: .5em;
}
</style>
<script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<!-- custom -->
<script src="slave.js" type="text/javascript"></script>
</head>
<body>
<!-- SLAVE -->
<!-- connect to master via WebSockets, then access master storage via WebRTC -->
<!-- shared storage access -->
<div data-gadget-url="gadget_officejs_webrtc_jio.html"
data-gadget-scope="access_storage_via_webrtc"
data-gadget-sandbox="public"></div>
<div class="ui-content">
<h3 style="display:inline-block;">Status:</h3>
<span class="ops-status"></span>
</div>
<div class="ui-content">
<form class="ops-form-initializer">
<h3>Request WebRTC Connection over WebSocket</h3>
<label><span>WebSocket Address</span><input type="text" class="ops-socket-connector" /></label>
<input type="submit" value="Request WebRTC Connection" />
</form>
</div>
<div class="ui-content">
<form class="ops-form-create">
<h3>Create Records</h3>
<label><span>First Name</span><input type="text" name="field_first_name" /></label>
<label><span>Last Name</span><input type="text" name="field_last_name" /></label>
<input type="submit" value="Create Record" />
</form>
</div>
<div class="ui-content">
<form class="ops-form-query">
<h3>Query Records</h3>
<label><span>Search</span><input type="search" name="field_search_string" /></label>
<input type="submit" value="Search For Names" />
</form>
<ul class="ops-result-list">
</ul>
</div>
</body>
</html>
\ No newline at end of file
/*global window, rJS, document, RSVP */
/*jslint indent: 2, maxerr: 3 */
(function (window, rJS, document, RSVP) {
"use strict";
/////////////////////////////////////////////////////////////////
// Some functions
/////////////////////////////////////////////////////////////////
function displayError(gadget, error) {
// Do not break the application in case of errors.
// Display it to the user for now, and allow user to go back to the frontpage
var error_text = "",
status = gadget.state_parameter_dict.status_message;
if (error instanceof RSVP.CancellationError) {
return;
}
if (error instanceof XMLHttpRequest) {
error_text = error.toString() + " " +
error.status + " " +
error.statusText;
} else if (error instanceof Error) {
error_text = error.toString();
} else {
error_text = JSON.stringify(error);
}
console.error(error);
console.error(error.stack);
status.textContent = "Error: " + error_text;
}
function escapeString(my_string) {
//return my_string.replace(/\"/g, "\\\"");
return my_string;
}
function findList(my_target) {
var list = my_target.nextSibling;
while(list && list.nodeType != 1) {
list = list.nextSibling;
}
return list;
}
function clearList(my_list) {
while (my_list.firstChild) {
my_list.removeChild(my_list.firstChild);
}
}
function queryRecordOverWebrtc(my_gadget, my_event) {
var target = my_event.target,
search_string = target.elements.field_search_string.value,
status = my_gadget.state_parameter_dict.status_message,
list = findList(target),
lookup;
if (search_string) {
lookup = escapeString(search_string);
clearList(list);
return new RSVP.Queue()
.push(function () {
status.textContent = "Querying storage...";
return my_gadget.shared_jio_allDocs({
"query": 'first:"%' + lookup + '%" OR last:"%' + lookup + '%"',
//"include_docs": true NOT SUPPORTED ON INDEXEDDB
});
})
.push(function (my_result) {
var promise_list = [],
i,
i_len;
// fetch records one by one
for (i = 0, i_len = my_result.data.total_rows; i < i_len; i += 1) {
promise_list.push(
my_gadget.shared_jio_get(my_result.data.rows[i].id)
);
}
return RSVP.all(promise_list);
})
.push(function (my_result_list) {
var fragment = document.createDocumentFragment(),
result,
doc,
i,
i_len;
status.textContent = "";
for (i = 0, i_len = my_result_list.length; i < i_len; i += 1) {
doc = my_result_list[i];
result = document.createElement("li");
result.textContent = doc.first + " " + doc.last;
fragment.appendChild(result);
}
list.appendChild(fragment);
});
}
status.textContent = "Please enter search string.";
}
function createRecordOverWebrtc(my_gadget, my_event) {
var target = my_event.target,
first = target.elements.field_first_name.value,
last = target.elements.field_last_name.value,
status = my_gadget.state_parameter_dict.status_message;
if (first && last) {
return new RSVP.Queue()
.push(function () {
status.textContent = "Posting to storage...";
return my_gadget.shared_jio_post({"first": first, "last": last});
})
.push(function (my_storage_reply) {
status.textContent = "ok, record created: " + my_storage_reply;
});
}
status.textContent = "Please enter first and last name.";
return;
}
function initializeWebrtcOverWebsocket(my_gadget, my_event) {
var target = my_event.target,
value = target.querySelector(".ops-socket-connector").value,
status = my_gadget.state_parameter_dict.status_message;
if (value) {
status.textContent = "Try to connect over " + value + " ...";
return new RSVP.Queue()
.push(function () {
return my_gadget.shared_jio_create({"socket_url": value});
})
.push(function () {
status.textContent = "WebRTC active.";
});
}
status.textContent = "Please enter WebSocket Url.";
return false;
}
/////////////////////////////////////////////////////////////////
// Gadget behaviour
/////////////////////////////////////////////////////////////////
rJS(window)
/////////////////////////////////////////////////////////////////
// ready
/////////////////////////////////////////////////////////////////
.ready(function (gadget) {
gadget.state_parameter_dict = {};
return gadget.getElement()
.push(function (element) {
gadget.state_parameter_dict.element = element;
gadget.state_parameter_dict.status_message =
element.querySelector(".ops-status");
});
})
/////////////////////////////////////////////////////////////////
// acquired methods
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
// published methods
/////////////////////////////////////////////////////////////////
.allowPublicAcquisition('reportServiceError', function (param_list, gadget_scope) {
if (gadget_scope === undefined) {
// don't fail in case of dropped subgadget (like previous page)
// only accept errors from header, panel and displayed page
return;
}
return displayError(this, param_list[0]);
})
/////////////////////////////////////////////////////////////////
// declared methods
/////////////////////////////////////////////////////////////////
.declareMethod('shared_jio_create', function (my_param_list) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("access_storage_via_webrtc");
})
.push(function (my_gadget) {
return my_gadget.createJio(my_param_list);
});
})
.declareMethod("shared_jio_post", function (my_param_dict) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("access_storage_via_webrtc");
})
.push(function (my_gadget) {
return my_gadget.post(my_param_dict);
});
})
.declareMethod("shared_jio_get", function (my_param_dict) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("access_storage_via_webrtc");
})
.push(function (my_gadget) {
return my_gadget.get(my_param_dict);
});
})
.declareMethod("shared_jio_allDocs", function (my_param_dict) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("access_storage_via_webrtc");
})
.push(function (my_gadget) {
return my_gadget.allDocs(my_param_dict);
});
})
/////////////////////////////////////////////////////////////////
// declared services
/////////////////////////////////////////////////////////////////
.declareService(function () {
var gadget = this;
function handleFormSubmit(my_event) {
switch (my_event.target.className) {
case "ops-form-initializer":
return initializeWebrtcOverWebsocket(gadget, my_event);
case "ops-form-create":
return createRecordOverWebrtc(gadget, my_event);
case "ops-form-query":
return queryRecordOverWebrtc(gadget, my_event);
}
}
// Listen to form submit
return loopEventListener(
gadget.state_parameter_dict.element,
'submit',
false,
handleFormSubmit
);
});
}(window, rJS, document, RSVP));
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment