Commit a225c460 authored by YJ's avatar YJ Committed by Thibaut Sardan
Browse files

Fix Signing Substrate Payload, Better UX for Errors (#340)

* fix: network key lookup, alert when check unexpected failures

* fix: bundle

* fix: signing a hash payload

* fix: finalize for review

* fix: bump rust deps

* chore: bump and relink

* chore: bump deps

* fix: make it sign
parent cbfbd328
Pipeline #50581 failed with stage
in 14 seconds
......@@ -5,7 +5,6 @@
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; };
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; };
......@@ -35,6 +34,7 @@
391AD3F022F9D848005136A0 /* libRNCNetInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 39D36A0622F4588C00EDA4F1 /* libRNCNetInfo.a */; };
3972BABE230D88B200202B52 /* libRNSecureStorage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3972BA68230C4CBE00202B52 /* libRNSecureStorage.a */; };
399D1B0E22DDDE1B00A815EB /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 399D1B0D22DDDE1B00A815EB /* JavaScriptCore.framework */; };
39B6F6432315B550009C3C05 /* main.jsbundle in Resources */ = {isa = PBXBuildFile; fileRef = 39B6F6102315B550009C3C05 /* main.jsbundle */; };
39D32408FC4A464384BA5A5C /* SimpleLineIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E8E0FEBC36F54D78A81C1639 /* SimpleLineIcons.ttf */; };
39D36A1222F458FC00EDA4F1 /* libRNGestureHandler.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 39D36A0F22F458DE00EDA4F1 /* libRNGestureHandler.a */; };
3C31DDCB4CD0465084344D5F /* Manifold-CF-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 624E6C0FF4A64A97A7D51BEF /* Manifold-CF-Bold.otf */; };
......@@ -473,6 +473,7 @@
31AAF2CB51C04377BFC79634 /* Manifold-CF-Light.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Manifold-CF-Light.otf"; path = "../res/fonts/Manifold-CF-Light.otf"; sourceTree = "<group>"; };
31EA7650B3084F7080008B3C /* RNSVG.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNSVG.xcodeproj; path = "../node_modules/react-native-svg/ios/RNSVG.xcodeproj"; sourceTree = "<group>"; };
399D1B0D22DDDE1B00A815EB /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
39B6F6102315B550009C3C05 /* main.jsbundle */ = {isa = PBXFileReference; lastKnownFileType = text; path = main.jsbundle; sourceTree = "<group>"; };
39D36A0922F458DD00EDA4F1 /* RNGestureHandler.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNGestureHandler.xcodeproj; path = "../node_modules/react-native-gesture-handler/ios/RNGestureHandler.xcodeproj"; sourceTree = "<group>"; };
39D36A6022F4A07200EDA4F1 /* RNCamera.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNCamera.xcodeproj; path = "../node_modules/react-native-camera/ios/RNCamera.xcodeproj"; sourceTree = "<group>"; };
3DB51A3702D94B6BA025DDE6 /* FontAwesome5_Brands.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Brands.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf"; sourceTree = "<group>"; };
......@@ -851,6 +852,7 @@
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
39B6F6102315B550009C3C05 /* main.jsbundle */,
13B07FAE1A68108700A75B9A /* NativeSigner */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
00E356EF1AD99517003FC87E /* NativeSignerTests */,
......@@ -1424,6 +1426,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
39B6F6432315B550009C3C05 /* main.jsbundle in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
0A5DA78AED2F468C8025AF7C /* Roboto-Black.ttf in Resources */,
......@@ -1797,4 +1800,4 @@
/* End XCConfigurationList section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
}
\ No newline at end of file
}
This diff is collapsed.
......@@ -9,6 +9,7 @@
},
"scripts": {
"android": "yarn run build-rust-android && react-native run-android --appIdSuffix \"dev\"",
"build-prod:ios": "NODE_OPTIONS=--max_old_space_size=8192 react-native bundle --dev false --entry-file index.js --bundle-output ios/main.jsbundle --platform ios",
"build-rust-ios": "cd rust/signer && make ios",
"build-rust-android": "cd rust/signer && make android",
"clean": "watchman watch-del-all && rm -rf /tmp/metro-bundler-cache-* && rm -rf /tmp/haste-map-react-native-packager-* && rm -rf node_modules/ && yarn cache clean --force",
......@@ -20,32 +21,32 @@
"test": "jest"
},
"dependencies": {
"@plugnet/util": "^0.94.100",
"@plugnet/util": "^1.1.100",
"@plugnet/wasm-crypto-js": "^0.11.102",
"@polkadot/api": "^0.90.0-beta.66",
"@polkadot/reactnative-identicon": "^0.42.0-beta.26",
"@polkadot/util": "^1.0.1",
"@polkadot/util-crypto": "1.1.0-beta.1",
"@polkadot/api": "^0.91.0-beta.10",
"@polkadot/reactnative-identicon": "^0.42.1",
"@polkadot/util": "^1.1.1",
"@polkadot/util-crypto": "1.1.1",
"@polkadot/wasm-crypto": "^0.13.1",
"@react-native-community/netinfo": "^4.1.1",
"@react-native-community/netinfo": "^4.1.5",
"@tradle/react-native-http": "^2.0.1",
"bignumber.js": "^9.0.0",
"hoist-non-react-statics": "^3.3.0",
"node-libs-react-native": "^1.0.3",
"prop-types": "^15.6.1",
"react": "^16.9.0",
"react-native": "0.60.4",
"react-native-camera": "^3.0.0",
"react-native-gesture-handler": "^1.3.0",
"react-native-keyboard-aware-scroll-view": "^0.8.0",
"react-native": "0.60.5",
"react-native-camera": "^3.3.0",
"react-native-gesture-handler": "^1.4.1",
"react-native-keyboard-aware-scroll-view": "^0.9.1",
"react-native-markdown-renderer": "^3.2.8",
"react-native-popup-menu": "^0.15.6",
"react-native-randombytes": "^3.5.3",
"react-native-secure-storage": "git+https://github.com/paritytech/react-native-secure-storage.git",
"react-native-svg": "^9.5.3",
"react-native-svg": "^9.7.1",
"react-native-tabs": "^1.0.9",
"react-native-vector-icons": "^6.6.0",
"react-navigation": "^3.11.1",
"react-navigation": "^3.12.1",
"readable-stream": "^3.4.0",
"unstated": "^2.1.1",
"vm-browserify": "1.1.0"
......@@ -57,17 +58,17 @@
"@babel/plugin-transform-runtime": "^7.5.0",
"@babel/runtime": "^7.5.4",
"@react-native-community/eslint-config": "^0.0.5",
"babel-eslint": "^10.0.1",
"babel-jest": "^24.8.0",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0",
"babel-plugin-rewrite-require": "^1.14.5",
"eslint": "^6.0.1",
"jest": "^24.8.0",
"jetifier": "^1.6.3",
"metro-react-native-babel-preset": "^0.55.0",
"eslint": "^6.2.2",
"jest": "^24.9.0",
"jetifier": "^1.6.4",
"metro-react-native-babel-preset": "^0.56.0",
"pre-git": "^3.14.0",
"prettier": "1.18.2",
"react-test-renderer": "16.8.6",
"reactotron-react-native": "^3.6.4"
"react-test-renderer": "16.9.0",
"reactotron-react-native": "^3.6.5"
},
"release": {
"analyzeCommits": "simple-commit-message"
......
This diff is collapsed.
......@@ -33,12 +33,12 @@ export default class PayloadDetailsCard extends React.PureComponent {
render() {
const { description, payload, signature, style } = this.props;
return (
<View style={[styles.body, style]}>
<Text style={styles.titleText}>{description}</Text>
{
payload && (
!!payload && (
<View style={{ padding: 5, paddingVertical: 2 }}>
<ExtrinsicPart label='Block Hash' value={payload.blockHash.toString()} />
<ExtrinsicPart label='Method' value={payload.method.toString()} />
......@@ -50,7 +50,7 @@ export default class PayloadDetailsCard extends React.PureComponent {
)
}
{
signature && (
!!signature && (
<View style={{ padding: 5, paddingVertical: 2 }}>
<Text style={styles.label}>Signature</Text>
<Text style={styles.secondaryText}>{signature}</Text>
......@@ -62,13 +62,11 @@ export default class PayloadDetailsCard extends React.PureComponent {
}
}
function ExtrinsicPart({ label, style, value }) {
function ExtrinsicPart({ label, value }) {
return (
<View style={[{ justifyContent: 'center', alignItems: 'flex-start' }, style]}>
<View
style={{ padding: 5, paddingVertical: 2 }}
>
<View style={[{ justifyContent: 'center', alignItems: 'flex-start' }]}>
<View style={{ padding: 5, paddingVertical: 2 }}>
<Text style={styles.label}>
{label}
</Text>
......
......@@ -22,14 +22,13 @@ import { StyleSheet, Text, View } from 'react-native';
import { NavigationActions, StackActions } from 'react-navigation';
import { Subscribe } from 'unstated';
import colors from '../colors';
import fonts from "../fonts";
import fonts from '../fonts';
import Background from '../components/Background';
import TextInput from '../components/TextInput';
import AccountsStore from '../stores/AccountsStore';
import ScannerStore from '../stores/ScannerStore';
export class AccountUnlockAndSign extends React.PureComponent {
render() {
const { navigation } = this.props;
const next = navigation.getParam('next', 'SignedTx');
......
......@@ -16,6 +16,7 @@
'use strict';
import { isU8a, u8aToHex } from '@polkadot/util';
import PropTypes from 'prop-types';
import React from 'react';
import { Alert, ScrollView, StyleSheet, Text } from 'react-native';
......@@ -39,6 +40,7 @@ export default class MessageDetails extends React.PureComponent {
<Subscribe to={[ScannerStore, AccountsStore]}>
{(scannerStore, accounts) => {
const dataToSign = scannerStore.getDataToSign();
const message = scannerStore.getMessage();
if (dataToSign) {
return (
......@@ -46,9 +48,9 @@ export default class MessageDetails extends React.PureComponent {
{...this.props}
scannerStore={scannerStore}
sender={scannerStore.getSender()}
message={scannerStore.getMessage()}
dataToSign={dataToSign}
isOversized={scannerStore.getIsOversized()}
message={isU8a(message) ? u8aToHex(message) : message}
dataToSign={isU8a(dataToSign) ? u8aToHex(dataToSign) : dataToSign}
isHash={scannerStore.getIsHash()}
onPressAccount={async account => {
await accounts.select(account);
this.props.navigation.navigate('AccountDetails');
......@@ -77,13 +79,13 @@ export class MessageDetailsView extends React.PureComponent {
static propTypes = {
onNext: PropTypes.func.isRequired,
dataToSign: PropTypes.string.isRequired,
isOversized: PropTypes.bool.isRequired,
isHash: PropTypes.bool.isRequired,
sender: PropTypes.object.isRequired,
message: PropTypes.string.isRequired
};
render() {
const {data, isOversized, message, onNext, onPressAccount, sender} = this.props;
const {dataToSign, isHash, message, onNext, onPressAccount, sender} = this.props;
return (
<ScrollView
......@@ -105,13 +107,13 @@ export class MessageDetailsView extends React.PureComponent {
<Text style={styles.message}>
{isAscii(message)
? message
: data}
: dataToSign}
</Text>
<Button
buttonStyles={{ height: 60 }}
title="Sign Message"
onPress={() => {
isOversized
isHash
? Alert.alert(
"Warning",
"The payload of the transaction you are signing is too big to be decoded. Not seeing what you are signing is inherently unsafe. If possible, contact the developer of the application generating the transaction to ask for multipart support.",
......
......@@ -65,31 +65,32 @@ export default class Scanner extends React.PureComponent {
return;
}
if(isAddressString(txRequestData.data)){
return this.showErrorMessage(scannerStore, text.ADDRESS_ERROR_TITLE, text.ADDRESS_ERROR_MESSAGE);
} else if (isJsonString(txRequestData.data)) {
// Ethereum Legacy
await scannerStore.setUnsigned(txRequestData.data);
} else {
try {
try {
if(isAddressString(txRequestData.data)) {
return this.showErrorMessage(scannerStore, text.ADDRESS_ERROR_TITLE, text.ADDRESS_ERROR_MESSAGE);
} else if (isJsonString(txRequestData.data)) {
// Ethereum Legacy
await scannerStore.setUnsigned(txRequestData.data);
} else {
const strippedData = rawDataToU8A(txRequestData.rawData);
await scannerStore.setParsedData(
strippedData,
accountsStore
);
} catch (e) {
return this.showErrorMessage(scannerStore, text.PARSE_ERROR_TITLE, e.message);
}
}
if (await scannerStore.setData(accountsStore)) {
if (scannerStore.getType() === 'transaction') {
this.props.navigation.navigate('TxDetails');
if (scannerStore.getUnsigned()) {
await scannerStore.setData(accountsStore);
if (scannerStore.getType() === 'transaction') {
this.props.navigation.navigate('TxDetails');
} else {
this.props.navigation.navigate('MessageDetails');
}
} else {
this.props.navigation.navigate('MessageDetails');
return;
}
} else {
return;
} catch (e) {
return this.showErrorMessage(scannerStore, text.PARSE_ERROR_TITLE, e.message);
}
}}
/>
......
......@@ -25,7 +25,7 @@ import fonts from "../fonts";
import QrView from '../components/QrView';
import AccountsStore from '../stores/AccountsStore';
import ScannerStore from '../stores/ScannerStore';
import { hexToAscii, isAscii } from '../util/message';
import { isAscii } from '../util/message';
export default class SignedMessage extends React.PureComponent {
render() {
......@@ -62,7 +62,7 @@ export class SignedMessageView extends React.PureComponent {
<Text style={styles.message}>
{isAscii(message)
? message
: hexToAscii(data)}
: data}
</Text>
</ScrollView>
);
......
......@@ -55,14 +55,13 @@ export class SignedTxView extends React.PureComponent {
data: PropTypes.string.isRequired,
gas: PropTypes.string,
gasPrice: PropTypes.string,
nonce: PropTypes.string,
recipient: PropTypes.object,
sender: PropTypes.object,
value: PropTypes.string,
};
render() {
const { data, gas, gasPrice, nonce, recipient, sender, value } = this.props;
const { data, gas, gasPrice, recipient, sender, value } = this.props;
return (
<ScrollView style={styles.body} contentContainerStyle={{ padding: 20 }}>
......
......@@ -23,7 +23,6 @@ import { loadAccounts, saveAccount } from '../util/db';
import {parseSURI} from '../util/suri'
import { decryptData, encryptData } from '../util/native';
export type Account = {
address: string,
archived: boolean,
......@@ -71,6 +70,7 @@ export default class AccountsStore extends Container {
updateNew(accountUpdate) {
this.setState({ newAccount : {...this.state.newAccount, ...accountUpdate} })
console.log(this.state.newAccount);
}
getNew() {
......
......@@ -15,8 +15,8 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
// @flow
import { createType } from '@polkadot/types';
import { GenericExtrinsicPayload } from '@polkadot/types';
import { hexStripPrefix, isU8a, u8aToHex } from '@polkadot/util';
import { Container } from 'unstated';
import { NETWORK_LIST, NetworkProtocols } from '../constants';
......@@ -66,7 +66,7 @@ const defaultState = {
tx: '',
txRequest: null,
type: null,
unsignedData: {}
unsignedData: null
};
export default class ScannerStore extends Container<ScannerState> {
......@@ -177,14 +177,14 @@ export default class ScannerStore extends Container<ScannerState> {
}
const tx = isEthereum ? await transaction(txRequest.data.rlp) : txRequest.data.data;
const networkKey = isEthereum ? tx.ethereumChainId : '456';
const networkKey = isEthereum ? tx.ethereumChainId : txRequest.data.data.genesisHash.toHex();
const sender = accountsStore.getById({
protocol,
networkKey,
address: txRequest.data.account
});
const networkTitle = NETWORK_LIST[networkKey].title;
if (!sender || !sender.encryptedSeed) {
......@@ -227,8 +227,19 @@ export default class ScannerStore extends Container<ScannerState> {
if (isEthereum) {
signedData = await brainWalletSign(seed, this.state.dataToSign);
} else {
signedData = await substrateSign(seed, isAscii(this.state.dataToSign) ? asciiToHex(this.state.dataToSign) : this.state.dataToSign.toHex());
let signable;
if (this.state.dataToSign instanceof GenericExtrinsicPayload) {
signable = hexStripPrefix(this.state.dataToSign.toHex());
} else if (isU8a(this.state.dataToSign)) {
signable = hexStripPrefix(u8aToHex(this.state.dataToSign));
} else if (isAscii(this.state.dataToSign)) {
signable = hexStripPrefix(asciiToHex(this.state.dataToSign));
}
signedData = await substrateSign(seed, signable);
}
this.setState({ signedData });
......@@ -239,7 +250,7 @@ export default class ScannerStore extends Container<ScannerState> {
tx: this.state.tx,
sender,
recipient: this.state.recipient,
signature: signedData,
signature: this.state.signedData,
createdAt: new Date().getTime()
});
}
......@@ -269,6 +280,10 @@ export default class ScannerStore extends Container<ScannerState> {
this.setState(defaultState);
}
getIsHash() {
return this.state.isHash;
}
getIsOversized() {
return this.state.isOversized;
}
......@@ -289,6 +304,10 @@ export default class ScannerStore extends Container<ScannerState> {
return this.state.message;
}
getUnsigned() {
return this.state.unsignedData;
}
getTx() {
return this.state.tx;
}
......
......@@ -4,14 +4,16 @@ export function accountId({
address,
networkKey
}) {
console.log('address => ', address);
console.log('netwokr list -> ', NETWORK_LIST);
const { ethereumChainId, protocol, genesisHash } = NETWORK_LIST[networkKey];
if (typeof address !== 'string' || address.length === 0 || !networkKey) {
throw new Error(`Couldn't create an accountId, address or networkKey missing`);
if (typeof address !== 'string' || address.length === 0 || !networkKey || !NETWORK_LIST[networkKey]) {
throw new Error(`Couldn't create an accountId. Address or networkKey missing, or network key was invalid.`);
}
if (protocol === NetworkProtocols.SUBSTRATE){
const { ethereumChainId='', protocol, genesisHash } = NETWORK_LIST[networkKey];
if (protocol === NetworkProtocols.SUBSTRATE) {
return `${protocol}:${address}:${genesisHash}`;
} else {
return `${protocol}:0x${address.toLowerCase()}@${ethereumChainId}`;
......
......@@ -54,7 +54,7 @@ function txKey(hash) {
export const deleteAccount = async account =>
SecureStorage.deleteItem(accountId(account), accountsStore);
export const saveAccount = account =>
export const saveAccount = account =>
SecureStorage.setItem(
accountId(account),
JSON.stringify(account, null, 0),
......
......@@ -141,50 +141,54 @@ export async function constructDataFromBytes(bytes) {
}
break;
case '53': // Substrate UOS payload
const crypto = firstByte === '00' ? 'ed25519' : firstByte === '01' ? 'sr25519' : null;
const pubKeyHex = uosAfterFrames.substr(6, 64)
const publicKeyAsBytes = hexToU8a('0x' + pubKeyHex);
const ss58Encoded = encodeAddress(publicKeyAsBytes, 2); // encode to kusama
const hexEncodedData = '0x' + uosAfterFrames.slice(70);
const rawPayload = hexToU8a(hexEncodedData);
const isOversized = rawPayload.length > 256;
data['data']['crypto'] = crypto;
data['data']['account'] = ss58Encoded;
switch (secondByte) {
case '00':
data['action'] = 'signTransaction';
data['oversized'] = isOversized;
data['isHash'] = isOversized;
data['data']['data'] = isOversized
? await blake2s(u8aToHex(rawPayload))
: new GenericExtrinsicPayload(rawPayload, { version: 3 });
break;
case '01':
data['action'] = 'signTransaction';
data['oversized'] = false;
data['isHash'] = true;
data['data']['data'] = rawPayload; // data is a hash
break;
case '02': // immortal
data['action'] = 'signTransaction';
data['oversized'] = isOversized;
data['isHash'] = isOversized;
data['data']['data'] = isOversized
? await blake2s(u8aToHex(rawPayload))
: new GenericExtrinsicPayload(rawPayload, { version: 3 });
break;
case '03': // Cold Signer should attempt to decode message to utf8
data['action'] = 'signData';
data['oversized'] = isOversized;
data['isHash'] = isOversized;
data['data']['data'] = isOversized
? await blake2s(u8aToHex(rawPayload))
: u8aToString(rawPayload);
break;
default:
break;
try {
const crypto = firstByte === '00' ? 'ed25519' : firstByte === '01' ? 'sr25519' : null;
const pubKeyHex = uosAfterFrames.substr(6, 64)
const publicKeyAsBytes = hexToU8a('0x' + pubKeyHex);
const ss58Encoded = encodeAddress(publicKeyAsBytes, 2); // encode to kusama
const hexEncodedData = '0x' + uosAfterFrames.slice(70);
const rawPayload = hexToU8a(hexEncodedData);
const isOversized = rawPayload.length > 256;
data['data']['crypto'] = crypto;
data['data']['account'] = ss58Encoded;
switch (secondByte) {
case '00': // sign mortal extrinsic
data['action'] = isOversized ? 'signData' : 'signTransaction';
data['oversized'] = isOversized;
data['isHash'] = isOversized;
data['data']['data'] = isOversized
? await blake2s(u8aToHex(rawPayload))
: new GenericExtrinsicPayload(rawPayload, { version: 3 });
break;
case '01': // data is a hash
data['action'] = 'signData';
data['oversized'] = false;
data['isHash'] = true;
data['data']['data'] = rawPayload;
break;
case '02': // immortal
data['action'] = isOversized ? 'signData' : 'signTransaction';
data['oversized'] = isOversized;
data['isHash'] = isOversized;
data['data']['data'] = isOversized
? await blake2s(u8aToHex(rawPayload))
: new GenericExtrinsicPayload(rawPayload, { version: 3 });
break;
case '03': // Cold Signer should attempt to decode message to utf8
data['action'] = 'signData';
data['oversized'] = isOversized;
data['isHash'] = isOversized;
data['data']['data'] = isOversized
? await blake2s(u8aToHex(rawPayload))
: u8aToString(rawPayload);
break;
default:
break;
}
} catch (e) {
throw new Error('we cannot handle the payload: ', bytes);
}
break;
default:
......@@ -197,13 +201,13 @@ export async function constructDataFromBytes(bytes) {
}
}
export function decodeToString(message: Uint8Array): string {
export function decodeToString(message) {
const decoder = new TextDecoder('utf8');
return decoder.decode(message);