Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
J
jio
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Sven Franck
jio
Commits
3fabd1d6
Commit
3fabd1d6
authored
Jan 27, 2017
by
Sven Franck
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
WIP serviceworker storage prototype
parent
a6e2e877
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
638 additions
and
0 deletions
+638
-0
src/jio.storage/serviceworkerstorage.js
src/jio.storage/serviceworkerstorage.js
+245
-0
test/serviceworker.js
test/serviceworker.js
+393
-0
test/serviceworker.test.js
test/serviceworker.test.js
+0
-0
No files found.
src/jio.storage/serviceworkerstorage.js
0 → 100644
View file @
3fabd1d6
/**
* JIO Service Worker Storage Type = "serviceworker".
* Servieworker "filesystem" storage.
*/
/*global Blob, jIO, RSVP, navigator MessageChannel*/
/*jslint indent: 2 nomen: true maxerr: 3*/
(
function
(
jIO
,
RSVP
,
navigator
)
{
"
use strict
"
;
// no need to validate attachment name, because serviceworker.js will throw
function
restrictDocumentId
(
id
)
{
if
(
id
.
indexOf
(
"
/
"
)
>
-
1
)
{
throw
new
jIO
.
util
.
jIOError
(
"
id should be a name, not a path)
"
,
400
);
}
return
id
;
}
// validate browser support, serviceworker registration must be done in gadget
function
validateConnection
()
{
if
(
"
serviceWorker
"
in
navigator
===
false
)
{
throw
new
jIO
.
util
.
jIOError
(
"
Serviceworker not available in browser
"
,
503
);
}
}
// 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.
// Alternatively, onmessage handle and controller.postMessage() could be used
function
sendMessage
(
message
)
{
return
new
RSVP
.
Promise
(
function
(
resolve
,
reject
,
notify
)
{
var
messageChannel
=
new
MessageChannel
();
messageChannel
.
port1
.
onmessage
=
function
(
event
)
{
if
(
event
.
data
.
error
)
{
reject
(
event
.
data
.
error
);
}
else
{
resolve
(
event
.
data
.
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
return
navigator
.
serviceWorker
.
controller
.
postMessage
(
message
,
[
messageChannel
.
port2
]);
});
}
/**
* The JIO Serviceworker Storage extension
*
* @class ServiceWorkerStorage
* @constructor
*/
function
ServiceWorkerStorage
()
{}
ServiceWorkerStorage
.
prototype
.
post
=
function
()
{
throw
new
jIO
.
util
.
jIOError
(
"
Storage requires 'put' to create new cache
"
,
400
);
};
ServiceWorkerStorage
.
prototype
.
get
=
function
(
id
)
{
return
new
RSVP
.
Queue
()
.
push
(
function
()
{
return
validateConnection
();
})
.
push
(
function
()
{
return
sendMessage
({
command
:
"
get
"
,
id
:
restrictDocumentId
(
id
)
});
})
.
push
(
undefined
,
function
(
error
)
{
if
(
error
.
status
===
404
)
{
throw
new
jIO
.
util
.
jIOError
(
error
.
message
,
404
);
}
throw
error
;
});
};
ServiceWorkerStorage
.
prototype
.
put
=
function
(
id
)
{
return
new
RSVP
.
Queue
()
.
push
(
function
()
{
return
validateConnection
();
})
.
push
(
function
()
{
return
sendMessage
({
command
:
"
get
"
,
id
:
restrictDocumentId
(
id
)
});
})
.
push
(
undefined
,
function
(
error
)
{
if
(
error
.
status
===
404
)
{
return
new
RSVP
.
Queue
()
.
push
(
function
()
{
return
sendMessage
({
command
:
"
put
"
,
id
:
id
});
});
}
throw
error
;
});
};
ServiceWorkerStorage
.
prototype
.
remove
=
function
(
id
)
{
return
new
RSVP
.
Queue
()
.
push
(
function
()
{
return
validateConnection
();
})
.
push
(
function
()
{
return
sendMessage
({
command
:
"
allAttachments
"
,
id
:
restrictDocumentId
(
id
)
});
})
.
push
(
function
(
attachment_dict
)
{
var
url_list
=
[],
url
;
for
(
url
in
attachment_dict
)
{
if
(
attachment_dict
.
hasOwnProperty
(
url
))
{
url_list
.
append
(
sendMessage
({
command
:
"
removeAttachment
"
,
id
:
url
}));
}
}
return
RSVP
.
all
(
url_list
);
})
.
push
(
function
()
{
return
sendMessage
({
command
:
"
remove
"
,
id
:
restrictDocumentId
(
id
)
});
});
};
ServiceWorkerStorage
.
prototype
.
removeAttachment
=
function
(
id
,
url
)
{
return
new
RSVP
.
Queue
()
.
push
(
function
()
{
return
validateConnection
();
})
.
push
(
function
()
{
return
sendMessage
({
command
:
"
removeAttachment
"
,
id
:
restrictDocumentId
(
id
),
name
:
url
});
});
};
ServiceWorkerStorage
.
prototype
.
getAttachment
=
function
(
id
,
url
)
{
// NOTE: alternatively get could also be run "official" way via
// an ajax request, which the serviceworker would catch via fetch listener!
// for a filesystem equivalent however, we don't assume fetching resources
// from the network, so all methods will go through sendMessage
return
new
RSVP
.
Queue
()
.
push
(
function
()
{
return
validateConnection
();
})
.
push
(
function
()
{
return
sendMessage
({
command
:
"
getAttachment
"
,
id
:
restrictDocumentId
(
id
),
name
:
url
});
})
.
push
(
function
(
my_blob_response
)
{
return
my_blob_response
;
});
};
ServiceWorkerStorage
.
prototype
.
putAttachment
=
function
(
id
,
name
,
param
)
{
return
new
RSVP
.
Queue
()
.
push
(
function
()
{
return
validateConnection
();
})
.
push
(
function
()
{
return
sendMessage
({
command
:
"
putAttachment
"
,
id
:
id
,
name
:
name
,
content
:
param
});
});
};
ServiceWorkerStorage
.
prototype
.
allAttachments
=
function
(
id
)
{
return
new
RSVP
.
Queue
()
.
push
(
function
()
{
return
validateConnection
();
})
.
push
(
function
()
{
return
sendMessage
({
command
:
"
allAttachments
"
,
id
:
restrictDocumentId
(
id
)
});
});
};
ServiceWorkerStorage
.
prototype
.
hasCapacity
=
function
(
name
)
{
return
(
name
===
"
list
"
);
};
// returns a list of all caches ~ folders
ServiceWorkerStorage
.
prototype
.
allDocs
=
function
(
options
)
{
var
context
=
this
;
if
(
options
===
undefined
)
{
options
=
{};
}
return
new
RSVP
.
Queue
()
.
push
(
function
()
{
return
validateConnection
();
})
.
push
(
function
()
{
if
(
context
.
hasCapacity
(
"
list
"
))
{
return
context
.
buildQuery
(
options
);
}
})
.
push
(
function
(
result
)
{
return
result
;
});
};
ServiceWorkerStorage
.
prototype
.
buildQuery
=
function
(
options
)
{
return
new
RSVP
.
Queue
()
.
push
(
function
()
{
return
validateConnection
();
})
.
push
(
function
()
{
return
sendMessage
({
command
:
"
allDocs
"
,
options
:
options
});
});
};
jIO
.
addStorage
(
"
serviceworker
"
,
ServiceWorkerStorage
);
}(
jIO
,
RSVP
,
navigator
,
MessageChannel
));
test/serviceworker.js
0 → 100644
View file @
3fabd1d6
/*
* JIO Service Worker Storage Backend.
*/
// this polyfill provides Cache.add(), Cache.addAll(), and CacheStorage.match(),
// should not be needed for Chromium > 47 And Firefox > 39
// see https://developer.mozilla.org/en-US/docs/Web/API/Cache
// importScripts('./serviceworker-cache-polyfill.js');
// debug:
// chrome://cache/
// chrome://inspect/#service-workers
// chrome://serviceworker-internals/
//
// bar = new Promise(function (resolve, reject) {
// return caches.keys()
// .then(function (result) {
// console.log(result);
// return caches.open(result[0])
// .then(function(cache){
// return cache.keys()
// .then(function (request_list) {
// console.log(request_list);
// console.log("DONE");
// resolve();
// });
// });
// });
//});
// clear last cache
// caches.keys().then(function(key_list) {console.log(key_list);return caches.open(key_list[0]);}).then(function(cache) {return cache.keys().then(function(request_list) {console.log(request_list); return cache.delete(request_list[0]);})});
// list caches
// caches.keys().then(function(key_list) {console.log(key_list);return caches.open(key_list[0]);}).then(function(cache) {return cache.keys().then(function(request_list) {console.log(request_list);})});
// multiple serviceworkers => https://github.com/w3c/ServiceWorker/issues/921
// https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers
// intro http://www.html5rocks.com/en/tutorials/service-worker/introduction/
// selective cache https://github.com/GoogleChrome/samples/blob/gh-pages/service-worker/selective-caching/service-worker.js
// handling POST with indexedDB: https://serviceworke.rs/request-deferrer.html
// versioning allows to keep a clean cache, current_cache is accessed on fetch
var
CURRENT_CACHE_VERSION
=
1
;
var
CURRENT_CACHE
;
var
CURRENT_CACHE_DICT
=
{
"
self
"
:
"
self-v
"
+
CURRENT_CACHE_VERSION
};
// runs while an existing worker runs or nothing controls the page (update here)
//self.addEventListener('install', function (event) {
//
//});
// runs active page, changes here (like deleting old cache) breaks page
self
.
addEventListener
(
"
activate
"
,
function
(
event
)
{
var
expected_cache_name_list
=
Object
.
keys
(
CURRENT_CACHE_DICT
).
map
(
function
(
key
)
{
return
CURRENT_CACHE_DICT
[
key
];
});
event
.
waitUntil
(
caches
.
keys
()
.
then
(
function
(
cache_name_list
)
{
return
Promise
.
all
(
cache_name_list
.
map
(
function
(
cache_name
)
{
version
=
cache_name
.
split
(
"
-v
"
)[
1
];
// removes caches which are out of version
if
(
!
(
version
&&
parseInt
(
version
,
10
)
===
CURRENT_CACHE_VERSION
))
{
return
caches
.
delete
(
cache_name
);
}
// removes caches which are not on the list of expected names
if
(
expected_cache_name_list
.
indexOf
(
cache_name
)
===
-
1
)
{
return
caches
.
delete
(
cache_name
);
}
})
);
})
);
});
// XXX "Server"
// intercept GET/POST network requests, serve from cache or network
/*
self.addEventListener("fetch", function (event) {
var url = event.request.url,
cacheable_list = [],
isCacheable = function (el) {
return url.indexOf(el) >= 0;
};
if (event.request.method === "GET") {
event.respondWith(caches.open(CURRENT_CACHE_DICT["self"])
.then(function(cache) {
return cache.match(event.request)
.then(function(response) {
// cached, return from cache
if (response) {
return response;
// not cached, fetch from network
}
// clone call, because any operation like fetch/put... will
// consume the request, so we need a copy of the original
// (see https://fetch.spec.whatwg.org/#dom-request-clone)
return fetch(event.request.clone())
.then(function(response) {
// add resource to cache
if (response.status < 400 && cacheable_list.some(isCacheable)) {
cache.put(event.request, response.clone());
}
return response;
});
});
})
.catch(function(error) {
// This catch() will handle exceptions that arise from the match()
// or fetch() operations. Note that a HTTP error response (e.g.
// 404) will NOT trigger an exception. It will return a normal
// response object that has the appropriate error code set.
throw error;
})
);
// XXX handle post with indexedDB here
//} else {
// event.respondWith(fetch(event.request));
}
});
*/
self
.
addEventListener
(
"
message
"
,
function
(
event
)
{
var
param
=
event
.
data
,
item
,
mime_type
,
result_list
;
switch
(
param
.
command
)
{
// case "post" not supported
// test if cache exits
case
"
get
"
:
caches
.
keys
().
then
(
function
(
key_list
)
{
var
i
,
len
;
CURRENT_CACHE
=
param
.
id
+
"
-v
"
+
CURRENT_CACHE_VERSION
;
for
(
i
=
0
,
len
=
key_list
.
length
;
i
<
len
;
i
+=
1
)
{
if
(
key_list
[
i
]
===
CURRENT_CACHE
)
{
event
.
ports
[
0
].
postMessage
({
error
:
null
});
}
}
// event.ports[0] corresponds to the MessagePort that was transferred
// as part of the controlled page's call to controller.postMessage().
// Therefore, event.ports[0].postMessage() will trigger the onmessage
// handler from the controlled page. It's up to you how to structure
// the messages that you send back; this is just one example.
event
.
ports
[
0
].
postMessage
({
error
:
{
"
status
"
:
404
,
"
message
"
:
"
Cache does not exist.
"
}
});
})
.
catch
(
function
(
error
)
{
event
.
ports
[
0
].
postMessage
({
error
:
{
"
message
"
:
error
.
toString
()}
});
});
break
;
// create new cache by opening it. this will only run once per cache/folder
case
"
put
"
:
if
(
param
.
id
===
"
self
"
)
{
event
.
port
[
0
].
postMessage
({
error
:
{
"
status
"
:
406
,
"
message
"
:
"
Reserved cache name. Please choose a different name.
"
}
});
}
CURRENT_CACHE
=
param
.
id
+
"
-v
"
+
CURRENT_CACHE_VERSION
;
CURRENT_CACHE_DICT
[
param
.
id
]
=
CURRENT_CACHE
;
caches
.
open
(
CURRENT_CACHE
)
.
then
(
function
()
{
event
.
ports
[
0
].
postMessage
({
error
:
null
,
data
:
param
.
id
});
})
.
catch
(
function
(
error
)
{
event
.
ports
[
0
].
postMessage
({
error
:
{
"
message
"
:
error
.
toString
()}
});
});
break
;
// remove a cache
case
"
remove
"
:
delete
CURRENT_CACHE_DICT
[
param
.
id
];
CURRENT_CACHE
=
param
.
id
+
"
-v
"
+
CURRENT_CACHE_VERSION
;
caches
.
delete
(
CURRENT_CACHE
)
.
then
(
function
()
{
event
.
ports
[
0
].
postMessage
({
error
:
null
});
})
.
catch
(
function
(
error
)
{
event
.
ports
[
0
].
postMessage
({
error
:
{
"
message
"
:
error
.
toString
()}
});
});
break
;
// return list of caches ~ folders
case
"
allDocs
"
:
caches
.
keys
().
then
(
function
(
key_list
)
{
var
result_list
=
[],
id
,
i
;
for
(
i
=
0
;
i
<
key_list
.
length
;
i
+=
1
)
{
id
=
key_list
[
i
].
split
(
"
-v
"
)[
0
];
if
(
id
!==
"
self
"
)
{
result_list
.
push
({
"
id
"
:
id
,
"
value
"
:
{}
});
}
}
event
.
ports
[
0
].
postMessage
({
error
:
null
,
data
:
result_list
});
})
.
catch
(
function
(
error
)
{
event
.
ports
[
0
].
postMessage
({
error
:
{
"
message
"
:
error
.
toString
()}
});
});
break
;
// return all urls stored in a cache
case
"
allAttachments
"
:
CURRENT_CACHE
=
param
.
id
+
"
-v
"
+
CURRENT_CACHE_VERSION
;
// returns a list of the URLs corresponding to the Request objects
// that serve as keys for the current cache. We assume all files
// are kept in cache, so there will be no network requests.
caches
.
open
(
CURRENT_CACHE
)
.
then
(
function
(
cache
)
{
cache
.
keys
()
.
then
(
function
(
request_list
)
{
var
result_list
=
request_list
.
map
(
function
(
request
)
{
return
request
.
url
;
}),
attachment_dict
=
{},
i
,
len
;
for
(
i
=
0
,
len
=
result_list
.
length
;
i
<
len
;
i
+=
1
)
{
attachment_dict
[
result_list
[
i
]]
=
{};
}
event
.
ports
[
0
].
postMessage
({
error
:
null
,
data
:
attachment_dict
});
});
})
.
catch
(
function
(
error
)
{
event
.
ports
[
0
].
postMessage
({
error
:
{
"
message
"
:
error
.
toString
()}
});
});
break
;
case
"
removeAttachment
"
:
CURRENT_CACHE
=
param
.
id
+
"
-v
"
+
CURRENT_CACHE_VERSION
;
caches
.
open
(
CURRENT_CACHE
)
.
then
(
function
(
cache
)
{
request
=
new
Request
(
param
.
name
,
{
mode
:
'
no-cors
'
});
cache
.
delete
(
request
)
.
then
(
function
(
success
)
{
event
.
ports
[
0
].
postMessage
({
error
:
success
?
null
:
{
"
status
"
:
404
,
"
message
"
:
"
Item not found in cache.
"
}
});
});
})
.
catch
(
function
(
error
)
{
event
.
ports
[
0
].
postMessage
({
error
:
{
'
message
'
:
error
.
toString
()}
});
});
break
;
case
"
getAttachment
"
:
CURRENT_CACHE
=
param
.
id
+
"
-v
"
+
CURRENT_CACHE_VERSION
;
caches
.
open
(
CURRENT_CACHE
)
.
then
(
function
(
cache
)
{
return
cache
.
match
(
param
.
name
)
.
then
(
function
(
response
)
{
// the response body is a ReadableByteStream which cannot be
// passed back through postMessage apparently. This link
// https://jakearchibald.com/2015/thats-so-fetch/ explains
// what can be done to get a Blob to return
// XXX Improve
// However, calling blob() does not allow to set mime-type, so
// for currently the blob is created, read, stored as new blob
// and returned (to be read again)
// https://github.com/whatwg/streams/blob/master/docs/ReadableByteStream.md
mime_type
=
response
.
headers
.
get
(
"
Content-Type
"
);
return
response
.
clone
().
blob
();
})
.
then
(
function
(
response_as_blob
)
{
return
new
Promise
(
function
(
resolve
)
{
var
blob_reader
=
new
FileReader
();
blob_reader
.
onload
=
resolve
;
blob_reader
.
readAsText
(
response_as_blob
);
});
})
.
then
(
function
(
reader_response
)
{
return
new
Blob
([
reader_response
.
target
.
result
],
{
"
type
"
:
mime_type
});
})
.
then
(
function
(
converted_response
)
{
if
(
converted_response
)
{
event
.
ports
[
0
].
postMessage
({
error
:
null
,
data
:
converted_response
});
}
else
{
event
.
ports
[
0
].
postMessage
({
error
:
{
"
status
"
:
404
,
"
message
"
:
"
Item not found in cache.
"
}
});
}
});
})
.
catch
(
function
(
error
)
{
event
.
ports
[
0
].
postMessage
({
error
:
{
'
message
'
:
error
.
toString
()}
});
});
break
;
case
"
putAttachment
"
:
CURRENT_CACHE
=
param
.
id
+
"
-v
"
+
CURRENT_CACHE_VERSION
;
caches
.
open
(
CURRENT_CACHE
)
.
then
(
function
(
cache
)
{
// If event.data.url isn't a valid URL, new Request() will throw a
// TypeError which will be handled by the outer .catch().
// Hardcode {mode: 'no-cors} since the default for new Requests
// constructed from strings is to require CORS, and we don't have any
// way of knowing whether an arbitrary URL that a user entered
// supports CORS.
request
=
new
Request
(
param
.
name
,
{
mode
:
"
no-cors
"
});
response
=
new
Response
(
param
.
content
);
return
cache
.
put
(
request
,
response
);
})
.
then
(
function
()
{
event
.
ports
[
0
].
postMessage
({
error
:
null
});
})
.
catch
(
function
(
error
)
{
event
.
ports
[
0
].
postMessage
({
error
:
{
"
message
"
:
error
.
toString
()}
});
});
break
;
// refuse all else
default
:
throw
"
Unknown command:
"
+
event
.
data
.
command
;
}
});
test/serviceworker.test.js
0 → 100644
View file @
3fabd1d6
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment