Commit 0bf6c921 authored by Michał Woźniak's avatar Michał Woźniak
Browse files

Merge branch 'wip-config' into 'master'

Service Worker and plugin config in a separate config file

See merge request rysiek/samizdat!27
parents fddef86e d9f4f80b
/*
* Samizdat config
*
* This is the config for Samizdat as deployed on the https://samizdat.is/ site
*
* When deploying Samizdat on your website you will need to create your own config,
* using this one as a template.
*/
// plugins config
self.SamizdatConfig.plugins["gateway-ipns"] = {
// the pubkey of the preconfigured IPNS node
ipnsPubkey: 'QmYGVgGGfD5N4Xcc78CcMJ99dKcH6K6myhd4Uenv5yJwiJ'
}
self.SamizdatConfig.plugins["gun+ipfs"] = {
// the pubkey of the preconfigured Gun user
gunPubkey: 'WUK5ylwqqgUorceQRa84qfbBFhk7eNRDUoPbGK05SyE.-yohFhTzWPpDT-UDMuKGgemOUrw_cMMYWpy6plILqrg'
}
self.SamizdatConfig.plugins["ipns+ipfs"] = {
// the pubkey of the preconfigured Gun user
ipnsPubkey: 'QmYGVgGGfD5N4Xcc78CcMJ99dKcH6K6myhd4Uenv5yJwiJ'
}
......@@ -2,129 +2,139 @@
|* === Stashing plugin using the Cache API === *|
\* ========================================================================= */
/**
* getting content from cache
*/
let getContentFromCache = (url) => {
console.log('Samizdat: getting from cache!')
return caches.open('v1')
.then((cache) => {
return cache.match(url)
})
.then((response) => {
if (typeof response === 'undefined') {
throw new Error('Resource not found in cache');
} else {
response.headers.forEach(function(v, k){
console.log('+-- Retrieved cached header: ', k, ' :: ', v)
});
// return the response
return response
}
})
}
// no polluting of the global namespace please
(function () {
/**
* add resources to cache
*
* implements the stash() Samizdat plugin method
*
* accepts either a Response
* or a string containing a URL
* or an Array of string URLs
*/
let cacheContent = (resource, key) => {
return caches.open('v1')
.then((cache) => {
if (typeof resource === 'string') {
// assume URL
console.log("(COMMIT_UNKNOWN) caching an URL")
return cache.add(resource)
} else if (Array.isArray(resource)) {
// assume array of URLs
console.log("(COMMIT_UNKNOWN) caching an Array of URLs")
return cache.addAll(resource)
} else {
// assume a Response
// which means we either have a Request in key, a string URL in key,
// or we can use the URL in resource.url
if ( (typeof key !== 'object') && ( (typeof key !== 'string') || (key === '') ) ) {
if (typeof resource.url !== 'string' || resource.url === '') {
throw new Error('No URL to work with!')
/*
* this plugin has no config settings
*/
/**
* getting content from cache
*/
let getContentFromCache = (url) => {
console.log('Samizdat: getting from cache!')
return caches.open('v1')
.then((cache) => {
return cache.match(url)
})
.then((response) => {
if (typeof response === 'undefined') {
throw new Error('Resource not found in cache');
} else {
response.headers.forEach(function(v, k){
console.log('+-- Retrieved cached header: ', k, ' :: ', v)
});
// return the response
return response
}
})
}
/**
* add resources to cache
*
* implements the stash() Samizdat plugin method
*
* accepts either a Response
* or a string containing a URL
* or an Array of string URLs
*/
let cacheContent = (resource, key) => {
return caches.open('v1')
.then((cache) => {
if (typeof resource === 'string') {
// assume URL
console.log("(COMMIT_UNKNOWN) caching an URL")
return cache.add(resource)
} else if (Array.isArray(resource)) {
// assume array of URLs
console.log("(COMMIT_UNKNOWN) caching an Array of URLs")
return cache.addAll(resource)
} else {
// assume a Response
// which means we either have a Request in key, a string URL in key,
// or we can use the URL in resource.url
if ( (typeof key !== 'object') && ( (typeof key !== 'string') || (key === '') ) ) {
if (typeof resource.url !== 'string' || resource.url === '') {
throw new Error('No URL to work with!')
}
key = resource.url
}
key = resource.url
// we need to create a new Response object
// with all the headers added explicitly
// otherwise the x-samizdat-* headers get ignored
var init = {
status: resource.status,
statusText: resource.statusText,
headers: {}
};
resource.headers.forEach(function(val, header){
init.headers[header] = val;
});
return resource
.blob()
.then((blob) => {
console.log("(COMMIT_UNKNOWN) caching a Response to: " + key)
return cache.put(key, new Response(
blob,
init
))
})
}
// we need to create a new Response object
// with all the headers added explicitly
// otherwise the x-samizdat-* headers get ignored
var init = {
status: resource.status,
statusText: resource.statusText,
headers: {}
};
resource.headers.forEach(function(val, header){
init.headers[header] = val;
});
return resource
.blob()
.then((blob) => {
console.log("(COMMIT_UNKNOWN) caching a Response to: " + key)
return cache.put(key, new Response(
blob,
init
))
})
}
/**
* remove resources from cache
*
* implements the unstash() Samizdat plugin method
*
* accepts either a Response
* or a string containing a URL
* or an Array of string URLs
*/
let clearCachedContent = (resource) => {
return caches.open('v1')
.then((cache) => {
if (typeof resource === 'string') {
// assume URL
console.log("(COMMIT_UNKNOWN) deleting a cached URL")
return cache.delete(resource)
} else if (Array.isArray(resource)) {
// assume array of URLs
console.log("(COMMIT_UNKNOWN) deleting an Array of cached URLs")
return Promise.all(
resource.map((res)=>{
return cache.delete(res)
})
}
})
}
)
} else {
// assume a Response
// which means we have an URL in resource.url
console.log("(COMMIT_UNKNOWN) removing a Response from cache: " + resource.url)
return cache.delete(resource.url)
}
})
}
/**
* remove resources from cache
*
* implements the unstash() Samizdat plugin method
*
* accepts either a Response
* or a string containing a URL
* or an Array of string URLs
*/
let clearCachedContent = (resource) => {
return caches.open('v1')
.then((cache) => {
if (typeof resource === 'string') {
// assume URL
console.log("(COMMIT_UNKNOWN) deleting a cached URL")
return cache.delete(resource)
} else if (Array.isArray(resource)) {
// assume array of URLs
console.log("(COMMIT_UNKNOWN) deleting an Array of cached URLs")
return Promise.all(
resource.map((res)=>{
return cache.delete(res)
})
)
} else {
// assume a Response
// which means we have an URL in resource.url
console.log("(COMMIT_UNKNOWN) removing a Response from cache: " + resource.url)
return cache.delete(resource.url)
}
})
}
// initialize the SamizdatPlugins array
if (!Array.isArray(self.SamizdatPlugins)) {
self.SamizdatPlugins = new Array()
}
// initialize the SamizdatPlugins array
if (!Array.isArray(self.SamizdatPlugins)) {
self.SamizdatPlugins = new Array()
}
// and add ourselves to it
// with some additional metadata
self.SamizdatPlugins.push({
name: 'cache',
description: 'Locally cached responses, using the Cache API.',
version: 'COMMIT_UNKNOWN',
fetch: getContentFromCache,
stash: cacheContent,
unstash: clearCachedContent
})
// and add ourselves to it
// with some additional metadata
self.SamizdatPlugins.push({
name: 'cache',
description: 'Locally cached responses, using the Cache API.',
version: 'COMMIT_UNKNOWN',
fetch: getContentFromCache,
stash: cacheContent,
unstash: clearCachedContent
})
// done with not poluting the global namespace
})()
......@@ -6,59 +6,69 @@
* this plugin does not implement any push method
*/
/**
* getting content using regular HTTP(S) fetch()
*/
let fetchContent = (url) => {
console.log('Samizdat: regular fetch!')
return fetch(url, {cache: "reload"})
.then((response) => {
// 4xx? 5xx? that's a paddlin'
if (response.status >= 400) {
// throw an Error to fall back to Samizdat:
throw new Error('HTTP Error: ' + response.status + ' ' + response.statusText);
}
// all good, it seems
console.log("(COMMIT_UNKNOWN) 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: {}
};
response.headers.forEach(function(val, header){
init.headers[header] = val;
});
// add the X-Samizdat-* headers to the mix
init.headers['X-Samizdat-Method'] = 'fetch'
init.headers['X-Samizdat-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,
init
)
})
})
}
// initialize the SamizdatPlugins array
if (!Array.isArray(self.SamizdatPlugins)) {
self.SamizdatPlugins = new Array()
}
// no polluting of the global namespace please
(function () {
/*
* this plugin has no config settings
*/
/**
* getting content using regular HTTP(S) fetch()
*/
let fetchContent = (url) => {
console.log('Samizdat: regular fetch!')
return fetch(url, {cache: "reload"})
.then((response) => {
// 4xx? 5xx? that's a paddlin'
if (response.status >= 400) {
// throw an Error to fall back to Samizdat:
throw new Error('HTTP Error: ' + response.status + ' ' + response.statusText);
}
// all good, it seems
console.log("(COMMIT_UNKNOWN) 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: {}
};
response.headers.forEach(function(val, header){
init.headers[header] = val;
});
// add the X-Samizdat-* headers to the mix
init.headers['X-Samizdat-Method'] = 'fetch'
init.headers['X-Samizdat-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,
init
)
})
})
}
// initialize the SamizdatPlugins array
if (!Array.isArray(self.SamizdatPlugins)) {
self.SamizdatPlugins = new Array()
}
// and add ourselves to it
// with some additional metadata
self.SamizdatPlugins.push({
name: 'fetch',
description: 'Just a regular HTTP(S) fetch()',
version: 'COMMIT_UNKNOWN',
fetch: fetchContent
})
// and add ourselves to it
// with some additional metadata
self.SamizdatPlugins.push({
name: 'fetch',
description: 'Just a regular HTTP(S) fetch()',
version: 'COMMIT_UNKNOWN',
fetch: fetchContent
})
// done with not poluting the global namespace
})()
......@@ -6,129 +6,148 @@
* this plugin does not implement any push method
*/
// the pubkey of the preconfigured IPNS node
const ipnsPubKey = 'QmYGVgGGfD5N4Xcc78CcMJ99dKcH6K6myhd4Uenv5yJwiJ'
// no polluting of the global namespace please
(function () {
// this will become useful later
//const gatewaysJSONUrl = "https://ipfs.github.io/public-gateway-checker/gateways.json";
// important:
// we cannot use gateways that use hash directly in the (sub)domain:
// https://github.com/node-fetch/node-fetch/issues/260
const gateways = [
'https://ninetailed.ninja/ipns/', // Russia
'https://10.via0.com/ipns/', // USA
'https://ipfs.sloppyta.co/ipns/', // UK
'https://gateway.temporal.cloud/ipns/', // Germany
'https://ipfs.best-practice.se/ipns/' // Sweden
]
/*
* plugin config settings
*/
// sane defaults
let defaultConfig = {
// the pubkey of the preconfigured IPNS node; always needs to be set in config.js
ipnsPubkey: null,
// some default IPFS gateways to use
//
// important:
// we cannot use gateways that use hash directly in the (sub)domain:
// https://github.com/node-fetch/node-fetch/issues/260
ipfsGateways: [
'https://ninetailed.ninja/ipns/', // Russia
'https://10.via0.com/ipns/', // USA
'https://ipfs.sloppyta.co/ipns/', // UK
'https://gateway.temporal.cloud/ipns/', // Germany
'https://ipfs.best-practice.se/ipns/' // Sweden
]
}
// merge the defaults with settings from SamizdatConfig
let config = {...defaultConfig, ...self.SamizdatConfig.plugins["gateway-ipns"]}
/*
* to do this right we need a Promise.any() polyfill
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
*/
Promise.any = async (promises) => {
// Promise.all() is the polar opposite of Promise.any()
// in that it returns as soon as there is a first rejection
// but without it, it returns an array of resolved results
return Promise.all(
promises.map(p => {
return new Promise((resolve, reject) =>
// swap reject and resolve, so that we can use Promise.all()
// and get the result we need
Promise.resolve(p).then(reject, resolve)
);
})
// now, swap errors and values back
).then(
err => Promise.reject(err),
val => Promise.resolve(val)
);
};
// reality check: Gun pubkey needs to be set to a non-empty string
if (typeof(config.ipnsPubkey) !== "string" || config.ipnsPubkey === "") {
let err = new Error("ipnsPubkey not confgured")
console.error(err)
throw err
}
/*
* to do this right we need a Promise.any() polyfill
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
*/
Promise.any = async (promises) => {
// Promise.all() is the polar opposite of Promise.any()
// in that it returns as soon as there is a first rejection
// but without it, it returns an array of resolved results
return Promise.all(
promises.map(p => {
return new Promise((resolve, reject) =>
// swap reject and resolve, so that we can use Promise.all()
// and get the result we need
Promise.resolve(p).then(reject, resolve)
);
})
// now, swap errors and values back
).then(
err => Promise.reject(err),
val => Promise.resolve(val)
);
};
/**
* getting content using regular HTTP(S) fetch()
*/
let fetchContentFromGatewayIPNS = (url) => {
console.log(`Samizdat: pre-configured gateways:\n ${gateways.join('\n ')}`)
/**
* getting content using regular HTTP(S) fetch()
*/
let fetchContentFromGatewayIPNS = (url) => {
// we're going to try a random gateway and building an URL of the form:
// https://<gateway_address>/<ipnsPubkey>/<rest_of_URL>
var ipnsUrl = ipnsPubKey + url.replace(/https?:\/\/[^/]+/, '')
// pick 3 gateways, at random
var sourceGateways = [...gateways]
// if we have fewer than 3 gateways configured, use all of them
if (sourceGateways.length <= 3) {
var useGateways = sourceGateways
// otherwise get 3 at random
} else {
var useGateways = new Array()
while (useGateways.length < 3) {
// put in the address while we're at it
useGateways.push(
sourceGateways
.splice(Math.floor(Math.random() * sourceGateways.length), 1)[0] + ipnsUrl
)
// we're going to try a random gateway and building an URL of the form:
// https://<gateway_address>/<pubkey>/<rest_of_URL>
var ipnsUrl = config.ipnsPubkey + url.replace(/https?:\/\/[^/]+/, '')
// pick 3 gateways, at random
var sourceGateways = [...config.ipfsGateways]
// if we have fewer than 3 gateways configured, use all of them
if (sourceGateways.length <= 3) {
var useGateways = sourceGateways
// otherwise get 3 at random
} else {
var useGateways = new Array()
while (useGateways.length < 3) {
// put in the address while we're at it
useGateways.push(
sourceGateways
.splice(Math.floor(Math.random() * sourceGateways.length), 1)[0] + ipnsUrl
)
}
}
// debug log
console.log(`Samizdat: gateway IPNS fetching:\n ${useGateways.join('\n ')}`)
return Promise.any(
useGateways.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
console.log("(COMMIT_UNKNOWN) 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: {}
};
response.headers.forEach(function(val, header){
init.headers[header] = val;
});
// add the X-Samizdat-* headers to the mix
init.headers['X-Samizdat-Method'] = 'gateway-ipns'
init.headers['X-Samizdat-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,
init
)
})
})
}
// debug log
console.log(`Samizdat: gateway IPNS fetching:\n ${useGateways.join('\n ')}`)
return Promise.any(
useGateways.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
console.log("(COMMIT_UNKNOWN) Fetched:", response.url);
// we need to create a new Response object