diff --git a/bindings/matrix-sdk-crypto-js/Cargo.toml b/bindings/matrix-sdk-crypto-js/Cargo.toml index c17a6d9be..b446f5e53 100644 --- a/bindings/matrix-sdk-crypto-js/Cargo.toml +++ b/bindings/matrix-sdk-crypto-js/Cargo.toml @@ -15,6 +15,14 @@ publish = false [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] +[package.metadata.wasm-pack.profile.profiling] +wasm-opt = false + +[package.metadata.wasm-pack.profile.profiling.wasm-bindgen] +debug-js-glue = false +demangle-name-section = true +dwarf-debug-info = true + [package.metadata.wasm-pack.profile.release] wasm-opt = ['-Oz'] diff --git a/bindings/matrix-sdk-crypto-js/package.json b/bindings/matrix-sdk-crypto-js/package.json index 70c2a428a..2d8d0a524 100644 --- a/bindings/matrix-sdk-crypto-js/package.json +++ b/bindings/matrix-sdk-crypto-js/package.json @@ -37,12 +37,9 @@ "node": ">= 10" }, "scripts": { - "build": "cross-env RUSTFLAGS='-C opt-level=z' WASM_BINDGEN_WEAKREF=1 wasm-pack build --release --target nodejs --scope matrix-org --out-dir ./pkg", + "build": "./scripts/build.sh", "test": "jest --verbose", "doc": "typedoc --tsconfig .", - "prepack": "npm run build && npm run test", - "pack": "wasm-pack pack", - "prepublish": "npm run pack", - "publish": "wasm-pack publish" + "prepack": "npm run build && npm run test" } } diff --git a/bindings/matrix-sdk-crypto-js/scripts/build.sh b/bindings/matrix-sdk-crypto-js/scripts/build.sh new file mode 100755 index 000000000..f62d9ef1d --- /dev/null +++ b/bindings/matrix-sdk-crypto-js/scripts/build.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# +# Build the JavaScript modules +# +# This script is really a workaround for https://github.com/rustwasm/wasm-pack/issues/1074. +# +# Currently, the only reliable way to load WebAssembly in all the JS +# environments we want to target (web-via-webpack, web-via-browserify, jest) +# seems to be to pack the WASM into base64, and then unpack it and instantiate +# it at runtime. +# +# Hopefully one day, https://github.com/rustwasm/wasm-pack/issues/1074 will be +# fixed and this will be unnecessary. + +set -e + +cd $(dirname "$0")/.. + +RUSTFLAGS='-C opt-level=z' WASM_BINDGEN_WEAKREF=1 wasm-pack build --release --target nodejs --scope matrix-org --out-dir pkg + +# Convert the Wasm into a JS file that exports the base64'ed Wasm. +echo "module.exports = \`$(base64 pkg/matrix_sdk_crypto_js_bg.wasm)\`;" > pkg/matrix_sdk_crypto_js_bg.wasm.js + +# Copy in the unbase64 module +cp scripts/unbase64.js pkg/ + +# In the JavaScript: +# 1. Replace the lines that load the Wasm, +# 2. Remove the imports of `TextDecoder` and `TextEncoder`. We rely on the global defaults. +loadwasm='const bytes = require("./unbase64.js")(require("./matrix_sdk_crypto_js_bg.wasm.js"));' + +# sed on OSX uses different syntax for sed -i, so let's just avoid it. +sed -e "/^const path = /d" \ + -e "s@^const bytes =.*@${loadwasm}@" \ + -e '/Text..coder.*= require(.util.)/d' \ + pkg/matrix_sdk_crypto_js.js >pkg/matrix_sdk_crypto_js.js.new +mv pkg/matrix_sdk_crypto_js.js.new pkg/matrix_sdk_crypto_js.js diff --git a/bindings/matrix-sdk-crypto-js/scripts/unbase64.js b/bindings/matrix-sdk-crypto-js/scripts/unbase64.js new file mode 100644 index 000000000..fa82e2dde --- /dev/null +++ b/bindings/matrix-sdk-crypto-js/scripts/unbase64.js @@ -0,0 +1,32 @@ +// JavaScript module which exports a function which will un-base64 a string. +// +// Based on the code at https://developer.mozilla.org/en-US/docs/Glossary/Base64#solution_2_%E2%80%93_rewriting_atob_and_btoa_using_typedarrays_and_utf-8 + +const lookup = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 62, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]); + +module.exports = (sBase64) => { + const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, ""); + const nInLen = sB64Enc.length; + const nOutLen = (nInLen * 3 + 1) >> 2; + const taBytes = new Uint8Array(nOutLen); + + let nMod3; + let nMod4; + let nUint24 = 0; + let nOutIdx = 0; + for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) { + nMod4 = nInIdx & 3; + nUint24 |= lookup[sB64Enc.charCodeAt(nInIdx)] << (6 * (3 - nMod4)); + if (nMod4 === 3 || nInLen - nInIdx === 1) { + nMod3 = 0; + while (nMod3 < 3 && nOutIdx < nOutLen) { + taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255; + nMod3++; + nOutIdx++; + } + nUint24 = 0; + } + } + + return taBytes; +}; diff --git a/bindings/matrix-sdk-crypto-js/src/verification.rs b/bindings/matrix-sdk-crypto-js/src/verification.rs index f51a0b66f..811311e42 100644 --- a/bindings/matrix-sdk-crypto-js/src/verification.rs +++ b/bindings/matrix-sdk-crypto-js/src/verification.rs @@ -484,8 +484,13 @@ impl Qr { /// The `to_qr_code` method can be used to instead output a QrCode /// object that can be rendered. #[wasm_bindgen(js_name = "toBytes")] - pub fn to_bytes(&self) -> Result { - Ok(self.inner.to_bytes()?.into_iter().map(JsValue::from).collect()) + pub fn to_bytes(&self) -> Result { + let bytes = self.inner.to_bytes()?; + let output = Uint8ClampedArray::new_with_length(bytes.len() as _); + + output.copy_from(&bytes); + + Ok(output) } /// Notify the other side that we have successfully scanned the QR @@ -754,7 +759,7 @@ impl QrCodeScan { /// This method is useful if you would like to do your own custom QR code /// decoding. #[wasm_bindgen(js_name = "fromBytes")] - pub fn from_bytes(buffer: Uint8ClampedArray) -> Result { + pub fn from_bytes(buffer: &Uint8ClampedArray) -> Result { let bytes = buffer.to_vec(); Ok(Self { inner: matrix_sdk_qrcode::QrVerificationData::from_bytes(&bytes)? }) @@ -1029,9 +1034,9 @@ impl VerificationRequest { /// for this verification flow. #[cfg(feature = "qrcode")] #[wasm_bindgen(js_name = "scanQrCode")] - pub fn scan_qr_code(&self, data: QrCodeScan) -> Promise { + pub fn scan_qr_code(&self, data: &QrCodeScan) -> Promise { let me = self.inner.clone(); - let qr_verification_data = data.inner; + let qr_verification_data = data.inner.clone(); future_to_promise( async move { Ok(me.scan_qr_code(qr_verification_data).await?.map(Qr::from)) }, diff --git a/bindings/matrix-sdk-crypto-js/tests/device.test.js b/bindings/matrix-sdk-crypto-js/tests/device.test.js index 7f8d8fe6a..a3fa80742 100644 --- a/bindings/matrix-sdk-crypto-js/tests/device.test.js +++ b/bindings/matrix-sdk-crypto-js/tests/device.test.js @@ -707,16 +707,14 @@ describe('Key Verification', () => { expect(qr2.roomId).toBeUndefined(); }); - let qrCodeBytes; - test('can read QR code\'s bytes', async () => { const qrCodeHeader = 'MATRIX'; const qrCodeVersion = '\x02'; - qrCodeBytes = qr2.toBytes(); + const qrCodeBytes = qr2.toBytes(); expect(qrCodeBytes).toHaveLength(122); - expect(qrCodeBytes.slice(0, 7)).toStrictEqual([...qrCodeHeader, ...qrCodeVersion].map(char => char.charCodeAt(0))); + expect(Array.from(qrCodeBytes.slice(0, 7))).toEqual([...qrCodeHeader, ...qrCodeVersion].map(char => char.charCodeAt(0))); }); test('can render QR code', async () => { @@ -793,7 +791,7 @@ describe('Key Verification', () => { let qr1; test('can scan a QR code from bytes', async () => { - const scan = QrCodeScan.fromBytes(qrCodeBytes); + const scan = QrCodeScan.fromBytes(qr2.toBytes()); expect(scan).toBeInstanceOf(QrCodeScan);