Indexed DB URLs via Service Workers
var dbs = new Map(); // name --> Promise<IDBDatabase>
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET') return;
var url = new URL(event.request.url);
if (url.hostname !== 'indexeddb.test') return;
var parts = url.pathname.split('/');
var database = parts[1];
var store = parts[2];
var index = parts[3];
var query = new Map(url.search.substring(1).split('&').map(kv => kv.split('=')));
var key = query.get('key');
var path = query.get('path');
if (!dbs.has(database)) {
dbs.set(database, new Promise((resolve, reject) => {
var request = indexedDB.open(database);
// Abort the open if it was not already populated.
request.onupgradeneeded = e => request.transaction.abort();
request.onerror = e => reject(request.error);
request.onsuccess = e => resolve(request.result);
}));
}
event.respondWith(
dbs.get(database).then(db => new Promise((resolve, reject) => {
var tx = db.transaction(store);
var request = !index
? tx.objectStore(store).get(key)
: tx.objectStore(store).index(index).get(key);
request.onerror = e => reject(request.error);
request.onsuccess = e => {
var result = request.result;
if (path) path.split('.').forEach(id => { result = result[id]; });
resolve(new Response(result));
};
})));
});
<!DOCTYPE html>
<meta charset=utf-8>
<title>Indexed DB URLs via Service Worker</title>
<script>
var records = [
{name: 'alex', url: 'https://avatars0.githubusercontent.com/u/97331?v=3&s=200'},
{name: 'jungkee', url: 'https://avatars1.githubusercontent.com/u/1331169?v=3&s=200'},
{name: 'jake', url: 'https://avatars1.githubusercontent.com/u/93594?v=3&s=200'}
];
// Fetch the images
Promise.all(records.map(
record => fetch(record.url)
.then(response => response.blob())
.then(blob => { record.image = blob; })
))
.then(() => {
indexedDB.deleteDatabase('resources');
var open = indexedDB.open('resources');
// Set up the database schema
open.onupgradeneeded = () => {
var db = open.result;
var store = db.createObjectStore('records', {autoIncrement: true});
store.createIndex('by_name', 'name');
};
open.onsuccess = () => {
var db = open.result;
// Store the images into the database
var tx = db.transaction('records', 'readwrite');
var store = tx.objectStore('records');
records.forEach((record) => store.put(record));
tx.oncomplete = () => {
db.close();
// Register the service worker
navigator.serviceWorker.register('sw.js', {scope: '/'})
.then(registration => registration.installing)
.then(worker => {
worker.addEventListener('statechange', () => {
if (worker.state !== 'activated') return;
// Add a controlled iframe.
document.querySelector('iframe').src = 'frame.html';
});
});
};
};
});
</script>
<iframe id="frame" style="width: 700px; height: 300px;"></iframe>
<!DOCTYPE html>
<meta charset=utf-8>
<title>demo iframe</title>
<h1>The usual suspects:</h1>
<img src="http://indexeddb.test/resources/records/by_name?key=alex&path=image">
<img src="http://indexeddb.test/resources/records/by_name?key=jungkee&path=image">
<img src="http://indexeddb.test/resources/records/by_name?key=jake&path=image">
Let's say you're using Indexed DB for the offline data store for a catalog. One of the object stores contains product images. Wouldn't it be great if you could just have something like this in your catalog page?
<img src="indexeddb/database/store/id">
You can do this with Service Workers without browsers having to
implement it! All you need to do is carve out the URL namespace
intercepted by the fetch
handler in your service worker and decide
how to map URLs to database queries.
This example uses the imaginary host indexeddb.test
to provide URLs
of the form:
http://indexeddb.test/$DATABASE/$STORE?key=$KEY
http://indexeddb.test/$DATABASE/$STORE?key=$KEY&path=$PATH
http://indexeddb.test/$DATABASE/$STORE/$INDEX?key=$KEY
http://indexeddb.test/$DATABASE/$STORE/$INDEX?key=$KEY&path=$PATH
The path
query parameter is optional. If used, it is evaluated as
key path against the record returned by the database, allowing only
part of the record to be served up (e.g. an image stored as a Blob as
part of a record).
The example here sets up a database named resources
which contains
an object store named records
with an index by_name
. The following
URL is used to retrieve records from that index, and respond with the
image
property of the record.
<img src="http://indexeddb.test/resources/records/by_name?key=alex&path=image">
Indexed DB keys are typed, e.g. they can be strings, numbers, etc. The
key "1" (string) and the key 1 (number) are distinct, so if you're
using numeric keys you'll need to convert URL substrings to number
e.g. k = Number(s)
before querying the database. If your use case
requires both numeric and string keys in same application, one
approach would be to use JSON.parse()
on the key values, so string
keys would look like /db/store?key="foo"
while numeric keys would
simply be /db/store?key=123
Using /
as a delimiter puts restrictions on the names for
databases/stores/indexes that aren't present in the spec. So be sure
to come up with a scheme that will support your naming conventions.