kopia lustrzana https://gitlab.com/rysiekpl/libresilient
125 wiersze
7.0 KiB
Markdown
125 wiersze
7.0 KiB
Markdown
# Architecture
|
|
|
|
Eventually this will document the architecture of LibResilient.
|
|
|
|
## Plugins
|
|
|
|
There are three kinds of plugins:
|
|
|
|
- **Transport plugins**
|
|
Plugins that *retrieve* website content, for example by using regular HTTPS [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), or by going through [IPFS](https://js.ipfs.io/). 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](https://developer.mozilla.org/en-US/docs/Web/API/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` (which is configurable via `config.json`).
|
|
|
|
Every plugin needs to be implemented as a constructor function that is added to the `LibResilientPluginConstructors` [Map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) object for later instantiation.
|
|
|
|
The constructor function should return a structure as follows (fields depending on the plugin type):
|
|
|
|
```javascript
|
|
{
|
|
name: 'plugin-name',
|
|
description: 'Plugin description. Just a few words, ideally.',
|
|
version: 'any relevant plugin version information',
|
|
fetch: functionImplementingFetch,
|
|
publish|stash|unstash: functionsImplementingRelevantFunctionality,
|
|
uses: []
|
|
}
|
|
```
|
|
|
|
### 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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/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; or to run them in a particular order. A composing plugin needs to set the `uses` key in the object returned by it's constructor. The key should contain mappings from plugin names to configuration:
|
|
|
|
```javascript
|
|
uses: [{
|
|
name: "composed-plugin-1",
|
|
configKey1: "whatever-data-here"
|
|
},{
|
|
name: "composed-plugin-2",
|
|
configKey2: "whatever-data-here"
|
|
},
|
|
{...}
|
|
}]
|
|
```
|
|
|
|
If these mappings are to be configured via the global configuration file, the `uses` key should instead point to `config.uses`:
|
|
|
|
```javascript
|
|
uses: config.uses
|
|
```
|
|
|
|
## 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. This order is configured via the `plugins` key of the `LibResilientConfig` variable, usually set via the `config.json` config file.
|
|
|
|
A minimal default configuration is hard-coded in case no site-specific configuration is provided. This default configuration runs these plugins:
|
|
|
|
1. `fetch`, to use the upstream site directly if it is available,
|
|
1. `cache`, to display the site immediately from the cache in case regular `fetch` fails (if content is already cached from previous visit).
|
|
|
|
A more robust configuration could look like this:
|
|
|
|
```json
|
|
{
|
|
"plugins": [{
|
|
"name": "fetch"
|
|
},{
|
|
"name": "cache"
|
|
},{
|
|
"name": "alt-fetch",
|
|
"endpoints": [
|
|
"https://fallback-endpoint.example.com"
|
|
]}
|
|
}]
|
|
}
|
|
```
|
|
|
|
For each resource, such a config would:
|
|
|
|
1. Perform a regular `fetch()` to the main site domain first; if that succeeds, content is added to cache and displayed to the user.
|
|
1. If the `fetch()` failed, the cache would be checked.
|
|
1. If the resource was cached, it would be displayed; at the same time, a background request for that resource would be made to `fallback-endpoint.example.com` instead of the (failing) main domain; if that succeeded, the new version of the resource would be cached.
|
|
1. If the resource was not cached, a request for that resource would be made to `fallback-endpoint.example.com`; if that succeded, the resource would be displayed and cached.
|
|
|
|
## 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()`](https://developer.mozilla.org/en-US/docs/Web/API/Client/postMessage) to post messages to the browser window context using the relevant [`Client ID`](https://developer.mozilla.org/en-US/docs/Web/API/Client/id), retrieved from the fetch event object.
|
|
|
|
When the browser window context wants to message the service worker, it uses the [`Worker.postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/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.
|