Penumbra
Fetch and decrypt files in the browser using whatwg streams and web workers.
Quickly and efficiently decrypt remote resources in the browser. Display the files in the DOM, or download them with conflux.
Usage
Importing Penumbra
With NPM
npm install --save @transcend-io/penumbra
import * as penumbra from '@transcend-io/penumbra'
penumbra.get(...files).then(penumbra.save);
Vanilla JS
<script src="lib/penumbra.js"></script>
<script>
penumbra.get(...files).then(penumbra.getTextOrURI).then(displayInDOM);
</script>
Check out this guide for asynchornous loading.
.get
Fetch and decrypt remote files.
penumbra.get(...resources: RemoteResource[]): Promise<PenumbraFile[]>
.save
Save files retrieved by Penumbra. Downloads a .zip if there are multiple files.
penumbra.save(data: PenumbraFile[], fileName?: string): Promise<void>
.getBlob
Load files retrieved by Penumbra into memory as a Blob.
penumbra.getBlob(data: PenumbraFile[] | PenumbraFile | ReadableStream, type?: string): Promise<Blob>
.getTextOrURI
Get file text (if content is text) or URI (if content is not viewable).
penumbra.getTextOrURI(data: PenumbraFile[] | PenumbraFile): Promise<{ type: 'text'|'uri', data: string, mimetype: string }>
.zip
Zip files retrieved by Penumbra.
penumbra.zip(data: PenumbraFile[] | PenumbraFile, compressionLevel?: number): Promise<ReadableStream>
.setWorkerLocation
Configure the location of Penumbra's worker threads.
penumbra.setWorkerLocation(location: WorkerLocationOptions | string): Promise<void>
Examples
Display encrypted text
const decryptedText = await penumbra
.get({
url: 'https://s3-us-west-2.amazonaws.com/bencmbrook/NYT.txt.enc',
mimetype: 'text/plain',
filePrefix: 'NYT',
decryptionOptions: {
key: 'vScyqmJKqGl73mJkuwm/zPBQk0wct9eQ5wPE8laGcWM=',
iv: '6lNU+2vxJw6SFgse',
authTag: 'gadZhS1QozjEmfmHLblzbg==',
},
})
.then((file) => penumbra.getTextOrURI(file));
document.getElementById('my-paragraph').innerText = decryptedText;
Display encrypted image
const imageSrc = await penumbra
.get({
url: 'https://s3-us-west-2.amazonaws.com/bencmbrook/tortoise.jpg.enc',
filePrefix: 'tortoise',
mimetype: 'image/jpeg',
decryptionOptions: {
key: 'vScyqmJKqGl73mJkuwm/zPBQk0wct9eQ5wPE8laGcWM=',
iv: '6lNU+2vxJw6SFgse',
authTag: 'ELry8dZ3djg8BRB+7TyXZA==',
},
})
.then((file) => penumbra.getTextOrURI(file));
document.getElementById('my-img').src = imageSrc;
Download an encrypted file
penumbra
.get({
url: 'https://s3-us-west-2.amazonaws.com/bencmbrook/africa.topo.json.enc',
filePrefix: 'africa',
mimetype: 'image/jpeg',
decryptionOptions: {
key: 'vScyqmJKqGl73mJkuwm/zPBQk0wct9eQ5wPE8laGcWM=',
iv: '6lNU+2vxJw6SFgse',
authTag: 'ELry8dZ3djg8BRB+7TyXZA==',
},
})
.then((file) => penumbra.save(file));
Download many encrypted files
penumbra
.get([
{
url: 'https://s3-us-west-2.amazonaws.com/bencmbrook/africa.topo.json.enc',
filePrefix: 'africa',
mimetype: 'image/jpeg',
decryptionOptions: {
key: 'vScyqmJKqGl73mJkuwm/zPBQk0wct9eQ5wPE8laGcWM=',
iv: '6lNU+2vxJw6SFgse',
authTag: 'ELry8dZ3djg8BRB+7TyXZA==',
},
},
{
url: 'https://s3-us-west-2.amazonaws.com/bencmbrook/NYT.txt.enc',
mimetype: 'text/plain',
filePrefix: 'NYT',
decryptionOptions: {
key: 'vScyqmJKqGl73mJkuwm/zPBQk0wct9eQ5wPE8laGcWM=',
iv: '6lNU+2vxJw6SFgse',
authTag: 'gadZhS1QozjEmfmHLblzbg==',
},
},
{
url: 'https://s3-us-west-2.amazonaws.com/bencmbrook/tortoise.jpg',
filePrefix: 'tortoise',
mimetype: 'image/jpeg',
},
])
.then((files) => penumbra.save({ data: files, fileName: 'example' }));
Advanced
Prepare connections for file downloads in advance
const resources = [
{
url: 'https://s3-us-west-2.amazonaws.com/bencmbrook/NYT.txt.enc',
filePrefix: 'NYT',
mimetype: 'text/plain',
decryptionOptions: {
key: 'vScyqmJKqGl73mJkuwm/zPBQk0wct9eQ5wPE8laGcWM=',
iv: '6lNU+2vxJw6SFgse',
authTag: 'gadZhS1QozjEmfmHLblzbg==',
},
},
{
url: 'https://s3-us-west-2.amazonaws.com/bencmbrook/tortoise.jpg.enc',
filePrefix: 'tortoise',
mimetype: 'image/jpeg',
decryptionOptions: {
key: 'vScyqmJKqGl73mJkuwm/zPBQk0wct9eQ5wPE8laGcWM=',
iv: '6lNU+2vxJw6SFgse',
authTag: 'ELry8dZ3djg8BRB+7TyXZA==',
},
},
];
penumbra.preconnect(...resources);
penumbra.preload(...resources);
Download Progress Event Emitter
You can listen to download progress events by listening to the penumbra-progress
event.
window.addEventListener(
'penumbra-progress',
({ detail: { percent, url, type } }) => {
console.log(`${type}% ${percent}% done for ${url}`);
},
);
Note: this feature requires the Content-Length
response header to be exposed. This works by adding Access-Control-Expose-Headers: Content-Length
to the response header (read more here and here)
On Amazon S3, this means adding the following line to your bucket policy, inside the <CORSRule>
block:
<ExposeHeader>Content-Length</ExposeHeader>
Configure worker location
penumbra.setWorkerLocation('/penumbra-workers/');
penumbra.setWorkerLocation({
base: '/penumbra-workers/',
decrypt: 'decrypt.js'
zip: 'zip-debug.js'
StreamSaver: 'StreamSaver.js'
});
penumbra.setWorkerLocation({decrypt: 'penumbra.decrypt.js'});
Waiting for the penumbra-ready
event
<script src="lib/penumbra.js" async defer></script>
const onReady = async ({ detail: { penumbra } } = { detail: self }) => {
await penumbra.get(...files).then(penumbra.save);
};
if (!self.penumbra) {
self.addEventListener('penumbra-ready', onReady);
} else {
onReady();
}
Contributing
npm install
npm run build
npm run test:interactive
Big Thanks
Cross-browser Testing Platform and Open Source <3 Provided by Sauce Labs