software/hoardy-web/./extension/lib/idbp.js

Passively capture, archive, and hoard your web browsing history, including the contents of the pages you visit, for later offline viewing, mirroring, and/or indexing. Your own personal private Wayback Machine that can also archive HTTP POST requests and responses, as well as most other HTTP-level data.

Files

Raw Source

Contents

/*
 * A tiny Promise-based wrapper over `window.indexedDB` API.
 *
 * Copyright (c) 2024 Jan Malakhovski <oxij@oxij.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

"use strict";

function idbRequestAsPromise(request) {
    return new Promise((resolve, reject) => {
        request.onerror = (event) => reject(event.target.error);
        request.onsuccess = (event) => resolve(event.target.result);
    });
}

function idbFunctionAsPromise(old, nthis) {
    return (...args) => {
        return idbRequestAsPromise(old.apply(nthis, args));
    };
}

function idbStoreProxyPromise(obj) {
    return {
        count: idbFunctionAsPromise(obj.count, obj),
        get: idbFunctionAsPromise(obj.get, obj),
        getKey: idbFunctionAsPromise(obj.getKey, obj),
        getAll: idbFunctionAsPromise(obj.getAll, obj),
        getAllKeys: idbFunctionAsPromise(obj.getAllKeys, obj),
        add: idbFunctionAsPromise(obj.add, obj),
        put: idbFunctionAsPromise(obj.put, obj),
        delete: idbFunctionAsPromise(obj.delete, obj),

        autoIncrement: obj.autoIncrement,
    };
}

function idbOpen(name, version, upgradeFunc) {
    return new Promise((resolve, reject) => {
        let request = window.indexedDB.open(name, version);
        request.onblocked = (event) => reject("blocked");
        request.onerror = (event) => reject(event.target.error);
        request.onsuccess = (event) => resolve(event.target.result);
        if (upgradeFunc !== undefined)
            request.onupgradeneeded = (event) => {
                let db = event.target.result;
                try {
                    upgradeFunc(db, event.oldVersion, event.newVersion);
                } catch (err) {
                    reject(err);
                    throw new Error("upgradeFunc threw an error, aborting indexdeDB upgrade");
                }
            };
    });
}

function idbDelete(name) {
    return idbRequestAsPromise(window.indexedDB.deleteDatabase(name));
}

function idbTransaction(db, mode, objectStoreNames, func) {
    return new Promise((resolve, reject) => {
        let transaction = db.transaction(objectStoreNames, mode);
        let result;
        let error;
        transaction.onerror = (event) => reject(error !== undefined ? error : event.target.error);
        transaction.oncomplete = (event) => {
            if (result instanceof Promise)
                result.then(resolve, reject);
            else
                resolve(result);
        };
        let args = [];
        for (let n of objectStoreNames)
            args.push(idbStoreProxyPromise(transaction.objectStore(n)));
        try {
            result = func(transaction, ...args);
        } catch (err) {
            error = err;
            transaction.abort();
        }
        if (result instanceof Promise)
            result.catch((err) => {
                error = err;
                transaction.abort();
                throw err;
            });
    });
}

async function idbDump(db) {
    for (let name of db.objectStoreNames) {
        console.log("store", name);
        await idbTransaction(db, "readonly", [name], async (transaction, store) => {
            let keys = await store.getAllKeys();
            console.log(name);
            for (let k of keys) {
                let v = await store.get(k);
                console.log("object", k, v);
            }
        }).catch(logError);
    }
}

async function idbExampleTest() {
    try {
        let db = await idbOpen("test", 1, (db, oldVersion, newVersion) => {
            db.createObjectStore("archive", { autoIncrement: true });
        });
        let res = await idbTransaction(db, "readwrite", ["archive"], async (transaction, archiveStore) => {
            let one = await archiveStore.add({data: "abc"});
            let two = await archiveStore.add({data: new Uint8Array([1, 2, 3])});
            //transaction.abort();
            //throw new Error("bad");
            return [one, two];
        });
        console.log("ok", res);
        db.close();
    } catch(err) {
        logError(err);
    } finally {
        idbDelete("test");
    }
}