redirect plugin implemented, along with tests (ref. #37)

merge-requests/12/merge
Michał 'rysiek' Woźniak 2022-03-09 21:13:08 +00:00
rodzic 50c7053764
commit 4212bb1c3b
2 zmienionych plików z 238 dodań i 0 usunięć

Wyświetl plik

@ -0,0 +1,102 @@
const makeServiceWorkerEnv = require('service-worker-mock');
global.fetch = require('node-fetch');
jest.mock('node-fetch')
describe("plugin: redirect", () => {
beforeEach(() => {
Object.assign(global, makeServiceWorkerEnv());
jest.resetModules();
global.LibResilientPluginConstructors = new Map()
init = {
name: 'redirect',
redirectStatus: 302,
redirectStatusText: "Found",
redirectTo: "https://redirected.example.org/subdir/"
}
LR = {
log: (component, ...items)=>{
console.debug(component + ' :: ', ...items)
}
}
})
test("it should register in LibResilientPluginConstructors", () => {
init = {
name: 'redirect',
redirectTo: 'https://example.org/'
}
require("../../plugins/redirect.js");
expect(LibResilientPluginConstructors.get('redirect')(LR, init).name).toEqual('redirect');
});
test("it should fail with incorrect redirectTo config value", () => {
init = {
name: 'redirect',
redirectTo: false
}
require("../../plugins/redirect.js")
expect.assertions(1)
expect(()=>{
LibResilientPluginConstructors.get('redirect')(LR, init)
}).toThrow(Error);
});
test("it should fail with incorrect redirectStatus config value", () => {
init = {
name: 'redirect',
redirectTo: 'https://example.org/',
redirectStatus: 'incorrect'
}
require("../../plugins/redirect.js")
expect.assertions(1)
expect(()=>{
LibResilientPluginConstructors.get('redirect')(LR, init)
}).toThrow(Error);
});
test("it should fail with incorrect redirectStatusText config value", () => {
init = {
name: 'redirect',
redirectTo: 'https://example.org/',
redirectStatusText: false
}
require("../../plugins/redirect.js")
expect.assertions(1)
expect(()=>{
LibResilientPluginConstructors.get('redirect')(LR, init)
}).toThrow(Error);
});
test("it should register in LibResilientPluginConstructors without error even if all config data is incorrect, as long as enabled is false", () => {
init = {
name: 'redirect',
redirectTo: false,
redirectStatus: "incorrect",
redirectStatusText: false,
enabled: false
}
require("../../plugins/redirect.js");
expect(LibResilientPluginConstructors.get('redirect')(LR, init).name).toEqual('redirect');
});
test("it should return a 302 Found redirect for any request", async () => {
init = {
name: 'redirect',
redirectTo: "https://redirected.example.org/subdirectory/"
}
require("../../plugins/redirect.js");
const response = await LibResilientPluginConstructors.get('redirect')(LR, init).fetch('https://resilient.is/test.json');
//expect().toEqual()
expect(response.url).toEqual('https://resilient.is/test.json')
expect(response.status).toEqual(302)
expect(response.statusText).toEqual('Found')
expect(response.headers.get('location')).toEqual('https://redirected.example.org/subdirectory/test.json')
})
});

136
plugins/redirect.js 100644
Wyświetl plik

@ -0,0 +1,136 @@
/* ========================================================================= *\
|* === HTTP(S) fetch() from alternative endpoints === *|
\* ========================================================================= */
/**
* this plugin does not implement any push method
*
* use this plugin to redirect all requests to a location based on a particular URL
*
* for example, if the original website is `https://some.example.org/`, and the `redirectTo`
* config field is set to `https://other.example.com/subdir/`, the redirect location will
* be the request URL with `https://some.example.org/` replaced with `https://other.example.com/subdir/`:
*
* - https://some.example.org/ redirects to https://other.example.com/subdir/
* - https://some.example.org/favicon.ico redirects to https://other.example.com/subdir/favicon.ico
* - https://some.example.org/api/test.json redirects to https://other.example.com/subdir/api/test.json
* ...and so on
*/
// no polluting of the global namespace please
(function(LRPC){
// this never changes
const pluginName = "redirect"
LRPC.set(pluginName, (LR, init={})=>{
/*
* plugin config settings
*/
// sane defaults
let defaultConfig = {
/*
* URL to base the redirect target on
*
* all requests will be redirected to <redirectTo>/<URL>
*
* if not a string, the plugin returns an error, thus allowing
* plugins configured later in the chain to handle requests
*
* don't forget the ending '/'
*/
redirectTo: null,
/*
* the HTTP code and status to use while redirecting
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
*
* if redirectStatus is not a number, the plugin returns an error,
* thus allowing plugins configured later in the chain to handle requests
*
* by default using 302 Found
*/
redirectStatus: 302,
redirectStatusText: "Found",
/*
* is the plugin enabled?
*
* if this is set to false:
* - the code will *not* check if the plugin is misconfigured;
* - when any handler is called, it will simply return false.
*
* mainly used to load a plugin in a service worker, such that a config.json
* update later can enable it even when the original domain is unavailable.
*/
enabled: true
}
// merge the defaults with settings from the init var
let config = {...defaultConfig, ...init}
// reality check: redirectTo, redirectStatus, redirectStatusText need to be sane
// but only if the plugin is enabled
if (config.enabled) {
if ( typeof config.redirectTo != "string" ) {
let err = new Error("redirectTo should be a string")
console.error(err)
throw err
} else if (config.redirectTo.charAt(config.redirectTo.length - 1) != '/') {
// sanity reminder
LR.log(pluginName, 'warning: redirectTo does not end in "/", this might lead to unexpected results!')
}
if ( typeof config.redirectStatus != "number" ) {
let err = new Error("redirectStatus should be a number")
console.error(err)
throw err
}
if ( typeof config.redirectStatusText != "string" ) {
let err = new Error("redirectStatusText should be a string")
console.error(err)
throw err
}
}
/**
* getting content using regular HTTP(S) fetch()
*/
let redirectInsteadOfFetch = (url, init={}) => {
// TODO: check for obvious redirect loops
// 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 redirectTarget = config.redirectTo + url.replace(/https?:\/\/[^/]+\//, '')
// debug log
LR.log(pluginName, `redirecting:\n - from: ${url}\n - to: ${redirectTarget}\n - status: ${config.redirectStatus} ${config.redirectStatusText}`)
// we need to create a new Response object
// with all the headers added explicitly,
// since response.headers is immutable
var responseInit = {
status: config.redirectStatus,
statusText: config.redirectStatusText,
headers: {
Location: redirectTarget
},
url: url
};
// return a new Response object for this
return new Response(null, responseInit)
}
// return the plugin data structure
return {
name: pluginName,
description: 'HTTP Redirect based on a configured target location',
version: 'COMMIT_UNKNOWN',
fetch: redirectInsteadOfFetch
}
})
// done with not polluting the global namespace
})(LibResilientPluginConstructors)