alt-fetch plugin modified to work with the new plugin loading system (ref. #15)

merge-requests/3/head
Michał 'rysiek' Woźniak 2021-09-15 20:27:27 +00:00
rodzic 0c7fb8c946
commit 4e4c9b83af
3 zmienionych plików z 224 dodań i 230 usunięć

Wyświetl plik

@ -36,38 +36,36 @@ describe("plugin: alt-fetch", () => {
beforeEach(() => {
Object.assign(global, makeServiceWorkerEnv());
jest.resetModules();
self.LibResilientPlugins = new Array()
self.LibResilientConfig = {
plugins: {
'alt-fetch':{
endpoints: [
'https://alt.resilient.is/test.json',
'https://error.resilientis/test.json',
'https://timeout.resilientis/test.json'
]
}
global.LibResilientPluginConstructors = new Map()
init = {
name: 'alt-fetch',
endpoints: [
'https://alt.resilient.is/test.json',
'https://error.resilientis/test.json',
'https://timeout.resilientis/test.json'
]}
LR = {
log: (component, ...items)=>{
console.debug(component + ' :: ', ...items)
}
}
self.log = function(component, ...items) {
console.debug(component + ' :: ', ...items)
}
})
test("it should register in LibResilientPlugins", () => {
require("../../plugins/alt-fetch.js");
expect(self.LibResilientPlugins[0].name).toEqual('alt-fetch');
expect(LibResilientPluginConstructors.get('alt-fetch')().name).toEqual('alt-fetch');
});
test("it should fail with bad config", () => {
self.LibResilientConfig = {
plugins: {
'alt-fetch':{
endpoints: "this is incorrect"
}
}
init = {
name: 'alt-fetch',
endpoints: "this is incorrect"
}
require("../../plugins/alt-fetch.js")
expect.assertions(1)
expect(()=>{require("../../plugins/alt-fetch.js")}).toThrow(Error);
expect(()=>{
LibResilientPluginConstructors.get('alt-fetch')(LR, init)
}).toThrow(Error);
});
test("it should fetch the content, trying all configured endpoints (if fewer or equal to concurrency setting)", async () => {
@ -90,7 +88,7 @@ describe("plugin: alt-fetch", () => {
return Promise.resolve(response);
});
const response = await self.LibResilientPlugins[0].fetch('https://resilient.is/test.json');
const response = await LibResilientPluginConstructors.get('alt-fetch')(LR, init).fetch('https://resilient.is/test.json');
expect(fetch).toHaveBeenCalledTimes(3);
expect(await response.json()).toEqual({test: "success"})
@ -99,20 +97,16 @@ describe("plugin: alt-fetch", () => {
test("it should fetch the content using, trying <concurrency> random endpoints out of all configured (if more than concurrency setting)", async () => {
self.LibResilientConfig = {
plugins: {
'alt-fetch':{
endpoints: [
'https://alt.resilient.is/test.json',
'https://error.resilientis/test.json',
'https://timeout.resilientis/test.json',
'https://alt2.resilient.is/test.json',
'https://alt3.resilient.is/test.json',
'https://alt4.resilient.is/test.json'
]
}
}
}
init = {
name: 'alt-fetch',
endpoints: [
'https://alt.resilient.is/test.json',
'https://error.resilientis/test.json',
'https://timeout.resilientis/test.json',
'https://alt2.resilient.is/test.json',
'https://alt3.resilient.is/test.json',
'https://alt4.resilient.is/test.json'
]}
require("../../plugins/alt-fetch.js");
@ -133,7 +127,7 @@ describe("plugin: alt-fetch", () => {
return Promise.resolve(response);
});
const response = await self.LibResilientPlugins[0].fetch('https://resilient.is/test.json');
const response = await LibResilientPluginConstructors.get('alt-fetch')(LR, init).fetch('https://resilient.is/test.json');
expect(fetch).toHaveBeenCalledTimes(3);
expect(await response.json()).toEqual({test: "success"})
@ -143,7 +137,7 @@ describe("plugin: alt-fetch", () => {
test("it should set the LibResilient headers", async () => {
require("../../plugins/alt-fetch.js");
const response = await self.LibResilientPlugins[0].fetch('https://resilient.is/test.json');
const response = await LibResilientPluginConstructors.get('alt-fetch')(LR, init).fetch('https://resilient.is/test.json');
expect(fetch).toHaveBeenCalledTimes(3);
expect(await response.json()).toEqual({test: "success"})
@ -154,7 +148,7 @@ describe("plugin: alt-fetch", () => {
expect(response.headers.get('X-LibResilient-ETag')).toEqual('TestingETagHeader')
});
test("it should set the LibResilient ETag basd on Kast-Modified header (if ETag is not available in the original response)", async () => {
test("it should set the LibResilient ETag basd on Last-Modified header (if ETag is not available in the original response)", async () => {
require("../../plugins/alt-fetch.js");
global.fetch.mockImplementation((url, init) => {
@ -174,7 +168,7 @@ describe("plugin: alt-fetch", () => {
return Promise.resolve(response);
});
const response = await self.LibResilientPlugins[0].fetch('https://resilient.is/test.json');
const response = await LibResilientPluginConstructors.get('alt-fetch')(LR, init).fetch('https://resilient.is/test.json');
expect(fetch).toHaveBeenCalledTimes(3);
expect(await response.json()).toEqual({test: "success"})
@ -204,7 +198,7 @@ describe("plugin: alt-fetch", () => {
require("../../plugins/alt-fetch.js");
expect.assertions(1)
expect(self.LibResilientPlugins[0].fetch('https://resilient.is/test.json')).rejects.toThrow(Error)
expect(LibResilientPluginConstructors.get('alt-fetch')(LR, init).fetch('https://resilient.is/test.json')).rejects.toThrow(Error)
});
});

Wyświetl plik

@ -11,141 +11,141 @@
*/
// no polluting of the global namespace please
(function () {
(function(LRPC){
// this never changes
const pluginName = "alt-fetch"
LRPC.set(pluginName, (LR, init={})=>{
/*
* plugin config settings
*/
/*
* 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: [],
// sane defaults
let defaultConfig = {
// name of this plugin
// should not be changed
name: "alt-fetch",
// 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
}
// 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 LibResilientConfig
let config = {...defaultConfig, ...self.LibResilientConfig.plugins[defaultConfig.name]}
// 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) => {
// we're going to try a random endpoint and building an URL of the form:
// https://<endpoint_address>/<pubkey>/<rest_of_URL>
var path = url.replace(/https?:\/\/[^/]+\//, '')
// 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) {
// put in the address while we're at it
useEndpoints.push(
sourceEndpoints
.splice(Math.floor(Math.random() * sourceEndpoints.length), 1)[0] + path
)
}
// 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
}
// debug log
self.log(config.name, `fetching from alternative endpoints:\n ${useEndpoints.join('\n ')}`)
return Promise.any(
useEndpoints.map(
u=>fetch(u, {cache: "reload"})
))
.then((response) => {
// 4xx? 5xx? that's a paddlin'
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
self.log(config.name, "fetched:", response.url);
// we need to create a new Response object
// with all the headers added explicitly,
// since response.headers is immutable
var init = {
status: response.status,
statusText: response.statusText,
headers: {},
url: url
};
response.headers.forEach(function(val, header){
init.headers[header] = val;
});
// add the X-LibResilient-* headers to the mix
init.headers['X-LibResilient-Method'] = config.name
// 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
init.headers['X-LibResilient-ETag'] = response.headers.get('ETag')
if (init.headers['X-LibResilient-ETag'] === null) {
// far from perfect, but what are we going to do, eh?
init.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,
init
)
})
})
}
// and add ourselves to it
// with some additional metadata
self.LibResilientPlugins.push({
name: config.name,
description: 'HTTP(S) fetch() using alternative endpoints',
version: 'COMMIT_UNKNOWN',
fetch: fetchContentFromAlternativeEndpoints
})
/**
* getting content using regular HTTP(S) fetch()
*/
let fetchContentFromAlternativeEndpoints = (url) => {
// we're going to try a random endpoint and building an URL of the form:
// https://<endpoint_address>/<pubkey>/<rest_of_URL>
var path = url.replace(/https?:\/\/[^/]+\//, '')
// 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) {
// put in the address while we're at it
useEndpoints.push(
sourceEndpoints
.splice(Math.floor(Math.random() * sourceEndpoints.length), 1)[0] + path
)
}
}
// debug log
LR.log(pluginName, `fetching from alternative endpoints:\n ${useEndpoints.join('\n ')}`)
return Promise.any(
useEndpoints.map(
u=>fetch(u, {cache: "reload"})
))
.then((response) => {
// 4xx? 5xx? that's a paddlin'
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 alternative endpoints',
version: 'COMMIT_UNKNOWN',
fetch: fetchContentFromAlternativeEndpoints
}
})
// done with not polluting the global namespace
})()
})(LibResilientPluginConstructors)

Wyświetl plik

@ -15,67 +15,67 @@
const pluginName = "fetch"
LRPC.set(pluginName, (LR, init={})=>{
/*
* plugin config settings
*/
// sane defaults
let defaultConfig = {}
// merge the defaults with settings from init
let config = {...defaultConfig, ...init}
/**
* getting content using regular HTTP(S) fetch()
*/
let fetchContent = (url) => {
LR.log(pluginName, `regular fetch: ${url}`)
return fetch(url, {cache: "reload"})
.then((response) => {
// 4xx? 5xx? that's a paddlin'
if (response.status >= 400) {
// throw an Error to fall back to LibResilient:
throw new Error('HTTP Error: ' + response.status + ' ' + response.statusText);
}
// all good, it seems
LR.log(pluginName, `fetched successfully: ${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: response.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
responseInit.headers['X-LibResilient-ETag'] = response.headers.get('ETag')
// return the new response, using the Blob from the original one
return response
.blob()
.then((blob) => {
return new Response(
blob,
responseInit
)
})
})
}
/*
* plugin config settings
*/
// sane defaults
let defaultConfig = {}
// merge the defaults with settings from init
let config = {...defaultConfig, ...init}
/**
* getting content using regular HTTP(S) fetch()
*/
let fetchContent = (url) => {
LR.log(pluginName, `regular fetch: ${url}`)
return fetch(url, {cache: "reload"})
.then((response) => {
// 4xx? 5xx? that's a paddlin'
if (response.status >= 400) {
// throw an Error to fall back to LibResilient:
throw new Error('HTTP Error: ' + response.status + ' ' + response.statusText);
}
// all good, it seems
LR.log(pluginName, `fetched successfully: ${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: response.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
responseInit.headers['X-LibResilient-ETag'] = response.headers.get('ETag')
// return the new response, using the Blob from the original one
return response
.blob()
.then((blob) => {
return new Response(
blob,
responseInit
)
})
})
}
// return the plugin
return {
name: pluginName,
description: 'Just a regular HTTP(S) fetch()',
version: 'COMMIT_UNKNOWN',
fetch: fetchContent
}
// return the plugin
return {
name: pluginName,
description: 'Just a regular HTTP(S) fetch()',
version: 'COMMIT_UNKNOWN',
fetch: fetchContent
}
})
})
// done with not polluting the global namespace
})(LibResilientPluginConstructors)