Commit 659eeb0d authored by Cédric Le Ninivin's avatar Cédric Le Ninivin

Move to jIO Storage for Service Worker

parent 191a947d
......@@ -2,20 +2,19 @@
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Jio Gadget</title>
<link rel="http://www.renderjs.org/rel/interface" href="interface_jio.html">
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title>Crib SW interface Gadget</title>
<!-- renderjs -->
<script src="./lib/rsvp.js" type="text/javascript"></script>
<script src="./lib/renderjs.js" type="text/javascript"></script>
<script src="./lib/jio-latest.js" type="text/javascript"></script>
<!-- custom script -->
<script src="gadget_jio_crib.js" type="text/javascript"></script>
<script src="./crib-enable.js" type="text/javascript"></script>
</head>
<body>
Couscosu
</body>
</html>
\ No newline at end of file
/*global window, rJS, RSVP, XMLHttpRequest, MessageChannel,
console, navigator, Blob
*/
/*jslint indent: 2, maxerr: 3 */
(function (rJS, RSVP, jIO) {
"use strict";
/**
* Send request with XHR and return a promise. xhr.onload: The promise is
* resolved when the status code is lower than 400 with the xhr object as
* first parameter. xhr.onerror: reject with xhr object as first
* parameter. xhr.onprogress: notifies the xhr object.
*
* @param {Object} param The parameters
* @param {String} [param.type="GET"] The request method
* @param {String} [param.dataType=""] The data type to retrieve
* @param {String} param.url The url
* @param {Any} [param.data] The data to send
* @param {Function} [param.beforeSend] A function called just before the
* send request. The first parameter of this function is the XHR object.
* @return {Promise} The promise
*/
function ajax(param) {
var xhr = new XMLHttpRequest();
return new RSVP.Promise(function (resolve, reject, notify) {
var k;
xhr.open(param.type || "GET", param.url, true);
xhr.addEventListener("load", function (e) {
var answer = {};
if (e.target.status >= 400) {
return reject(e);
}
answer.responseText = this.responseText;
answer.responseType = this.getResponseHeader("content-type");
answer.responseURL = param.url;
resolve(answer);
});
xhr.addEventListener("error", reject);
xhr.addEventListener("progress", notify);
if (typeof param.xhrFields === 'object' && param.xhrFields !== null) {
for (k in param.xhrFields) {
if (param.xhrFields.hasOwnProperty(k)) {
xhr[k] = param.xhrFields[k];
}
}
}
xhr.send();
}, function () {
xhr.abort();
});
}
function sendMessage(message) {
// This wraps the message posting/response in a promise, which will resolve if the response doesn't
// contain an error, and reject with the error if it does. If you'd prefer, it's possible to call
// controller.postMessage() and set up the onmessage handler independently of a promise, but this is
// a convenient wrapper.
return new RSVP.Promise(function (resolve, reject, notify) {
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function (event) {
console.log(event);
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
};
// This sends the message data as well as transferring messageChannel.port2 to the service worker.
// The service worker can then use the transferred port to reply via postMessage(), which
// will in turn trigger the onmessage handler on messageChannel.port1.
// See https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage
return navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
});
}
function setStatus(statusMessage) {
console.log(statusMessage);
}
rJS(window)
.ready(function (g) {
g.props = {};
return g.getElement()
.push(function (element) {
g.props.element = element;
});
})
.ready(function (gadget) {
// Initialize the gadget local parameters
gadget.state_parameter_dict = {};
if ('serviceWorker' in navigator) {
// XXX Hack to not add a new service worker when one is already declared
if (!navigator.serviceWorker.controller) {
return new RSVP.Promise(function (resolve, reject, notify) {
navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(
function () {
if (navigator.serviceWorker.controller) {
resolve();
} else {
reject("Please reload this page to allow Service Worker to control this page");
}
})
.then(undefined, function (error) {
reject(error);
});
});
}
} else {
throw "Service Worker are not available in your browser";
}
})
/**
* allDocs return the list of document in the cache
*
* @params {Object} Not taken into account
* @return {} Return the data url of the document
*/
.declareMethod('allDocs', function (params) {
if (params && params.cached_only) {
return new RSVP.Queue()
.push(function () {
return sendMessage({
command: 'keys'
});
});
}
return new RSVP.Queue()
.push(function () {
return sendMessage({
command: 'allDocs'
});
});
})
/**
* get Return a data url. Cannot return a blob as the data
* is transmitted through an iFrame
*
* @url {string} url of the document to retrieve
* @return {data_url} Return the data url of the document
*/
.declareMethod('get', function (url) {
return new RSVP.Queue()
.push(function () {
return ajax({
url: url
});
})
.push(function (result) {
return new Blob([result.responseText], {type: result.responseType});
})
.push(function (result) {
return jIO.util.readBlobAsDataURL(result);
})
.push(function (e) {
return e.target.result;
});
})
/**
* put Return a data url. Cannot provide a blob as the data
* is transmitted through an iFrame
*
* @url {string} url of the document to update
* @parameter {data_url} data url of the document to put, it will be transformed in a blob
* @return {data_url} Return the data url of the document
*/
.declareMethod('put', function (url, parameter) {
return new RSVP.Queue()
.push(function () {
return sendMessage({
command: 'add',
url: url,
information: jIO.util.dataURItoBlob(parameter)
});
}).push(function () {
// If the promise resolves, just display a success message.
console.log("Done adding " + url);
return 'Added to cache: ' + url + ' at ' + Date();
}).fail(setStatus);
})
/**
* Remove an url from the cache
*
* @url {string} url of the document to remove
* @return {}
*/
.declareMethod('remove', function (url) {
return new RSVP.Queue()
.push(function () {
return sendMessage({
command: 'delete',
url: url
});
});
});
}(rJS, RSVP, jIO));
\ No newline at end of file
......@@ -2,19 +2,20 @@
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title>Crib SW interface Gadget</title>
<meta name="viewport" content="width=device-width" />
<title>Jio Gadget</title>
<link rel="http://www.renderjs.org/rel/interface" href="interface_jio.html">
<!-- renderjs -->
<script src="./lib/rsvp.js" type="text/javascript"></script>
<script src="./lib/renderjs.js" type="text/javascript"></script>
<script src="./lib/jio-latest.js" type="text/javascript"></script>
<!-- custom script -->
<script src="./crib-enable.js" type="text/javascript"></script>
<script src="crib-enable.js" type="text/javascript"></script>
</head>
<body>
Couscosu
</body>
</html>
\ No newline at end of file
/*global window, rJS, RSVP, XMLHttpRequest, MessageChannel,
console, navigator, Blob
*/
/*global window, rJS, jIO, FormData */
/*jslint indent: 2, maxerr: 3 */
(function (rJS, RSVP, jIO) {
(function (window, rJS, jIO) {
"use strict";
/**
* Send request with XHR and return a promise. xhr.onload: The promise is
* resolved when the status code is lower than 400 with the xhr object as
* first parameter. xhr.onerror: reject with xhr object as first
* parameter. xhr.onprogress: notifies the xhr object.
*
* @param {Object} param The parameters
* @param {String} [param.type="GET"] The request method
* @param {String} [param.dataType=""] The data type to retrieve
* @param {String} param.url The url
* @param {Any} [param.data] The data to send
* @param {Function} [param.beforeSend] A function called just before the
* send request. The first parameter of this function is the XHR object.
* @return {Promise} The promise
*/
function ajax(param) {
var xhr = new XMLHttpRequest();
return new RSVP.Promise(function (resolve, reject, notify) {
var k;
xhr.open(param.type || "GET", param.url, true);
xhr.addEventListener("load", function (e) {
var answer = {};
if (e.target.status >= 400) {
return reject(e);
}
answer.responseText = this.responseText;
answer.responseType = this.getResponseHeader("content-type");
answer.responseURL = param.url;
resolve(answer);
});
xhr.addEventListener("error", reject);
xhr.addEventListener("progress", notify);
if (typeof param.xhrFields === 'object' && param.xhrFields !== null) {
for (k in param.xhrFields) {
if (param.xhrFields.hasOwnProperty(k)) {
xhr[k] = param.xhrFields[k];
function waitForServiceWorkerActive(registration) {
var serviceWorker;
if (registration.installing) {
serviceWorker = registration.installing;
} else if (registration.waiting) {
serviceWorker = registration.waiting;
} else if (registration.active) {
serviceWorker = registration.active;
}
if (serviceWorker.state !== "activated") {
return RSVP.Promise(function (resolve, reject) {
serviceWorker.addEventListener('statechange', function (e) {
if (e.target.state === "activated") {
resolve();
}
}
}
xhr.send();
}, function () {
xhr.abort();
});
}
function sendMessage(message) {
// This wraps the message posting/response in a promise, which will resolve if the response doesn't
// contain an error, and reject with the error if it does. If you'd prefer, it's possible to call
// controller.postMessage() and set up the onmessage handler independently of a promise, but this is
// a convenient wrapper.
return new RSVP.Promise(function (resolve, reject, notify) {
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function (event) {
console.log(event);
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
};
// This sends the message data as well as transferring messageChannel.port2 to the service worker.
// The service worker can then use the transferred port to reply via postMessage(), which
// will in turn trigger the onmessage handler on messageChannel.port1.
// See https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage
return navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
});
}
function setStatus(statusMessage) {
console.log(statusMessage);
});
RSVP.delay(500).then(function () {
reject(new Error("Timeout service worker install"));
});
});
}
}
rJS(window)
.ready(function (g) {
g.props = {};
return g.getElement()
.push(function (element) {
g.props.element = element;
});
})
.ready(function (gadget) {
// Initialize the gadget local parameters
gadget.state_parameter_dict = {};
gadget.state_parameter_dict.default_document = window.location.protocol +
"//" + window.location.host +
window.location.pathname.substring(
0,
window.location.pathname.length - "gadget_jio_crib.html".length
) + "/";
console.log(gadget.state_parameter_dict.default_document);
this.state_parameter_dict.jio_storage = jIO.createJIO({
"type": "indexeddb",
"database": "ojs_source_code"
});
return this.state_parameter_dict.jio_storage.put(gadget.state_parameter_dict.default_document, {})
})
.ready(function (gadget) {
if ('serviceWorker' in navigator) {
// XXX Hack to not add a new service worker when one is already declared
if (!navigator.serviceWorker.controller) {
return new RSVP.Promise(function (resolve, reject, notify) {
navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(
function () {
if (navigator.serviceWorker.controller) {
resolve();
} else {
reject("Please reload this page to allow Service Worker to control this page");
}
})
.then(undefined, function (error) {
reject(error);
});
});
return new RSVP.Queue()
.push(function () {
return navigator.serviceWorker.register(
'/gadget_cribjs_bootloader_serviceworker.js',
{scope: '/'}
);
})
.push(function (registration) {
return waitForServiceWorkerActive(registration);
})
}
} else {
throw "Service Worker are not available in your browser";
}
})
/**
* allDocs return the list of document in the cache
*
* @params {Object} Not taken into account
* @return {} Return the data url of the document
*/
.declareMethod('allDocs', function (params) {
if (params && params.cached_only) {
return new RSVP.Queue()
.push(function () {
return sendMessage({
command: 'keys'
});
});
}
return new RSVP.Queue()
.push(function () {
return sendMessage({
command: 'allDocs'
});
});
})
/**
* get Return a data url. Cannot return a blob as the data
* is transmitted through an iFrame
*
* @url {string} url of the document to retrieve
* @return {data_url} Return the data url of the document
*/
.declareMethod('get', function (url) {
return new RSVP.Queue()
.push(function () {
return ajax({
url: url
var storage = this.state_parameter_dict.jio_storage;
return storage.getAttachment(this.state_parameter_dict.default_document, url)
.push(function (result) {
return jIO.util.readBlobAsDataURL(result);
})
.push(function (e) {
return e.target.result;
});
})
.push(function (result) {
return new Blob([result.responseText], {type: result.responseType});
})
.push(function (result) {
return jIO.util.readBlobAsDataURL(result);
})
.push(function (e) {
return e.target.result;
});
})
/**
* put Return a data url. Cannot provide a blob as the data
* is transmitted through an iFrame
*
* @url {string} url of the document to update
* @parameter {data_url} data url of the document to put, it will be transformed in a blob
* @return {data_url} Return the data url of the document
*/
.declareMethod('put', function (url, parameter) {
return new RSVP.Queue()
.push(function () {
return sendMessage({
command: 'add',
url: url,
information: jIO.util.dataURItoBlob(parameter)
});
}).push(function () {
// If the promise resolves, just display a success message.
console.log("Done adding " + url);
return 'Added to cache: ' + url + ' at ' + Date();
}).fail(setStatus);
.declareMethod('put', function (url, data_uri) {
var storage = this.state_parameter_dict.jio_storage;
data_uri = jIO.util.dataURItoBlob(data_uri);
console.log(this.state_parameter_dict.default_document);
console.log(url);
return storage.putAttachment(
this.state_parameter_dict.default_document,
url,
data_uri
);
})
.declareMethod('allDocs', function () {
var storage = this.state_parameter_dict.jio_storage;
console.log(this.state_parameter_dict.default_document);
return storage.allAttachments(this.state_parameter_dict.default_document)
.push(function(result) {
console.log(result);
return result;
})
})
/**
* Remove an url from the cache
*
* @url {string} url of the document to remove
* @return {}
*/
.declareMethod('remove', function (url) {
return new RSVP.Queue()
.push(function () {
return sendMessage({
command: 'delete',
url: url
});
});
var storage = this.state_parameter_dict.jio_storage;
return storage.removeAttachment(
this.state_parameter_dict.default_document,
url
);
});
}(rJS, RSVP, jIO));
\ No newline at end of file
}(window, rJS, jIO));
\ No newline at end of file
......@@ -21,7 +21,8 @@
}
function loadZipIntoCrib(crib_sw_gadget, zip, from_path, path_to_load) {
var promise_list = [], url_number = 0;
var promise_list = [], url_number = 0,
site_url = window.location.protocol + "//" + window.location.host;
zip.forEach(function (relativePath, zipEntry) {
var end_url;
url_number += 1;
......@@ -32,12 +33,11 @@
return;
}
relativePath = relativePath.substring(from_path.length);
if (!relativePath.startsWith("/") && !path_to_load.endsWith("/")) {
end_url = path_to_load + "/" + relativePath;
} else if (relativePath.startsWith("/") && path_to_load.endsWith("/")) {
end_url = path_to_load + relativePath.substring(1);
console.log(relativePath);
if (relativePath.startsWith("/")) {
end_url = relativePath.substring(1);
} else {
end_url = path_to_load + relativePath;
end_url = relativePath;
}
promise_list.push(
new RSVP.Queue()
......
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>CribJS Loader</title>
<script src="../lib/rsvp.js"></script>
<script src="../lib/renderjs.js"></script>
<script src="../lib/jszip.js" type="text/javascript"></script>
<script src="./gadget_landing_cribjs2.js"></script>
</head>
<body>
<div data-gadget-url="./crib-sw-gadget.html"
data-gadget-scope="crib_sw_gadget"
data-gadget-sandbox="public"></div>
<div data-gadget-url="./gadget_jio.html"
data-gadget-scope="jio_gadget"
data-gadget-sandbox="public"></div>
</body>
</html>
\ No newline at end of file
/*jslint browser: true, indent: 2, maxlen: 80*/
/*globals RSVP, rJS,
location, console, fetch, Promise*/
(function (window, document, RSVP, rJS, location, console, JSZip) {
"use strict";
function getExtension(url) {
var extension = url.split('.').pop();
if (extension.endsWith('/')) {
return ".html";
}
return "." + extension;
}
function getSetting(gadget, key, default_value) {
if (key === "site_editor_gadget_url") {
return window.location.protocol + "//" + window.location.host +
"/gadget_jio_crib.html";
}
return default_value;
}
function loadZipIntoCrib(crib_sw_gadget, zip, from_path, path_to_load) {
var promise_list = [], url_number = 0,
site_url = window.location.protocol + "//" + window.location.host;
zip.forEach(function (relativePath, zipEntry) {
var end_url;
url_number += 1;
if (zipEntry.dir) {
return;
}
if (!relativePath.startsWith(from_path)) {
return;
}
relativePath = relativePath.substring(from_path.length);
console.log(relativePath);
if (relativePath.startsWith("/")) {
end_url = relativePath.substring(1);
} else {
end_url = relativePath;
}
promise_list.push(
new RSVP.Queue()
.push(function () {
return zipEntry.async('blob');
})
.push(function (result) {
if (end_url.endsWith(".js")) {
// This is a ugly hack as mimetype needs to be correct for JS
result = result.slice(0, result.size, "application/javascript");
} else if (end_url.endsWith(".html")) {
// This is a ugly hack as mimetype needs to be correct for JS
result = result.slice(0, result.size, "text/html");
} else if (end_url.endsWith(".css")) {
// This is a ugly hack as mimetype needs to be correct for JS
result = result.slice(0, result.size, "text/css");
}
return crib_sw_gadget.put(end_url, {blob: result});
})
);
});
return RSVP.all(promise_list);
}
function loadContentFromZIPFile(gadget, options) {
var path_to_load = options.to_path,
from_path = options.from_path,
file_list = options.file_list;
if (file_list.length === 0) {
return "No File to Load";
}
return new RSVP.Queue()
.push(function () {
return RSVP.all([
gadget.getDeclaredGadget('crib_sw_gadget'),
JSZip.loadAsync(file_list[0])
]);
})
.push(function (result_list) {
return loadZipIntoCrib(
result_list[0], result_list[1], from_path, path_to_load
);
})
.push(console.log, console.log);
}
function loadContentFromZIPURL(gadget, options) {
var path_to_load = options.to_path, file_list, crib_sw_gadget,
from_path = options.from_path, zip_url = options.zip_url,
jio_gadget, url_list = [], url_number = 0;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget('crib_sw_gadget');
})
.push(function (returned_gadget) {
crib_sw_gadget = returned_gadget;
return fetch(zip_url)
.then(function (response) { // 2) filter on 200 OK
if (response.status === 200 || response.status === 0) {
return Promise.resolve(response.blob());
} else {
return Promise.reject(new Error(response.statusText));
}
});
})
.push(JSZip.loadAsync)
.push(function (zip) {
return loadZipIntoCrib(crib_sw_gadget, zip, from_path, path_to_load);
})
.push(console.log, console.log);
}
rJS(window)
.ready(function (g) {
g.props = {};
})
.declareMethod('loadFromZipUrl', function (options) {
return loadContentFromZIPURL(this, options);
})
.declareMethod('loadFromZipFile', function (options) {
return loadContentFromZIPFile(this, options);
})
.allowPublicAcquisition("getSetting", function (argument_list) {
return getSetting(this, argument_list[0], argument_list[1]);
});
}(window, document, RSVP, rJS, location, console, JSZip));
\ No newline at end of file
......@@ -52,7 +52,6 @@ var global = self, window = self;
self.addEventListener("fetch", function (event) {
var relative_url = event.request.url.split("#")[0]
.replace(self.registration.scope, "")
.replace(self.version_url, "");
if (relative_url[relative_url.length - 1] === "/" || relative_url === "") {
relative_url = relative_url + "index.html";
}
......@@ -60,7 +59,8 @@ var global = self, window = self;
event.respondWith(new Response(self.cache_list));
return;
}
else if (event.request !== undefined && event.request.referrer === self.registration.scope) {