nonlocal-forage
TypeScript icon, indicating that this package has built-in type declarations

0.1.2 • Public • Published

Nonlocal Forage

This is a set of drivers for localForage to make its storage non-local. More precisely, it is a set of drivers for using cloud storage services as a backend for “local” storage. At present, it supports using Google Drive, Dropbox, or WebDAV as backends.

It also has a FileSystemDirectoryHandle backend (i.e., a backend for use with FileSystemDirectoryHandles), simply because all of the cloud backends are based on the principle of using files within a directory structure, so it made sense to do the same with an actual directory structure.

Because the nonlocal storage may be shared, it makes sense to combine this with lockableForage. If you do, make sure to set the timeout time quite high (say, 10 seconds), as clock skew will interfere with locking. The caching driver uses lockableForage itself, and so the LockableForage class is exported as nonlocalForage.LockableForage. You do not need to import it separately.

General Approach

Nonlocal Forage declares a global namespace NonlocalForage which contains a number of drivers. You should usually use the cacheForage driver, which is a caching driver for using local storage as a fast temporary cache for cloud storage. Load it like so:

await localforage.defineDriver(NonlocalForage.cacheForage);

Then load your other nonlocal storage drivers.

Most nonlocalForage driver requires an actually local localForage instance for storing temporary data such as tokens and keys. This can be the default localforage instance, but this is not recommended.

const keylf = await localforage.createInstance({
    name: "nonlocal-forage-keys" // use any name you want
});

In addition, cacheForage requires a local localForage instance to use as a cache. This must not be the default localforage instance.

const cachelf = await localforage.createInstance({
    name: "nonlocal-forage-cache" // use any name you want
});

To create a nonlocalForage instance, define its driver (described for each backend, below), then create an instance using that driver. Several instance creation options are specific to nonlocalForage and must be set:

const nllf = await localforage.createInstance({
    driver: driverName, // String name of the driver to use

    localforage: keylf, // localForage instance to use for keys

    nonlocalforage: {
        /* Function to call to request transient activation, if needed. This
         * *must* be defined, and must be an asynchronous function. Transient
         * activation must be active when the promise returned by this function
         * resolves. */
        transientActivation: ..., 

        /* Optional second function, used if transient activation is needed
         * later, for example if a login token is temporary and must be
         * renewed. If this is not defined, transientActivation will be used
         * again. */
        lateTransientActivation: ...,

        /* Optional (but highly recommended, particularly if you use CORS)
         * function to show a cancellation dialog. When CORS is active, there's
         * no way to know if the login screen has been closed. Thus, it's
         * necessary to allow the user to tell the system that they've given up
         * on logging in. This should be a function that returns a promise that
         * *only resolves* if the user has chosen to cancel. */
        cancellable: ...,

        /* If cancellable is set, this should be a function that hides the
         * cancellation dialog shown by cancellable. */
        hideCancellable: ...,

        /* Optional directory name to use as a root for all nonlocalForage data
         * on this service. If not specified, the directory name
         * "nonlocalForage" will be used. */
        directory: "nonlocalForage",

        /* Normally, cloud backends use the directory above, plus the normal
         * `options.name`, plus `options.storeName`, to choose a directory. Set
         * this to true to ignore `options.name`. */
        noName: false,

        /* Set this to true to ignore `options.storeName`. */
        noStore: false,

        /* If set to a truthy value, will force a login prompt, rather than
         * logging in with saved credentials. For instance, when using Google
         * Drive, this will require the user to actually click their username,
         * rather than simply reusing the established account. */
        forcePrompt: false
    },

    name: "nonlocal", // Standard localFoage createInstance options are allowed

    ... // Other options are driver specific
});
await nllf.ready();

It is very important to await the ready() method of a nonlocalForage instance, as this is where the login actually occurs.

Regardless of the backend, entries are stores as files, with the name corresponding to the key, serialized to be safe on most platforms. The content is also serialized; see src/serializer.ts for details on how data is serialized.

Nonlocal backends—that is, every backend provided by this library other than cacheForage—additionally provide one extra method not normally in localForage: storageEstimate. await lf.storageEstimate() returns an object in the form of navigator.storage.estimate(). It has a quota field and a usage field, referencing the number of bytes of storage maximum provided by the backend, and the number of bytes used, respectively. This method is correctly named: it is an estimate.

cacheForage

If you're using cacheForage (which you are strongly advised to), you then need to link the nonlocalForage instance with the local caching instance as a cacheForage instance like so:

const clf = await localforage.createInstance({
    driver: "cacheForage",
    cacheForage: {
        local: cachelf,
        nonlocal: nllf
    }
});

The cacheForage instance (in this case, clf) can be used like any other localForage instance, and will transparently use a local copy of data until it's uploaded, and the remote copy for anything not cached locally.

If you are using lockableForage, make sure to initialize it with the backend localforage, not the caching localforage. Anything that needs to be controlled by locks should be accessed directly, uncached.

The cacheForage driver provides two methods in addition to the standard localForage methods:

Use clf.cachedSize() to get the amount of data, in bytes (estimated), currently in the cache that has not yet been flushed to the server. Note that it is not necessary to await this; cachedSize is not asynchronous.

Use clf.nonlocalPromise() to get the promise for the nonlocal side. await this to ensure that all data written to the cache (at the time this was called) is flushed.

Google Drive

The Google Drive driver is exposed as nonlocalForage.googleDriveLocalForage. Define it like so:

await localforage.defineDriver(NonlocalForage.googleDriveLocalForage);

Due to a lot of weirdness in how Google implements OAuth2, there are two options for handling login. Either login can be client-only, but the user will need to refresh it every hour (using lateTransientActivation), or a server component can be included to make login more transparent. The server component, called a “code server” as it handles authentication-code based login, is fairly simple; an example is given in server/google-code-server.jss.

Login is performed through oauth2-login.html, which must be present next to your web app. It is opened in a popup window for initial login. It also must be a valid redirect URI for your Google API project.

To create an instance, use the driver name "googleDrive", and include a googleDrive field in the options as an object with the apiKey and clientId fields set, like so:

const gdlf = await localforage.createInstance({
    driver: "googleDrive",
    localforage: keylf,
    nonlocalforage: {
        transientActivation: ..., 
        lateTransientActivation: ...
    },
    googleDrive: {
        apiKey: "Google Drive API key",
        clientId: "Google Drive API client ID"
    }
});
await gdlf.ready();

If using a code server, additionally set the codeServer field to the URL (which may be relative) to the code server:

const gdlf = await localforage.createInstance({
    driver: "googleDrive",
    ...
    googleDrive: {
        apiKey: "Google Drive API key",
        clientId: "Google Drive API client ID",
        codeServer: "/api/google-code-server.jss"
    }
});
await gdlf.ready();

Files are stored in <nonlocalforage.directory>/<options.name>/<options.storeName>/<key>.

Dropbox

The Dropbox driver is exposed as nonlocalForage.dropboxLocalForage. Define it like so:

await localforage.defineDriver(NonlocalForage.dropboxLocalForage);

To create an instance, use the driver name "dropbox", and include a dropbox field in the options as an object with the clientId field set, like so:

const dbxlf = await localforage.createInstance({
    driver: "dropbox",
    localforage: keylf,
    nonlocalforage: {
        transientActivation: ...
    },
    dropbox: {
        clientId: "Dropbox API client ID"
    }
});
await dbxlf.ready();

In order to be able to log into Dropbox, you must put oauth2-login.html next to your web app. It is opened in a window to begin the Dropbox login process. It also must be a valid redirect URI for your Dropbox API project.

Files are stored in Apps/<application name>/<nonlocalforage.directory>/<options.name>/<options.storeName>/<key>.

WebDAV

The WebDAV driver is exposed as nonlocalForage.webDAVLocalForage. Define it like so:

await localforage.defineDriver(NonlocalForage.webDAVLocalForage);

To create an instance, use the driver name "webDAV", and include a webDAV field in the options as an object with the username, password, and server fields set, like so:

const wdlf = await localforage.createInstance({
    driver: "webDAV",
    webDAV: {
        username: "WebDAV username",
        password: "WebDAV password",
        server: "WebDAV server URL"
    }
});
await wdlf.ready();

As WebDAV uses a username and password, it's up to you to prompt for them. It does not use a prompt, and does not use the keystore.

Files are stored in <nonlocalforage.directory>/<options.name>/<options.storeName>/<key>.

The WebDAV backend was intended for use with ownCloud and Nextcloud. You should advise users to add an “app password” for your app, and to add your domain to the list of “CORS domains”.

The WebDAV backend uses Perry Mitchell's WebDAV client to access WebDAV.

FileSystemDirectoryHandle

The FileSystemDirectoryHandle driver is exposed as nonlocalForage.fsdhLocalForage. Define it like so:

await localforage.defineDriver(NonlocalForage.fsdhForage);

To create an instance, use the driver name "FileSystemDirectoryHandle", and include a directoryHandle field in the options as the base directory handle to use. It must already have permissions; it's up to you to establish all necessary permissions before creating an instance.

const fsdhlf = await localforage.createInstance({
    driver: "FileSystemDirectoryHandle",
    directoryHandle: dirHandle
});
await fsdhlf.ready();

Files are stored in <base directory>/<nonlocalforage.directory>/<options.name>/<options.storeName>/<key>.

Package Sidebar

Install

npm i nonlocal-forage

Weekly Downloads

2

Version

0.1.2

License

ISC

Unpacked Size

292 kB

Total Files

21

Last publish

Collaborators

  • yahweasel