WebWorker for SHA-1 and SHA-256 calculations using WebCrypto API
Also includes call to cryto outside worker on UI Thread Does not work in Edge
by mgoetzke
HTML
<label><input type="checkbox" id="repeat">Repeat 10x</label>
<br>
<label><input type="checkbox" id="uiThread" checked>Run on UI Thread</label>
<label><input type="checkbox" id="workerThread" checked>Run on Worker Thread</label>
<br>
<input type="file" id="file">
<br>
<button id="run">Run</button>
<script type="text/ww">
self.onmessage = async (e) => {
const data = e.data
if (!data.id) {
return
}
if (data.file) {
const result = await calc(data.method, data.file, data.repeat)
self.postMessage({ id: data.id, type: data.method, result })
}
}
function calc(method, file, repeat) {
const methods = { sha1: f=>hash(f, 'SHA-1'), sha256: f=>hash(f, 'SHA-256') }
const fn = methods[method]
if (!fn) {
throw new Error('Unsupported method ' + method)
}
/*
* 10 times for load test
*/
if (repeat) {
for (let i=0; i<10; i++) {
fn(file)
}
}
return fn(file)
}
async function hash(data, method) {
const buffer = await readAsBuffer(data)
const hashBuffer = await crypto.subtle.digest(method, buffer)
const hashArray = Array.from(new Uint8Array(hashBuffer))
const hashHex = hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('')
return hashHex;
}
function readAsBuffer(data) {
if (typeof data === 'string') {
return new TextEncoder('utf-8').encode(message)
} else {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsArrayBuffer(data)
reader.onload = () => {
if (reader.readyState === 2) {
if (reader.error) {
reject(reader.error)
} else {
resolve(reader.result)
}
}
}
})
}
}
async function sha256(file) {
const msgBuffer = new TextEncoder('utf-8').encode(message); // encode as UTF-8
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); // hash the message
const hashArray = Array.from(new...
CSS
body::after {
content: '';
position:absolute;
width:20px;
height:20px;
background:red;
top:30px;
right:30px;
animation: rotation 1s infinite linear;
}
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
JavaScript
class UiWorker {
constructor(code) {
this.workerInstance = { postMessage: (result)=>this.onmessage({data:result}) }
const workerCode = new Function('self',code)
workerCode(this.workerInstance)
}
postMessage(message) {
this.workerInstance.onmessage({ data: message })
}
}
try {
const ww = document.querySelector('script[type="text/ww"]')
const code = ww.textContent
const blob = new Blob([code], {type: 'text/javascript'}),
blobUrl = URL.createObjectURL(blob),
worker = new Worker(blobUrl);
worker.onmessage = function(e) {onResult('Worker', e)};
var uiWorker = new UiWorker(code)
uiWorker.onmessage = function(e) { onResult('UI Worker', e)}
function onResult(source, e) {
var _time = (Date.now() - _start) / 1000;
console.log(_time, e, e.data);
var log = document.createElement('p');
log.innerHTML = source + ' roundtrip took ' + _time + ' sec. Response: <pre>' + e.data.type + '=>' + e.data.result + '</pre>';
document.body.appendChild(log);
}
const file = document.getElementById('file')
file.onchange = run
const btn = document.getElementById('run')
btn.onclick = run
function run(){
const uiThread = document.getElementById('uiThread').checked
const workerThread = document.getElementById('workerThread').checked
const repeat = document.getElementById('repeat').checked
_start = Date.now();
const files = file.files
const theFile = files[0]
if (file) {
if (workerThread) {
worker.postMessage({ method:'sha1', id:1, file:theFile, repeat })
}
if (uiThread) {
uiWorker.postMessage({ method:'sha1', id:1, file:theFile, repeat })
}
// worker.postMessage({ method:'sha256', id:1, file:theFile })
}
};
}
catch (ex) {
alert(ex);
}