libresilient/docs/ARCHITECTURE.md

5.7 KiB

Architecture

Eventually this will document the architecture of LibResilient.

Plugins

There are three kinds of plugins:

  • Transport plugins
    Plugins that retrieve website content, e.g. by using regular HTTPS fetch(), or by going through IPFS. They should also offer a way to publish content by website admins (if relevant credentials or encryption keys are provided, depending on the method).
    Methods these plugins implement:

    • fetch - fetch content from an external source (e.g., from IPFS)
    • publish - publish the content to the external source (e.g., to IPFS)
  • Stashing plugins
    Plugins that stash content locally (e.g., in the browser cache) for displaying when no transport plugin works, or before content is received via one of them.
    Methods these plugins implement:

    • fetch - fetch the locally stored content (e.g., from cache)
    • stash - stash the content locally (e.g., in cache)
    • unstash - clear the content from the local store (e.g., clear the cache)
  • Composing plugins
    Plugins that compose other plugins, for example by running them simultaneously to retrieve content from whichever succeeds first.
    Methods these plugins implement depend on which plugins they compose. Additionally, plugins being composed the uses key, providing the configuration for them the same way configuration is provided for plugins in the plugins key of LibResilientConfig.

Any plugin needs to add itself to the LibResilientPlugins global variable, using a data structure as follows:

self.LibResilientPlugins.push({
    name: 'plugin-name',
    description: 'Plugin description. Just a few words, ideally.',
    version: 'any relevant plugin version information',
    fetch: functionImplementingFetch,
    publish|stash|unstash: functionsImplementingRelevantFunctionality,
    uses: {
        composed-plugin-1: {
            configKey1: "whatever-data-here"
        },
        composed-plugin-2: {
            configKey2: "whatever-data-here"
        },
        {...}
    }
})

Transport plugins

Transport plugins must add X-LibResilient-Method and X-LibResilient-ETag headers to the response they return, so as to facilitate informing the user about new content after content was displayed using a stashing plugin.

  • X-LibResilient-Method:
    contains the name of the plugin used to fetch the content.

  • X-LibResilient-ETag:
    contains the ETag for the content; this can be an actual ETag header for HTTPS-based plugins, or some arbitrary string identifying a particular version of the resource (e.g., for IPFS-based plugins this can be the IPFS address, since that is based on content and different content results in a different IPFS address).

Stashing plugins

Stashing plugins must stash the request along with the X-LibResilient-Method and X-LibResilient-ETag headers.

Composing plugins

Composing plugins work by composing other plugins, for example to run them simultaneously and retrieve content from the first one that succeeds. A composing plugin needs to set the uses key in it's LibResilientPlugins. The key should contain mappings from plugin names to configuration:

uses: {
    composed-plugin-1: {
        configKey1: "whatever-data-here"
    },
    composed-plugin-2: {
        configKey2: "whatever-data-here"
    },
    {...}
}

Fetching a resource via LibResilient

Whenever a resource is being fetched on a LibResilient-enabled site, the service-worker.js script dispatches plugins in the set order. Currently this order is hard-coded in service-worker.js, and is:

  1. fetch, to use the upstream site directly if it is available,
  2. cache, to display the site immediately from the cache in case regular fetch fails,
  3. gun-ipfs, in the background if cache call succeeded, otherwise as the active fetch handler.

If a background plugin fetch() succeeds, the result is added to the cache and will be immediately available on page reload.

Stashed versions invalidation

Invalidation heuristic is rather naïve, and boils down to checking if either of X-LibResilient-Method or X-LibResilient-ETag differs between the response from a transport plugin and whatever has already been stashed by a stashing plugin. If either differs, the transport plugin response is considered "fresher".

This is far from ideal and will need improvements in the long-term. The difficulty is that different transport plugins can provide different ways of determining the "freshness" of fetched content -- HTTPS-based requests offer ETag, Date, Last-Modified, and other headers that can help with that; whereas IPFS can't really offer much apart from the address which itself is a hash of the content, so at least we know the content is different (but is it fresher though?).

Messaging

The ServiceWorker can communicate with the browser window using the Client.postMessage() to post messages to the browser window context using the relevant Client ID, retrieved from the fetch event object.

When the browser window context wants to message the service worker, it uses the Worker.postMessage() call, with clientId field set to the relevant client ID if a response is expected. ServiceWorker then again responds using Client.postMessage() using the clientId field as source of the Client ID.

Messages

This section is a work in progress.