kopia lustrzana https://gitlab.com/rysiekpl/libresilient
alt-fetch plugin modified to work with the new plugin loading system (ref. #15)
rodzic
0c7fb8c946
commit
4e4c9b83af
|
@ -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)
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -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)
|
||||
|
|
120
plugins/fetch.js
120
plugins/fetch.js
|
@ -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)
|
||||
|
|
Ładowanie…
Reference in New Issue