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);
}