Skip to content
This repository has been archived by the owner on Aug 5, 2020. It is now read-only.

Commit

Permalink
Fix checksum calculation in Edge
Browse files Browse the repository at this point in the history
Checksum calculations were failing in Edge because the Rust WebAssembly
wrapper depends on `TextDecoder`, which is not supported by the
current version of Edge (even though WebAssembly itself is supported):

https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/TextDecoder

This commit fixes the problem by trying to load the WASM module, and
falling back to checksums calculated in JavaScript if the module fails to
load.

This uses exceptions for control flow, but there's no obvious property to
test to be confident that WASM will work, since it may depend on modules
other than `TextDecoder` in the future.

A possible alternative would be to polyfill `TextDecoder`, as described
here: golang/go#27295 (comment)

The polyfill is quite complicated, though, so the JS checksum fallback
seems like a better solution, at least for now. We can revisit it if there
are major performance problems.
  • Loading branch information
suzannehamilton committed Oct 25, 2019
1 parent 0cdfc61 commit 755e080
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 47 deletions.
2 changes: 0 additions & 2 deletions js-src/bootstrap.ts

This file was deleted.

39 changes: 37 additions & 2 deletions js-src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,45 @@
import { updateFileStatuses } from "./fileStatus";
import { upload } from "./upload";
import {ChecksumCalculator, upload} from "./upload";

const wasmSupported = (() => {
try {
if (
typeof WebAssembly === "object" &&
typeof WebAssembly.instantiate === "function"
) {
const module = new WebAssembly.Module(
Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)
);
if (module instanceof WebAssembly.Module)
return (
new WebAssembly.Instance(module) instanceof
WebAssembly.Instance
);
}
} catch (e) {}
return false;
})();

window.onload = function() {
if (wasmSupported) {
// @ts-ignore
import("@nationalarchives/checksum-calculator")
.then(checksumModule => {
renderModules(checksumModule);
})
.catch(e => {
console.error("Error importing checksum module:", e);
renderModules()
});
} else {
renderModules();
}
};

const renderModules = (checksumCalculator?: ChecksumCalculator) => {
const uploadContainer: HTMLDivElement | null = document.querySelector(".upload-form");
if (uploadContainer) {
upload();
upload(checksumCalculator);
}
const fileStatusContainer: HTMLDivElement | null = document.querySelector(".file-status");
if (fileStatusContainer) {
Expand Down
71 changes: 29 additions & 42 deletions js-src/upload/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import Axios from "axios";
import * as S3 from "aws-sdk/clients/s3";
import * as wasm from "@nationalarchives/checksum-calculator";
import { generateHash } from "./checksum";

export interface ChecksumCalculator {
generate_checksum(blob: any): any;
}

interface HTMLInputTarget extends EventTarget {
files?: InputElement;
}
Expand Down Expand Up @@ -36,26 +39,7 @@ export interface IWebkitEntry extends DataTransferItem {
file: (success: (file: File) => void) => void;
}

const wasmSupported = (() => {
try {
if (
typeof WebAssembly === "object" &&
typeof WebAssembly.instantiate === "function"
) {
const module = new WebAssembly.Module(
Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)
);
if (module instanceof WebAssembly.Module)
return (
new WebAssembly.Instance(module) instanceof
WebAssembly.Instance
);
}
} catch (e) {}
return false;
})();

const upload: () => void = () => {
const upload: (checksumCalculator?: ChecksumCalculator) => void = (checksumCalculator) => {
const uploadForm: HTMLFormElement | null = document.querySelector(
"#file-upload-form"
);
Expand All @@ -68,7 +52,7 @@ const upload: () => void = () => {
ev.preventDefault();
const target: HTMLInputTarget | null = ev.currentTarget;
const files: TdrFile[] = target!.files!.files!;
processFiles(files)
processFiles(files, checksumCalculator)
.then(() => {
if (commenceUploadForm) {
commenceUploadForm.submit();
Expand All @@ -87,9 +71,25 @@ const upload: () => void = () => {
});
});
}

const onDrop: (e: DragEvent) => void = async e => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
const dataTransferItems: DataTransferItemList = e.dataTransfer!.items;

//Assume one folder in the drag and drop for now
const files: TdrFile[] = await getAllFiles(
dataTransferItems[0].webkitGetAsEntry(),
[]
);
processFiles(files, checksumCalculator);
};

const dragAndDrop: HTMLDivElement | null = document.querySelector(
".govuk-file-drop"
);

if (dragAndDrop) {
dragAndDrop.ondragover = onDragOver;
dragAndDrop.ondragleave = () => setIsDragging(false);
Expand Down Expand Up @@ -168,26 +168,13 @@ const getAllFiles: (
return fileInfoInput;
};

const onDrop: (e: DragEvent) => void = async e => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
const dataTransferItems: DataTransferItemList = e.dataTransfer!.items;

//Assume one folder in the drag and drop for now
const files: TdrFile[] = await getAllFiles(
dataTransferItems[0].webkitGetAsEntry(),
[]
);
processFiles(files);
};

const getFileInfo: (
tdrFile: TdrFile
) => Promise<CreateFileInput> = async tdrFile => {
tdrFile: TdrFile,
checksumCalculator?: ChecksumCalculator
) => Promise<CreateFileInput> = async (tdrFile, checksumCalculator) => {
let clientSideChecksum;
if (wasmSupported) {
clientSideChecksum = await wasm.generate_checksum(tdrFile);
if (checksumCalculator) {
clientSideChecksum = await checksumCalculator.generate_checksum(tdrFile);
} else {
clientSideChecksum = await generateHash(tdrFile);
}
Expand Down Expand Up @@ -248,7 +235,7 @@ function uploadFileData(batches: CreateFileInput[][], consignmentId: number) {
return Promise.all(responses);
}

async function processFiles(files: TdrFile[]) {
async function processFiles(files: TdrFile[], checksumCalculator?: ChecksumCalculator) {
const fileInfoList: CreateFileInput[] = [];
const urlParams: URLSearchParams = new URLSearchParams(
window.location.search
Expand All @@ -266,7 +253,7 @@ async function processFiles(files: TdrFile[]) {
console.log(`Got file info for ${fileInfoCount} files`);
}

const fileInfo: CreateFileInput = await getFileInfo(tdrFile);
const fileInfo: CreateFileInput = await getFileInfo(tdrFile, checksumCalculator);

fileInfoList.push(fileInfo);
filePathToFile[fileInfo.path!] = tdrFile;
Expand Down
2 changes: 1 addition & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const path = require('path');
const webpack = require('webpack');

module.exports = {
entry: './js-src/bootstrap.ts',
entry: './js-src/index.ts',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'public/javascripts'),
Expand Down

0 comments on commit 755e080

Please sign in to comment.