...
 
Commits (15)
......@@ -4,6 +4,12 @@ A browser-based solution to Web censorship, implemented as a JavaScript library
Ideally, users should not need to install any special software nor change any settings to continue being able to access a blocked Samizdat-enabled site as soon as they are able to access it *once*.
## Curent status
Samizdat is currently considered *alpha*: the code works, but major rewrites and API changes are coming.
Feel free to test it, but be aware that it might not work as expected. If you'd like to get in touch, please e-mail us at `rysiek+samizdat[at]occrp.org`.
## Rationale
While a number of censorship circumvention technologies exist, these typically require those who want to access the blocked content (readers) to install specific tools (applications, browser extensions, VPN software, etc.), or change their settings (DNS servers, HTTP proxies, etc.). This approach does not scale.
......
......@@ -19,6 +19,14 @@ h1 {
text-align:center;
}
code {
background: #fff6;
padding: 0.2em 0.5em;
border-radius: 0.4em;
text-shadow: none;
font-weight: bold;
}
#subtitle {
font-style: italic;
font-variant: super;
......
......@@ -15,6 +15,7 @@
<script defer src="./lib/gun.js"></script>
<script defer src="./lib/sea.js"></script>
<script defer src="./lib/webrtc.js"></script>
<script defer src="./plugins/fetch.js"></script>
<script defer src="./plugins/cache.js"></script>
<script defer src="./plugins/gun-ipfs.js"></script>
<script>
......@@ -66,7 +67,7 @@
if (typeof fetchedResourcesDisplay !== 'object') {
fetchedResourcesDisplay = document.getElementById("fetched-resources-list")
}
var itemHTML = `<li class="fetched-resources-item"><label><input type="checkbox" checked="checked"/><span class="fetched-resource-url"><span>${si.url}</span></span><span class="fetched-resource-method fetch ${(si.method === 'fetch') ? 'active' : ''}">fetch</span>`
var itemHTML = `<li class="fetched-resources-item"><label><input type="checkbox" checked="checked"/><span class="fetched-resource-url"><span>${si.url}</span></span>`
SamizdatPlugins.forEach((plugin)=>{
var pclass = samizdat.safeClassName(plugin.name);
itemHTML += `<span class="fetched-resource-method ${pclass} ${(si.method === plugin.name) ? 'active' : ''}">${plugin.name}</span>`
......@@ -183,33 +184,35 @@
/**
* adding certain resources from cache
* stashing and unstashing resources
*
* stash param means "stash" if set to true (the default), "unstash" otherwise
*/
samizdat.addResourcesToCache = () => {
caches.open('v1')
.then((cache) => {
var resources = []
document.querySelectorAll('.fetched-resources-item input:checked')
.forEach((el)=>{
resources.push(el.parentElement.querySelector('.fetched-resource-url').innerText)
})
return SamizdatPlugins[0].push(resources)
})
}
/**
* removing certain resources from cache
*/
samizdat.clearResourcesFromCache = () => {
caches.open('v1')
.then((cache) => {
document.querySelectorAll('.fetched-resources-item input:checked')
.forEach((el)=>{
cache.delete(el.parentElement.querySelector('.fetched-resource-url').innerText)
})
})
samizdat.stashOrUnstashResources = (stash=true) => {
// what are we doing?
if (stash) {
var operation = 'stash'
} else {
var operation = 'unstash'
}
// get the resources
var resources = []
document
.querySelectorAll('.fetched-resources-item input:checked')
.forEach((el)=>{
resources.push(el.parentElement.querySelector('.fetched-resource-url').innerText)
})
// cycle through plugins and find the first that implements a stash() method
for (i=0; i<SamizdatPlugins.length; i++) {
if (typeof SamizdatPlugins[i][operation] === 'function') {
console.log('(COMMIT_UNKNOWN) Using plugin "' + SamizdatPlugins[i].name + '" to ' + operation + ' the resources...')
return SamizdatPlugins[i][operation](resources)
}
}
// if we're here that means there was no plugin able to stash things
return Promise.reject(new Error('No stashing plugin found'))
}
/**
* publishing certain resources to Gun+IPFS
......@@ -225,15 +228,11 @@
.forEach((el)=>{
resources.push(el.parentElement.querySelector('.fetched-resource-url').innerText)
})
return SamizdatPlugins[1].push(resources, user, pass)
return SamizdatPlugins[1].publish(resources, user, pass)
}
// add plugin status display
samizdat.addPluginStatus({
name: 'fetch',
description: 'Regular HTTP(S) fetch()'
})
SamizdatPlugins.forEach(samizdat.addPluginStatus)
// TODO: do it better, watch for ongoing requests or some such?
......@@ -260,12 +259,7 @@
)
// once we have all the data...
.then((val)=>{
// add plugin stats for the regular HTTP(S) fetch()
samizdat.updatePluginStatus({
name: 'fetch',
description: 'Regular HTTP(S) fetch()'
})
// and all other plugins
// add plugins' stats
SamizdatPlugins.forEach(samizdat.updatePluginStatus)
})
}, 5000)
......@@ -283,6 +277,7 @@
<p><em>Samizdat</em> is a browser-based Web censorship circumvention library, easily deployable on any website.</p>
<p>Implemented in JavaScript, It uses <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers">Service Workers</a> and a set of non-standard in-browser content delivery mechanisms (with strong focus on decentralized ones, like <a href="https://gun.eco/">Gun</a>, and <a href="https://github.com/ipfs/js-ipfs">JS-IPFS</a>).</p>
<p>Ideally, users would not need to install any special software nor change any settings to continue being able to access a blocked <em>Samizdat</em>-enabled site as soon as they are able to access it <em>once</em>.</p>
<p><em>Samizdat</em> is currently considered <code>alpha</code> software. We would love to hear if you'd like to test it &mdash; you can contact us on <code>rysiek+samizdat[at]occrp.org</code>.<p>
</div>
<div id="logo-container">
<div id="logo">
......@@ -299,7 +294,7 @@
<p>The list below contains all resources fetched in relation to this page.</p>
<ul id="fetched-resources-list"></ul>
<p id="fetched-resources-list-empty">The list is empty, but if the Service Worker is running it should be populated soon.</p>
<p id="fetched-resources-controls"><button type="button" onclick="samizdat.toggleResourceCheckboxes()">Toggle selection</button><span class="spacer"></span><button type="button" onclick="samizdat.addResourcesToCache()">Add selected to cache</button><button type="button" onclick="samizdat.clearResourcesFromCache()">Clear selected from cache</button><span class="spacer"></span><input type="text" placeholder="Gun username" id="samizdat-gun-user"/><input type="password" placeholder="Gun password" id="samizdat-gun-password"/><button type="button" onclick="samizdat.publishResourcesToGunAndIPFS()">Publish to Gun+IPFS</button></p>
<p id="fetched-resources-controls"><button type="button" onclick="samizdat.toggleResourceCheckboxes()">Toggle selection</button><span class="spacer"></span><button type="button" onclick="samizdat.stashOrUnstashResources(true)">Add selected to cache</button><button type="button" onclick="samizdat.stashOrUnstashResources(false)">Clear selected from cache</button><span class="spacer"></span><input type="text" placeholder="Gun username" id="samizdat-gun-user"/><input type="password" placeholder="Gun password" id="samizdat-gun-password"/><button type="button" onclick="samizdat.publishResourcesToGunAndIPFS()">Publish to Gun+IPFS</button></p>
</div>
<p id="footer">ServiceWorker: <span id="samizdat-commit-service-worker">NO_INFO</span>&nbsp;::&nbsp;index.html: <span id="samizdat-commit-index-html">COMMIT_UNKNOWN</span><br/>code: <span id="samizdat-code"><a href="https://git.occrp.org/libre/samizdat/">here</a></span>&nbsp;::&nbsp;license: <span id="samizdat-license"><a href="https://git.occrp.org/libre/samizdat/blob/master/LICENSE">AGPL</a></span></p>
</body>
......
/* ========================================================================= *\
|* === Stashing plugin using the Cache API === *|
\* ========================================================================= */
/**
* getting content from cache
*/
......@@ -17,9 +21,13 @@ let getContentFromCache = (url) => {
}
/**
* 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
* or an Array of string URLs
*/
let cacheContent = (resource, url) => {
return caches.open('v1')
......@@ -46,6 +54,40 @@ let cacheContent = (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)) {
......@@ -59,5 +101,6 @@ self.SamizdatPlugins.push({
description: 'Locally cached responses, using the Cache API.',
version: 'COMMIT_UNKNOWN',
fetch: getContentFromCache,
push: cacheContent
stash: cacheContent,
unstash: clearCachedContent
})
/* ========================================================================= *\
|* === Regular HTTP(S) fetch() plugin === *|
\* ========================================================================= */
/**
* 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)
.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);
return response;
})
}
// 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,
})
......@@ -421,5 +421,5 @@ self.SamizdatPlugins.push({
description: 'Decentralized resource fetching using Gun for address resolution and IPFS for content delivery.',
version: 'COMMIT_UNKNOWN',
fetch: getContentFromGunAndIPFS,
push: publishContent
publish: publishContent
})
This diff is collapsed.