kopia lustrzana https://gitlab.com/rysiekpl/libresilient
Subresource Integrity exposed to plugins from Service Worker
rodzic
0d5db7e2a0
commit
4f528a4123
|
@ -134,6 +134,62 @@ describe("plugin: alt-fetch", () => {
|
|||
expect(response.url).toEqual('https://resilient.is/test.json')
|
||||
})
|
||||
|
||||
test("it should pass the Request() init data to fetch() for all used endpoints", async () => {
|
||||
|
||||
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");
|
||||
|
||||
global.fetch.mockImplementation((url, init) => {
|
||||
const response = new Response(
|
||||
new Blob(
|
||||
[JSON.stringify({ test: "success" })],
|
||||
{type: "application/json"}
|
||||
),
|
||||
{
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
'ETag': 'TestingETagHeader'
|
||||
},
|
||||
url: url
|
||||
});
|
||||
return Promise.resolve(response);
|
||||
});
|
||||
|
||||
var initTest = {
|
||||
method: "GET",
|
||||
headers: new Headers({"x-stub": "STUB"}),
|
||||
mode: "mode-stub",
|
||||
credentials: "credentials-stub",
|
||||
cache: "cache-stub",
|
||||
referrer: "referrer-stub",
|
||||
// these are not implemented by service-worker-mock
|
||||
// https://github.com/zackargyle/service-workers/blob/master/packages/service-worker-mock/models/Request.js#L20
|
||||
redirect: undefined,
|
||||
integrity: undefined,
|
||||
cache: undefined
|
||||
}
|
||||
|
||||
const response = await LibResilientPluginConstructors.get('alt-fetch')(LR, init).fetch('https://resilient.is/test.json', initTest);
|
||||
|
||||
expect(fetch).toHaveBeenCalledTimes(3);
|
||||
expect(fetch).toHaveBeenNthCalledWith(1, expect.stringContaining('/test.json'), initTest);
|
||||
expect(fetch).toHaveBeenNthCalledWith(2, expect.stringContaining('/test.json'), initTest);
|
||||
expect(fetch).toHaveBeenNthCalledWith(3, expect.stringContaining('/test.json'), initTest);
|
||||
expect(await response.json()).toEqual({test: "success"})
|
||||
expect(response.url).toEqual('https://resilient.is/test.json')
|
||||
})
|
||||
|
||||
test("it should set the LibResilient headers", async () => {
|
||||
require("../../plugins/alt-fetch.js");
|
||||
|
||||
|
|
|
@ -93,6 +93,31 @@ describe("plugin: any-of", () => {
|
|||
expect(response.url).toEqual('https://resilient.is/test.json')
|
||||
});
|
||||
|
||||
test("it should return data from fetch()", async () => {
|
||||
require("../../plugins/any-of.js");
|
||||
|
||||
var initTest = {
|
||||
method: "GET",
|
||||
headers: new Headers({"x-stub": "STUB"}),
|
||||
mode: "mode-stub",
|
||||
credentials: "credentials-stub",
|
||||
cache: "cache-stub",
|
||||
referrer: "referrer-stub",
|
||||
// these are not implemented by service-worker-mock
|
||||
// https://github.com/zackargyle/service-workers/blob/master/packages/service-worker-mock/models/Request.js#L20
|
||||
redirect: undefined,
|
||||
integrity: undefined,
|
||||
cache: undefined
|
||||
}
|
||||
|
||||
const response = await LibResilientPluginConstructors.get('any-of')(LR, init).fetch('https://resilient.is/test.json', initTest);
|
||||
|
||||
expect(fetch).toHaveBeenCalled();
|
||||
expect(fetch).toHaveBeenCalledWith('https://resilient.is/test.json', initTest);
|
||||
expect(await response.json()).toEqual({test: "success"})
|
||||
expect(response.url).toEqual('https://resilient.is/test.json')
|
||||
});
|
||||
|
||||
test("it should throw an error when HTTP status is >= 400", async () => {
|
||||
|
||||
global.fetch.mockImplementation((url, init) => {
|
||||
|
|
|
@ -3,25 +3,25 @@ const makeServiceWorkerEnv = require('service-worker-mock');
|
|||
global.fetch = require('node-fetch');
|
||||
jest.mock('node-fetch')
|
||||
|
||||
global.fetch.mockImplementation((url, init) => {
|
||||
const response = new Response(
|
||||
new Blob(
|
||||
[JSON.stringify({ test: "success" })],
|
||||
{type: "application/json"}
|
||||
),
|
||||
{
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
'ETag': 'TestingETagHeader'
|
||||
},
|
||||
url: url
|
||||
});
|
||||
return Promise.resolve(response);
|
||||
});
|
||||
|
||||
describe("plugin: fetch", () => {
|
||||
beforeEach(() => {
|
||||
global.fetch.mockImplementation((url, init) => {
|
||||
const response = new Response(
|
||||
new Blob(
|
||||
[JSON.stringify({ test: "success" })],
|
||||
{type: "application/json"}
|
||||
),
|
||||
{
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
'ETag': 'TestingETagHeader'
|
||||
},
|
||||
url: url
|
||||
});
|
||||
return Promise.resolve(response);
|
||||
});
|
||||
|
||||
Object.assign(global, makeServiceWorkerEnv());
|
||||
jest.resetModules();
|
||||
global.LibResilientPluginConstructors = new Map()
|
||||
|
@ -31,6 +31,7 @@ describe("plugin: fetch", () => {
|
|||
}
|
||||
}
|
||||
})
|
||||
|
||||
test("it should register in LibResilientPluginConstructors", () => {
|
||||
require("../../plugins/fetch.js");
|
||||
expect(LibResilientPluginConstructors.get('fetch')().name).toEqual('fetch');
|
||||
|
@ -46,6 +47,30 @@ describe("plugin: fetch", () => {
|
|||
expect(response.url).toEqual('https://resilient.is/test.json')
|
||||
});
|
||||
|
||||
test("it should pass the Request() init data to fetch()", async () => {
|
||||
require("../../plugins/fetch.js");
|
||||
|
||||
var initTest = {
|
||||
method: "GET",
|
||||
headers: new Headers({"x-stub": "STUB"}),
|
||||
mode: "mode-stub",
|
||||
credentials: "credentials-stub",
|
||||
cache: "cache-stub",
|
||||
referrer: "referrer-stub",
|
||||
// these are not implemented by service-worker-mock
|
||||
// https://github.com/zackargyle/service-workers/blob/master/packages/service-worker-mock/models/Request.js#L20
|
||||
redirect: undefined,
|
||||
integrity: undefined,
|
||||
cache: undefined
|
||||
}
|
||||
|
||||
const response = await LibResilientPluginConstructors.get('fetch')(LR).fetch('https://resilient.is/test.json', initTest);
|
||||
|
||||
expect(fetch).toHaveBeenCalledWith('https://resilient.is/test.json', initTest);
|
||||
expect(await response.json()).toEqual({test: "success"})
|
||||
expect(response.url).toEqual('https://resilient.is/test.json')
|
||||
});
|
||||
|
||||
test("it should set the LibResilient headers", async () => {
|
||||
require("../../plugins/fetch.js");
|
||||
|
||||
|
|
|
@ -169,6 +169,76 @@ describe("service-worker", () => {
|
|||
expect(await response.json()).toEqual({ test: "success" })
|
||||
});
|
||||
|
||||
test("plugins should receive the Request() init data", async () => {
|
||||
self.LibResilientConfig = {
|
||||
plugins: [{
|
||||
name: 'reject-all'
|
||||
},{
|
||||
name: 'resolve-all'
|
||||
}],
|
||||
loggedComponents: [
|
||||
'service-worker'
|
||||
]
|
||||
}
|
||||
let rejectingFetch = jest.fn((request, init)=>{ return Promise.reject(request); })
|
||||
let resolvingFetch = jest.fn((request, init)=>{
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
new Blob(
|
||||
[JSON.stringify({ test: "success" })],
|
||||
{type: "application/json"}
|
||||
),
|
||||
{
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
'ETag': 'TestingETagHeader'
|
||||
},
|
||||
url: self.location.origin + '/test.json'
|
||||
})
|
||||
)
|
||||
})
|
||||
global.LibResilientPluginConstructors.set('reject-all', ()=>{
|
||||
return {
|
||||
name: 'reject-all',
|
||||
description: 'Reject all requests.',
|
||||
version: '0.0.1',
|
||||
fetch: rejectingFetch
|
||||
}
|
||||
})
|
||||
global.LibResilientPluginConstructors.set('resolve-all', ()=>{
|
||||
return {
|
||||
name: 'resolve-all',
|
||||
description: 'Resolve all requests.',
|
||||
version: '0.0.1',
|
||||
fetch: resolvingFetch
|
||||
}
|
||||
})
|
||||
|
||||
var initTest = {
|
||||
method: "GET",
|
||||
headers: new Headers({"x-stub": "STUB"}),
|
||||
mode: "mode-stub",
|
||||
credentials: "credentials-stub",
|
||||
cache: "cache-stub",
|
||||
referrer: "referrer-stub",
|
||||
// these are not implemented by service-worker-mock
|
||||
// https://github.com/zackargyle/service-workers/blob/master/packages/service-worker-mock/models/Request.js#L20
|
||||
redirect: undefined,
|
||||
integrity: undefined,
|
||||
cache: undefined
|
||||
}
|
||||
|
||||
require("../service-worker.js");
|
||||
|
||||
var response = await self.trigger('fetch', new Request('/test.json', initTest))
|
||||
expect(rejectingFetch).toHaveBeenCalled();
|
||||
expect(resolvingFetch).toHaveBeenCalled();
|
||||
expect(await response.json()).toEqual({ test: "success" })
|
||||
expect(rejectingFetch).toHaveBeenCalledWith('https://www.test.com/test.json', initTest)
|
||||
expect(resolvingFetch).toHaveBeenCalledWith('https://www.test.com/test.json', initTest)
|
||||
});
|
||||
|
||||
test("defaultPluginTimeout should be respected", async () => {
|
||||
jest.useFakeTimers()
|
||||
self.LibResilientConfig = {
|
||||
|
@ -575,6 +645,110 @@ describe("service-worker", () => {
|
|||
})).toEqual({ test: "success" })
|
||||
});
|
||||
|
||||
test("after a retrieval from a stashing plugin, background plugin should receive the Request() init data", async () => {
|
||||
self.LibResilientConfig = {
|
||||
plugins: [{
|
||||
name: 'stashing-test'
|
||||
},{
|
||||
name: 'resolve-all'
|
||||
}],
|
||||
loggedComponents: [
|
||||
'service-worker'
|
||||
]
|
||||
}
|
||||
let resolvingFetch = jest.fn((request, init)=>{
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
new Blob(
|
||||
[JSON.stringify({ test: "success" })],
|
||||
{type: "application/json"}
|
||||
),
|
||||
{
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
'X-LibResilient-Method': 'resolve-all',
|
||||
'X-LibResilient-ETag': 'TestingETagHeader'
|
||||
},
|
||||
url: self.location.origin + '/test.json'
|
||||
})
|
||||
)
|
||||
})
|
||||
let resolvingFetch2 = jest.fn((request, init)=>{
|
||||
return Promise.resolve(
|
||||
new Response(
|
||||
new Blob(
|
||||
[JSON.stringify({ test: "success2" })],
|
||||
{type: "application/json"}
|
||||
),
|
||||
{
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
'ETag': 'NewTestingETagHeader'
|
||||
},
|
||||
url: self.location.origin + '/test.json'
|
||||
})
|
||||
)
|
||||
})
|
||||
let stashingStash = jest.fn(async (response, url)=>{
|
||||
expect(await response.json()).toEqual({ test: "success2" })
|
||||
expect(response.headers.get('ETag')).toEqual('NewTestingETagHeader')
|
||||
})
|
||||
|
||||
global.LibResilientPluginConstructors.set('stashing-test', ()=>{
|
||||
return {
|
||||
name: 'stashing-test',
|
||||
description: 'Mock stashing plugin.',
|
||||
version: '0.0.1',
|
||||
fetch: resolvingFetch,
|
||||
stash: stashingStash
|
||||
}
|
||||
})
|
||||
global.LibResilientPluginConstructors.set('resolve-all', ()=>{
|
||||
return {
|
||||
name: 'resolve-all',
|
||||
description: 'Resolve all requests.',
|
||||
version: '0.0.1',
|
||||
fetch: resolvingFetch2
|
||||
}
|
||||
})
|
||||
|
||||
var testClient = new Client()
|
||||
self.clients.clients.push(testClient)
|
||||
var fetchedDiffersFound = false
|
||||
testClient.addEventListener('message', event => {
|
||||
if (event.data.fetchedDiffers) {
|
||||
fetchedDiffersFound = true
|
||||
}
|
||||
})
|
||||
|
||||
require("../service-worker.js");
|
||||
|
||||
var initTest = {
|
||||
method: "GET",
|
||||
headers: new Headers({"x-stub": "STUB"}),
|
||||
mode: "mode-stub",
|
||||
credentials: "credentials-stub",
|
||||
cache: "cache-stub",
|
||||
referrer: "referrer-stub",
|
||||
// these are not implemented by service-worker-mock
|
||||
// https://github.com/zackargyle/service-workers/blob/master/packages/service-worker-mock/models/Request.js#L20
|
||||
redirect: undefined,
|
||||
integrity: undefined,
|
||||
cache: undefined
|
||||
}
|
||||
|
||||
var response = await self.trigger('fetch', {
|
||||
request: new Request('/test.json', initTest),
|
||||
clientId: testClient.id
|
||||
})
|
||||
expect(resolvingFetch).toHaveBeenCalled();
|
||||
expect(await response.json()).toEqual({ test: "success" })
|
||||
expect(resolvingFetch).toHaveBeenCalledWith('https://www.test.com/test.json', initTest);
|
||||
expect(resolvingFetch2).toHaveBeenCalledWith('https://www.test.com/test.json', initTest);
|
||||
});
|
||||
|
||||
test("unstashing content explicitly should work", async () => {
|
||||
self.LibResilientConfig = {
|
||||
plugins: [{
|
||||
|
|
|
@ -62,12 +62,16 @@
|
|||
/**
|
||||
* getting content using regular HTTP(S) fetch()
|
||||
*/
|
||||
let fetchContentFromAlternativeEndpoints = (url) => {
|
||||
let fetchContentFromAlternativeEndpoints = (url, init={}) => {
|
||||
|
||||
// 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 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]
|
||||
|
||||
|
@ -91,7 +95,7 @@
|
|||
|
||||
return Promise.any(
|
||||
useEndpoints.map(
|
||||
u=>fetch(u, {cache: "reload"})
|
||||
u=>fetch(u, init)
|
||||
))
|
||||
.then((response) => {
|
||||
// 4xx? 5xx? that's a paddlin'
|
||||
|
|
|
@ -32,10 +32,10 @@
|
|||
/**
|
||||
* getting content using regular HTTP(S) fetch()
|
||||
*/
|
||||
let fetchContent = (url) => {
|
||||
let fetchContent = (url, init={}) => {
|
||||
LR.log(pluginName, `using: [${config.uses.map(p=>p.name).join(', ')}]!`)
|
||||
return Promise.any(
|
||||
config.uses.map(p=>p.fetch(url))
|
||||
config.uses.map(p=>p.fetch(url, init))
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
/**
|
||||
* getting content from cache
|
||||
*/
|
||||
let getContentFromCache = (url) => {
|
||||
let getContentFromCache = (url, init={}) => {
|
||||
LR.log(pluginName, `getting from cache: ${url}`)
|
||||
return caches.open('v1')
|
||||
.then((cache) => {
|
||||
|
|
|
@ -24,9 +24,13 @@
|
|||
/**
|
||||
* getting content using regular HTTP(S) fetch()
|
||||
*/
|
||||
let fetchContent = (url) => {
|
||||
let fetchContent = (url, init={}) => {
|
||||
LR.log(pluginName, `regular fetch: ${url}`)
|
||||
return fetch(url, {cache: "reload"})
|
||||
// we really want to make fetch happen, Regina!
|
||||
// TODO: this change should *probably* be handled on the Service Worker level
|
||||
init.cache = 'reload'
|
||||
// run built-in regular fetch()
|
||||
return fetch(url, init)
|
||||
.then((response) => {
|
||||
// 4xx? 5xx? that's a paddlin'
|
||||
if (response.status >= 400) {
|
||||
|
|
|
@ -165,7 +165,7 @@ if (typeof window === 'undefined') {
|
|||
/**
|
||||
* the workhorse of this plugin
|
||||
*/
|
||||
async function getContentFromGunAndIPFS(url) {
|
||||
async function getContentFromGunAndIPFS(url, init={}) {
|
||||
|
||||
await setup_gun_ipfs();
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@
|
|||
/**
|
||||
* the workhorse of this plugin
|
||||
*/
|
||||
async function getContentFromIPNSAndIPFS(url) {
|
||||
async function getContentFromIPNSAndIPFS(url, init={}) {
|
||||
return new Error("Not implemented yet.")
|
||||
|
||||
var urlArray = url.replace(/https?:\/\//, '').split('/')
|
||||
|
|
|
@ -398,15 +398,35 @@ let LibResilientResourceInfo = class {
|
|||
|* === Main Brain of LibResilient === *|
|
||||
\* ========================================================================= */
|
||||
|
||||
/**
|
||||
* generate Request()-compatible init object from an existing Request
|
||||
*
|
||||
* req - the request to work off of
|
||||
*/
|
||||
let initFromRequest = (req) => {
|
||||
return {
|
||||
method: req.method,
|
||||
headers: req.headers,
|
||||
mode: req.mode,
|
||||
credentials: req.credentials,
|
||||
cache: req.cache,
|
||||
redirect: req.redirect,
|
||||
referrer: req.referrer,
|
||||
integrity: req.integrity
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* run a plugin's fetch() method
|
||||
* while handling all the auxiliary stuff like saving info in reqInfo
|
||||
*
|
||||
* plugin - the plugin to use
|
||||
* url - string containing the URL to fetch
|
||||
* init - Request() initialization parameters
|
||||
* reqInfo - instance of LibResilientResourceInfo
|
||||
*/
|
||||
let libresilientFetch = (plugin, url, reqInfo) => {
|
||||
let libresilientFetch = (plugin, url, init, reqInfo) => {
|
||||
|
||||
// status of the plugin
|
||||
reqInfo.update({
|
||||
|
@ -416,11 +436,13 @@ let libresilientFetch = (plugin, url, reqInfo) => {
|
|||
|
||||
// log stuff
|
||||
self.log('service-worker', "LibResilient Service Worker handling URL:", url,
|
||||
'\n+-- using method(s):', plugin.name)
|
||||
'\n+-- init:', Object.getOwnPropertyNames(init).map(p=>`\n - ${p}: ${init[p]}`).join(''),
|
||||
'\n+-- using method(s):', plugin.name
|
||||
)
|
||||
|
||||
// race the plugin(s) vs. a timeout
|
||||
return Promise.race([
|
||||
plugin.fetch(url),
|
||||
plugin.fetch(url, init),
|
||||
promiseTimeout(
|
||||
self.LibResilientConfig.defaultPluginTimeout,
|
||||
false,
|
||||
|
@ -464,6 +486,9 @@ let getResourceThroughLibResilient = (request, clientId, useStashed=true, doStas
|
|||
// clean the URL, removing any fragment identifier
|
||||
var url = request.url.replace(/#.+$/, '');
|
||||
|
||||
// get the init object from Request
|
||||
var init = initFromRequest(request)
|
||||
|
||||
// set-up reqInfo for the fetch event
|
||||
var reqInfo = new LibResilientResourceInfo(url, clientId)
|
||||
|
||||
|
@ -503,14 +528,14 @@ let getResourceThroughLibResilient = (request, clientId, useStashed=true, doStas
|
|||
state: "error",
|
||||
fetchError: error.toString()
|
||||
})
|
||||
return libresilientFetch(currentPlugin, url, reqInfo)
|
||||
return libresilientFetch(currentPlugin, url, init, reqInfo)
|
||||
})
|
||||
},
|
||||
// this libresilientFetch() will run first
|
||||
// all other promises generated by LibResilientPlugins[] will be chained on it
|
||||
// using the catch() in reduce() above
|
||||
// skipping this very first plugin by way of slice(1)
|
||||
libresilientFetch(LibResilientPluginsRun[0], url, reqInfo)
|
||||
libresilientFetch(LibResilientPluginsRun[0], url, init, reqInfo)
|
||||
)
|
||||
.then((response)=>{
|
||||
// we got a successful response
|
||||
|
@ -532,6 +557,7 @@ let getResourceThroughLibResilient = (request, clientId, useStashed=true, doStas
|
|||
self.log('service-worker', 'starting background no-stashed fetch for:', url);
|
||||
// event.waitUntil?
|
||||
// https://stackoverflow.com/questions/37902441/what-does-event-waituntil-do-in-service-worker-and-why-is-it-needed/37906330#37906330
|
||||
// TODO: perhaps don't use the `request` again? some wrapper?
|
||||
getResourceThroughLibResilient(request, clientId, false, true, response.clone()).catch((e)=>{
|
||||
self.log('service-worker', 'background no-stashed fetch failed for:', url);
|
||||
})
|
||||
|
|
Ładowanie…
Reference in New Issue