Skip to content

Commit

Permalink
Deploy LibResilient with GitHub Pages as a backend
Browse files Browse the repository at this point in the history
  • Loading branch information
link2xt committed Oct 18, 2024
1 parent 712dffc commit 6e66012
Show file tree
Hide file tree
Showing 7 changed files with 2,206 additions and 0 deletions.
1 change: 1 addition & 0 deletions _layouts/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

<link rel="stylesheet" href="../assets/css/styles.css" type="text/css" />
<link rel="alternate" href="https://delta.chat/feed.xml" type="application/atom+xml" title="{%if tx.rss_news_title != ""%}{{ tx.rss_news_title }}{%else%}{{ txEn.rss_news_title }}{%endif%}" />
<script defer src="../assets/js/libresilient/libresilient.js"></script>
<script defer src="https://cosmos.delta.chat/banner.js"></script>
</head>

Expand Down
15 changes: 15 additions & 0 deletions assets/js/libresilient/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"plugins": [{
"name": "fetch"
},{
"name": "cache"
},{
"name": "alt-fetch",
"endpoints": [
"https://deltachat.github.io/deltachat-pages/"
]
}
],
"loggedComponents": ["service-worker", "fetch", "cache", "alt-fetch"]
}

41 changes: 41 additions & 0 deletions assets/js/libresilient/libresilient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* minimal LibResilient service worker loading script
* include it in your HTML files to deploy LibResilient
*
* for a more complete and fancy implementation of browser-side
* see `libresilient-fancy.js`
*/

// check if the browser implements ServiceWorkers API
if ('serviceWorker' in navigator) {

if (navigator.serviceWorker.controller) {
// Service worker already registered.
console.log('A Service Worker is already registered.')
} else {
// NOTICE: Assumptions made here:
// 1. the Service Worker script is called service-worker.js
// 2. ...and is located in the same directory as this file
var scriptPath = document.currentScript.src
var scriptFolder = scriptPath.substr(0, scriptPath.lastIndexOf( '/' )+1 )
var serviceWorkerPath = scriptFolder + 'service-worker.js'
console.log('LibResilient: using Service Worker script at: ' + serviceWorkerPath)
// this code actually finally registers the Service Worker
navigator.serviceWorker.register(serviceWorkerPath, {
// NOTICE: "There is frequent confusion surrounding the meaning and use of scope.
// NOTICE: Since a service worker can't have a scope broader than its own location,
// NOTICE: only use the scope option when you need a scope that is narrower than the default."
//
// so, "./" is the broadest scope, also the default. but the ServiceWorker can be registered
// for a narrower scope, for example "./subdir/".
scope: './'
}).then(function(reg) {
// Success.
console.log('LibResilient: Service Worker registered.')
}).catch(error => {
console.error("LibResilient: Error while registering a service worker!", error)
})
}
} else {
console.warn("unable to load LibResilient: ServiceWorker API not available in the browser")
}
165 changes: 165 additions & 0 deletions assets/js/libresilient/plugins/alt-fetch/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/* ========================================================================= *\
|* === HTTP(S) fetch() from alternative endpoints === *|
\* ========================================================================= */

/**
* this plugin does not implement any push method
*
* NOTICE: this plugin uses Promise.any()
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
* the polyfill is implemented in LibResilient's service-worker.js
*/

// no polluting of the global namespace please
(function(LRPC){
// this never changes
const pluginName = "alt-fetch"
LRPC.set(pluginName, (LR, init={})=>{

/*
* plugin config settings
*/

// sane defaults
let defaultConfig = {
// endpoints to use
//
// they have to respond to requests formatted like:
// <endpoint-url>/<path>
//
// let's say the endpoint is:
// https://example.com/api/endpoint/
// ...and that we are trying to get:
// <original-domain>/some/path/img.png
//
// the endpoint is supposed to return the expected image
// when this URL is requested:
// https://example.com/api/endpoint/some/path/img.png
//
// this has to be explicitly configured by the website admin
endpoints: [],

// how many simultaneous connections to different endpoints do we want
//
// more concurrency means higher chance of a request succeeding
// but uses more bandwidth and other resources;
//
// 3 seems to be a reasonable default
concurrency: 3
}

// merge the defaults with settings from the init var
let config = {...defaultConfig, ...init}

// reality check: endpoints need to be set to an array of non-empty strings
if (typeof(config.endpoints) !== "object" || !Array.isArray(config.endpoints)) {
let err = new Error("endpoints not confgured")
console.error(err)
throw err
}


/**
* getting content using regular HTTP(S) fetch()
*/
let fetchContentFromAlternativeEndpoints = (url, init={}) => {

// remove the https://original.domain/ bit to get the relative path
// TODO: this assumes that URLs we handle are always relative to the root
// TODO: of the original domain, this needs to be documented
var path = url.replace(/https?:\/\/[^/]+\//, '')

// we really want to make fetch happen, Regina!
// TODO: this change should *probably* be handled on the Service Worker level
init.cache = 'reload'

// we don't want to modify the original endpoints array
var sourceEndpoints = [...config.endpoints]

// if we have fewer than the configured concurrency or just as many, use all of them
if (sourceEndpoints.length <= config.concurrency) {
var useEndpoints = sourceEndpoints

// otherwise get `config.concurrency` endpoints at random
} else {
var useEndpoints = new Array()
while (useEndpoints.length < config.concurrency) {
useEndpoints.push(
sourceEndpoints
.splice(Math.floor(Math.random() * sourceEndpoints.length), 1)[0]
)
}
}

// add the rest of the path to each endpoint
useEndpoints.forEach((endpoint, index) => {
useEndpoints[index] = endpoint + path;
});

// debug log
LR.log(pluginName, `fetching from alternative endpoints:\n ${useEndpoints.join('\n ')}`)

return Promise.any(
useEndpoints.map(
u=>fetch(u, init)
))
.then((response) => {
// 4xx? 5xx? that's a paddlin'
// NOTICE: normally 4xx errors are returned to the client by other plugins,
// NOTICE: but here we are relying on multiple alternative endpoints;
// NOTICE: so, we want to maximize the chance that we get *something* useful
// TODO: shouldn't this reject() instead
if (response.status >= 400) {
// throw an Error to fall back to other plugins:
throw new Error('HTTP Error: ' + response.status + ' ' + response.statusText);
}
// all good, it seems
LR.log(pluginName, "fetched:", response.url);

// we need to create a new Response object
// with all the headers added explicitly,
// since response.headers is immutable
var responseInit = {
status: response.status,
statusText: response.statusText,
headers: {},
url: url
};
response.headers.forEach(function(val, header){
responseInit.headers[header] = val;
});

// add the X-LibResilient-* headers to the mix
responseInit.headers['X-LibResilient-Method'] = pluginName

// we will not have it most of the time, due to CORS rules:
// https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header
responseInit.headers['X-LibResilient-ETag'] = response.headers.get('ETag')
if (responseInit.headers['X-LibResilient-ETag'] === null) {
// far from perfect, but what are we going to do, eh?
responseInit.headers['X-LibResilient-ETag'] = response.headers.get('last-modified')
}

// return the new response, using the Blob from the original one
return response
.blob()
.then((blob) => {
return new Response(
blob,
responseInit
)
})
})
}

// return the plugin data structure
return {
name: pluginName,
description: 'HTTP(S) fetch() using preconfigured alternative endpoints',
version: 'COMMIT_UNKNOWN',
fetch: fetchContentFromAlternativeEndpoints
}

})
// done with not polluting the global namespace
})(LibResilientPluginConstructors)
Loading

0 comments on commit 6e66012

Please sign in to comment.