Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging
This commit is contained in:
commit
008b2cc67e
7
www/assert/frame/frame.html
Normal file
7
www/assert/frame/frame.html
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="respond.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
147
www/assert/frame/frame.js
Normal file
147
www/assert/frame/frame.js
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
(function () {
|
||||||
|
|
||||||
|
var Frame = {};
|
||||||
|
|
||||||
|
var uid = function () {
|
||||||
|
return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
|
||||||
|
.toString(32).replace(/\./g, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
// create an invisible iframe with a given source
|
||||||
|
// append it to a parent element
|
||||||
|
// execute a callback when it has loaded
|
||||||
|
Frame.create = function (parent, src, onload, timeout) {
|
||||||
|
var iframe = document.createElement('iframe');
|
||||||
|
|
||||||
|
timeout = timeout || 10000;
|
||||||
|
var to = window.setTimeout(function () {
|
||||||
|
onload('[timeoutError] could not load iframe at ' + src);
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
iframe.setAttribute('id', 'cors-store');
|
||||||
|
|
||||||
|
iframe.onload = function (e) {
|
||||||
|
onload(void 0, iframe, e);
|
||||||
|
window.clearTimeout(to);
|
||||||
|
};
|
||||||
|
// We must pass a unique parameter here to avoid cache problems in Firefox with
|
||||||
|
// the NoScript plugin: if the iframe's content is taken from the cache, the JS
|
||||||
|
// is not executed with NoScript....
|
||||||
|
iframe.setAttribute('src', src + '?t=' + new Date().getTime());
|
||||||
|
|
||||||
|
iframe.style.display = 'none';
|
||||||
|
parent.appendChild(iframe);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* given an iframe with an rpc script loaded, create a frame object
|
||||||
|
with an asynchronous 'send' method */
|
||||||
|
Frame.open = function (e, A, timeout) {
|
||||||
|
var win = e.contentWindow;
|
||||||
|
|
||||||
|
var frame = {};
|
||||||
|
frame.id = uid();
|
||||||
|
|
||||||
|
var listeners = {};
|
||||||
|
var timeouts = {};
|
||||||
|
|
||||||
|
timeout = timeout || 5000;
|
||||||
|
|
||||||
|
frame.accepts = function (o) {
|
||||||
|
return A.some(function (e) {
|
||||||
|
switch (typeof(e)) {
|
||||||
|
case 'string': return e === o;
|
||||||
|
case 'object': return e.test(o);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var changeHandlers = frame.changeHandlers = [];
|
||||||
|
|
||||||
|
frame.change = function (f) {
|
||||||
|
if (typeof(f) !== 'function') {
|
||||||
|
throw new Error('[Frame.change] expected callback');
|
||||||
|
}
|
||||||
|
changeHandlers.push(f);
|
||||||
|
};
|
||||||
|
|
||||||
|
var _listener = function (e) {
|
||||||
|
if (!frame.accepts(e.origin)) {
|
||||||
|
console.log("message from %s rejected!", e.origin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var message = JSON.parse(e.data);
|
||||||
|
var uid = message._uid;
|
||||||
|
var error = message.error;
|
||||||
|
var data = message.data;
|
||||||
|
|
||||||
|
if (!uid) {
|
||||||
|
console.log("No uid!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uid === 'change' && changeHandlers.length) {
|
||||||
|
changeHandlers.forEach(function (f) {
|
||||||
|
f(data);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeouts[uid]) {
|
||||||
|
window.clearTimeout(timeouts[uid]);
|
||||||
|
}
|
||||||
|
if (listeners[uid]) {
|
||||||
|
listeners[uid](error, data, e);
|
||||||
|
delete listeners[uid];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('message', _listener);
|
||||||
|
|
||||||
|
frame.close = function () {
|
||||||
|
window.removeEventListener('message', _listener);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* method (string): (set|get|remove)
|
||||||
|
key (string)
|
||||||
|
data (string)
|
||||||
|
cb (function) */
|
||||||
|
frame.send = function (method, content, cb) {
|
||||||
|
var req = {
|
||||||
|
method: method,
|
||||||
|
//key: key,
|
||||||
|
data: content, //data,
|
||||||
|
};
|
||||||
|
|
||||||
|
var id = req._uid = uid();
|
||||||
|
// uid must not equal 'change'
|
||||||
|
while(id === 'change') {
|
||||||
|
id = req._uid = uid();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof(cb) === 'function') {
|
||||||
|
//console.log("setting callback!");
|
||||||
|
listeners[id] = cb;
|
||||||
|
//console.log("setting timeout of %sms", timeout);
|
||||||
|
timeouts[id] = window.setTimeout(function () {
|
||||||
|
// when the callback is executed it will clear this timeout
|
||||||
|
cb('[TimeoutError] request timed out after ' + timeout + 'ms');
|
||||||
|
}, timeout);
|
||||||
|
} else {
|
||||||
|
console.log(typeof(cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
win.postMessage(JSON.stringify(req), '*');
|
||||||
|
};
|
||||||
|
|
||||||
|
return frame;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof(module) !== 'undefined' && module.exports) {
|
||||||
|
module.exports = Frame;
|
||||||
|
} else if (typeof(define) === 'function' && define.amd) {
|
||||||
|
define(['jquery'], function () {
|
||||||
|
return Frame;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.Frame = Frame;
|
||||||
|
}
|
||||||
|
}());
|
||||||
32
www/assert/frame/respond.js
Normal file
32
www/assert/frame/respond.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
var validDomains = [ /.*/i, ];
|
||||||
|
var isValidDomain = function (o) {
|
||||||
|
return validDomains.some(function (e) {
|
||||||
|
switch (typeof(e)) {
|
||||||
|
case 'string': return e === o;
|
||||||
|
case 'object': return e.test(o);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('message', function(e) {
|
||||||
|
if (!isValidDomain(e.origin)) { return; }
|
||||||
|
var payload = JSON.parse(e.data);
|
||||||
|
var parent = window.parent;
|
||||||
|
var respond = function (error, data) {
|
||||||
|
var res = {
|
||||||
|
_uid: payload._uid,
|
||||||
|
error: error,
|
||||||
|
data: data,
|
||||||
|
};
|
||||||
|
parent.postMessage(JSON.stringify(res), '*');
|
||||||
|
};
|
||||||
|
|
||||||
|
//console.error(payload);
|
||||||
|
switch(payload.method) {
|
||||||
|
case undefined:
|
||||||
|
return respond('No method supplied');
|
||||||
|
default:
|
||||||
|
return respond(void 0, "EHLO");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
@ -249,7 +249,6 @@ define([
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var evt = Util.mkEvent();
|
var evt = Util.mkEvent();
|
||||||
var respond = function (e, out) {
|
var respond = function (e, out) {
|
||||||
evt.fire(e, out);
|
evt.fire(e, out);
|
||||||
@ -259,9 +258,8 @@ define([
|
|||||||
try {
|
try {
|
||||||
var parsed = JSON.parse(raw);
|
var parsed = JSON.parse(raw);
|
||||||
var txid = parsed.txid;
|
var txid = parsed.txid;
|
||||||
var message = parsed.message;
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
service(message.command, message.content, function (e, result) {
|
service(parsed.q, parsed.content, function (e, result) {
|
||||||
respond(JSON.stringify({
|
respond(JSON.stringify({
|
||||||
txid: txid,
|
txid: txid,
|
||||||
error: e,
|
error: e,
|
||||||
@ -285,33 +283,56 @@ define([
|
|||||||
});
|
});
|
||||||
}, "Test rpc factory");
|
}, "Test rpc factory");
|
||||||
|
|
||||||
/*
|
|
||||||
assert(function (cb) {
|
assert(function (cb) {
|
||||||
var getBlob = function (url, cb) {
|
require([
|
||||||
var xhr = new XMLHttpRequest();
|
'/assert/frame/frame.js',
|
||||||
xhr.open("GET", url, true);
|
], function (Frame) {
|
||||||
xhr.responseType = "blob";
|
Frame.create(document.body, '/assert/frame/frame.html', function (e, frame) {
|
||||||
xhr.onload = function () {
|
if (e) { return cb(false); }
|
||||||
cb(void 0, this.response);
|
|
||||||
};
|
|
||||||
xhr.send();
|
|
||||||
};
|
|
||||||
|
|
||||||
var $img = $('img#thumb-orig');
|
var channel = Frame.open(frame, [
|
||||||
getBlob($img.attr('src'), function (e, blob) {
|
/.*/i,
|
||||||
console.log(e, blob);
|
], 5000);
|
||||||
Thumb.fromImageBlob(blob, function (e, thumb) {
|
|
||||||
console.log(thumb);
|
channel.send('HELO', null, function (e, res) {
|
||||||
var th = new Image();
|
if (res === 'EHLO') { return cb(true); }
|
||||||
th.src = URL.createObjectURL(thumb);
|
cb(false);
|
||||||
th.onload = function () {
|
|
||||||
$(document.body).append($(th).addClass('thumb'));
|
|
||||||
cb(th.width === Thumb.dimension && th.height === Thumb.dimension);
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
*/
|
}, "PEWPEW");
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
var guid = Wire.uid();
|
||||||
|
|
||||||
|
var t = Wire.tracker({
|
||||||
|
timeout: 1000,
|
||||||
|
hook: function (txid, q, content) {
|
||||||
|
console.info(JSON.stringify({
|
||||||
|
guid: guid,
|
||||||
|
txid: txid,
|
||||||
|
q: q,
|
||||||
|
content: content,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(function (cb) {
|
||||||
|
t.call('SHOULD_TIMEOUT', null, function (e) {
|
||||||
|
if (e === 'TIMEOUT') { return cb(true); }
|
||||||
|
cb(false);
|
||||||
|
});
|
||||||
|
}, 'tracker should timeout');
|
||||||
|
|
||||||
|
assert(function (cb) {
|
||||||
|
var id = t.call('SHOULD_NOT_TIMEOUT', null, function (e, out) {
|
||||||
|
if (e) { return cb(false); }
|
||||||
|
if (out === 'YES') { return cb(true); }
|
||||||
|
cb(false);
|
||||||
|
});
|
||||||
|
t.respond(id, void 0, 'YES');
|
||||||
|
}, "tracker should not timeout");
|
||||||
|
}());
|
||||||
|
|
||||||
Drive.test(assert);
|
Drive.test(assert);
|
||||||
|
|
||||||
|
|||||||
@ -7,25 +7,118 @@ define([
|
|||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
|
|
||||||
* some transmission methods can be interrupted
|
* [x] some transmission methods can be interrupted
|
||||||
* handle disconnects and reconnects
|
* [x] handle disconnects and reconnects
|
||||||
* handle callbacks
|
* [x] handle callbacks
|
||||||
* configurable timeout
|
* [x] configurable timeout
|
||||||
* Service should expose 'addClient' method
|
* [x] be able to answer only queries with a particular id
|
||||||
* and handle broadcast
|
* be able to implement arbitrary listeners on the service-side
|
||||||
|
* and not call 'ready' until those listeners are ready
|
||||||
|
* identical API for:
|
||||||
|
* iframe postMessage
|
||||||
|
* server calls over netflux
|
||||||
|
* postMessage to webworker
|
||||||
|
* postMessage to sharedWorker
|
||||||
|
* on-wire protocol should actually be the same for rewriting purposes
|
||||||
|
* q
|
||||||
|
* guid (globally unique id)
|
||||||
|
* txid (message id)
|
||||||
|
* content
|
||||||
|
* be able to compose different RPCs as streams
|
||||||
|
* intercept and rewrite capacity
|
||||||
|
* multiplex multiple streams over one stream
|
||||||
|
* blind redirect
|
||||||
|
* intelligent router
|
||||||
|
* broadcast (with ACK?)
|
||||||
|
* message
|
||||||
|
|
||||||
|
|
||||||
*
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var uid = function () {
|
var uid = Wire.uid = function () {
|
||||||
return Number(Math.floor(Math.random () *
|
return Number(Math.floor(Math.random () *
|
||||||
Number.MAX_SAFE_INTEGER)).toString(32);
|
Number.MAX_SAFE_INTEGER)).toString(32);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* tracker(options)
|
||||||
|
maintains a registry of asynchronous function calls
|
||||||
|
|
||||||
|
allows you to:
|
||||||
|
hook each call to actually send to a remote service...
|
||||||
|
abort any call
|
||||||
|
trigger the pending callback with arguments
|
||||||
|
set the state of the tracker (active/inactive)
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
Wire.tracker = function (opt) {
|
||||||
|
opt = opt || {};
|
||||||
|
var hook = opt.hook || function () {};
|
||||||
|
var timeout = opt.timeout || 5000;
|
||||||
|
var pending = {};
|
||||||
|
var timeouts = {};
|
||||||
|
|
||||||
|
var call = function (method, data, cb) {
|
||||||
|
var id = uid();
|
||||||
|
|
||||||
|
// if the callback is not invoked in time, time out
|
||||||
|
timeouts[id] = setTimeout(function () {
|
||||||
|
if (typeof(pending[id]) === 'function') {
|
||||||
|
cb("TIMEOUT");
|
||||||
|
delete pending[id];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error('timed out without function to call');
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
pending[id] = function () {
|
||||||
|
// invoke the function with arguments...
|
||||||
|
cb.apply(null, Array.prototype.slice.call(arguments));
|
||||||
|
// clear its timeout
|
||||||
|
clearTimeout(timeouts[id]);
|
||||||
|
// remove the function from pending
|
||||||
|
delete pending[id];
|
||||||
|
};
|
||||||
|
|
||||||
|
hook(id, method, data);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
|
||||||
|
var respond = function (id, err, response) {
|
||||||
|
if (typeof(pending[id]) !== 'function') {
|
||||||
|
throw new Error('invoked non-existent callback');
|
||||||
|
}
|
||||||
|
pending[id](err, response);
|
||||||
|
};
|
||||||
|
|
||||||
|
var abort = function (id) {
|
||||||
|
if (pending[id]) {
|
||||||
|
clearTimeout(timeouts[id]);
|
||||||
|
delete pending[id];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
var t = {
|
||||||
|
call: call,
|
||||||
|
respond: respond,
|
||||||
|
abort: abort,
|
||||||
|
state: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
t.setState = function (active) {
|
||||||
|
t.state = Boolean(active);
|
||||||
|
};
|
||||||
|
|
||||||
|
return t;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
opt = {
|
opt = {
|
||||||
|
timeout: 30000,
|
||||||
send: function () {
|
send: function () {
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -45,50 +138,44 @@ opt = {
|
|||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Wire.create = function (opt, cb) {
|
var parseMessage = function (raw) {
|
||||||
var ctx = {};
|
try { return JSON.parse(raw); } catch (e) { return; }
|
||||||
var pending = ctx.pending = {};
|
|
||||||
ctx.connected = false;
|
|
||||||
|
|
||||||
var rpc = {};
|
|
||||||
|
|
||||||
opt.constructor(function (e, service) {
|
|
||||||
if (e) { return setTimeout(function () { cb(e); }); }
|
|
||||||
|
|
||||||
rpc.send = function (type, data, cb) {
|
|
||||||
var txid = uid();
|
|
||||||
if (typeof(cb) !== 'function') {
|
|
||||||
throw new Error('expected callback');
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.pending[txid] = function (err, response) {
|
|
||||||
cb(err, response);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Wire.create = function (opt, cb) {
|
||||||
|
opt.constructor(function (e, service) {
|
||||||
|
if (e) { return setTimeout(function () { cb(e); }); }
|
||||||
|
var rpc = {};
|
||||||
|
|
||||||
|
var guid = Wire.uid();
|
||||||
|
var t = Wire.tracker({
|
||||||
|
timeout: opt.timeout,
|
||||||
|
hook: function (txid, q, content) {
|
||||||
service.send(JSON.stringify({
|
service.send(JSON.stringify({
|
||||||
|
guid: guid,
|
||||||
|
q: q,
|
||||||
txid: txid,
|
txid: txid,
|
||||||
message: {
|
content: content,
|
||||||
command: type,
|
|
||||||
content: data,
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
rpc.send = function (type, data, cb) {
|
||||||
|
t.call(type, data, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
service.receive(function (raw) {
|
service.receive(function (raw) {
|
||||||
try {
|
var data = parseMessage(raw);
|
||||||
var data = JSON.parse(raw);
|
if (typeof(data) === 'undefined') {
|
||||||
var txid = data.txid;
|
return console.error("UNHANDLED_MESSAGE", raw);
|
||||||
if (!txid) { throw new Error('NO_TXID'); }
|
}
|
||||||
var cb = pending[txid];
|
if (!data.txid) { throw new Error('NO_TXID'); }
|
||||||
if (data.error) { return void cb(data.error); }
|
t.respond(data.txid, data.error, data.content);
|
||||||
cb(void 0, data.content);
|
|
||||||
} catch (e) { console.error("UNHANDLED_MESSAGE", raw); }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cb(void 0, rpc);
|
cb(void 0, rpc);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return Wire;
|
return Wire;
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user