Compare commits

...

8 Commits

Author SHA1 Message Date
Mark Sinclair 317806005a Add rollup config to output CommonJS bundle 2023-01-17 16:32:05 +00:00
Mark Sinclair 5723078413 wasm-client: add method to validate a recipient's address 2023-01-17 14:34:16 +00:00
Mark Sinclair 9777da0964 Update SDK docs and images 2023-01-17 11:47:59 +00:00
Mark Sinclair bee8638572 WASM client: fix tests 2023-01-17 11:33:59 +00:00
Mark Sinclair be7504b036 Add Parcel 2.0 example 2023-01-17 10:39:43 +00:00
Mark Sinclair cf342108c3 Suppress errors in build script 2023-01-17 10:39:43 +00:00
Mark Sinclair 69f5a0d6ba Use rollup to bundle the web worker script to support more downstream bundlers
The WASM bundle is embedded as a base64 encoded resource and loaded synchronously, because this is the only mechanism widely supported to load WASM inside a web worker currently. Hopefully in the future this can be changed to pure modules.
2023-01-17 10:39:43 +00:00
Mark Sinclair 548dfed713 WASM Client: simplify sending of custom messages by always setting headers and a mime-type for the content 2023-01-17 10:39:43 +00:00
34 changed files with 5094 additions and 440 deletions
+1
View File
@@ -41,3 +41,4 @@ storybook-static
envs/qwerty.env
Cargo.lock
nym-connect/Cargo.lock
.parcel-cache
+2
View File
@@ -22,6 +22,8 @@ futures = "0.3"
js-sys = "0.3"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
anyhow = "1.0"
serde-wasm-bindgen = "0.4"
tokio = { version = "1.24.1", features = ["sync"] }
url = "2.2"
+9 -8
View File
@@ -16,11 +16,7 @@ They should be implemented soon. You can build your applications, but don't rely
## Using it
See the [Nym docs](https://nymtech.net/docs).
### Demo
There's a demo web application in the `js-example` folder. To run it, first make sure you've got a recent `npm` installed, then follow the instructions in its README.
See the [SDK directory](../../sdk/typescript/examples) for examples on how to use it and the NPM packages available.
## Developing
@@ -36,8 +32,13 @@ To be clear, this is not something that most JS developers need to worry about,
### Packaging
If you're a Nym platform developer who's made changes to the Rust (or JS) files and wants to re-publish the package to NPM, here's how you do it:
If you're a Nym platform developer who's made changes to the Rust files and wants to re-publish the package to NPM, here's how you do it:
1. bump version numbers as necessary for SemVer
2. `wasm-pack build --scope nymproject` builds the wasm binaries into the `pkg` directory (not in source control)
3. `cd pkg && npm publish --access=public` will publish your changed package to NPM
2. go the `sdk/typescript` directory (off the project root)
3. run:
```
yarn
yarn build
yarn publish
```
@@ -1,217 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
#[wasm_bindgen(typescript_custom_section)]
const TS_DEFS: &'static str = r#"
export interface BinaryMessage {
kind: number,
payload: Uint8Array;
headers: string,
}
export interface StringMessage {
kind: number,
payload: string;
}
"#;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = "BinaryMessage")]
pub type IBinaryMessage;
#[wasm_bindgen(typescript_type = "StringMessage")]
pub type IStringMessage;
}
#[derive(Serialize, Deserialize)]
pub struct BinaryMessage {
pub kind: u8,
pub payload: Vec<u8>,
pub headers: String,
}
#[derive(Serialize, Deserialize)]
pub struct StringMessage {
pub kind: u8,
pub payload: String,
}
/// Create a new binary message with a user-specified `kind`.
#[wasm_bindgen]
pub fn create_binary_message(kind: u8, payload: Vec<u8>) -> Vec<u8> {
create_binary_message_with_headers(kind, payload, "".to_string())
}
/// Create a new message with a UTF-8 encoded string `payload` and a user-specified `kind`.
#[wasm_bindgen]
pub fn create_binary_message_from_string(kind: u8, payload: String) -> Vec<u8> {
create_binary_message_with_headers(kind, payload.as_bytes().to_vec(), "".to_string())
}
/// Create a new binary message with a user-specified `kind`, and `headers` as a string.
#[wasm_bindgen]
pub fn create_binary_message_with_headers(kind: u8, payload: Vec<u8>, headers: String) -> Vec<u8> {
let headers = headers.as_bytes().to_vec();
let size = (headers.len() as u64).to_be_bytes().to_vec();
vec![vec![kind], size, headers, payload].concat()
}
/// Parse the `kind` and byte array `payload` from a byte array
#[wasm_bindgen]
pub async fn parse_binary_message(message: Vec<u8>) -> Result<IBinaryMessage, JsError> {
if message.len() < 2 {
return Err(JsError::new(
"Could not parse message, as less than 2 bytes long",
));
}
let (kind, _headers, payload) = parse_binary_payload(&message);
Ok(serde_wasm_bindgen::to_value(&BinaryMessage {
kind,
payload: payload.to_vec(),
headers: "".to_string(),
})
.unwrap()
.unchecked_into::<IBinaryMessage>())
}
/// Parse the `kind` and byte array `payload` from a byte array with headers
#[wasm_bindgen]
pub async fn parse_binary_message_with_headers(
message: Vec<u8>,
) -> Result<IBinaryMessage, JsError> {
if message.len() < 2 {
return Err(JsError::new(
"Could not parse message, as less than 2 bytes long",
));
}
let (kind, headers, payload) = parse_binary_payload(&message);
Ok(serde_wasm_bindgen::to_value(&BinaryMessage {
kind,
payload: payload.to_vec(),
headers,
})
.unwrap()
.unchecked_into::<IBinaryMessage>())
}
/// Parse the `kind` and UTF-8 string `payload` from a byte array with headers
#[wasm_bindgen]
pub async fn parse_string_message_with_headers(
message: Vec<u8>,
) -> Result<IStringMessage, JsError> {
if message.len() < 2 {
return Err(JsError::new(
"Could not parse message, as less than 2 bytes long",
));
}
let (kind, _headers, payload) = parse_binary_payload(&message);
let payload = String::from_utf8_lossy(payload).into_owned();
Ok(
serde_wasm_bindgen::to_value(&StringMessage { kind, payload })
.unwrap()
.unchecked_into::<IStringMessage>(),
)
}
pub(crate) fn parse_binary_payload(message: &[u8]) -> (u8, String, &[u8]) {
// 1st byte is the kind
let kind = message[0];
// then the size as u64 big endian
let mut size = [0u8; 8];
size.clone_from_slice(&message[1..9]);
let size = u64::from_be_bytes(size) as usize;
// then the headers
let headers = String::from_utf8_lossy(&message[9..9 + size]).into_owned();
// finally the payload
let payload = &message[9 + size..];
(kind, headers, payload)
}
/// Parse the `kind` and UTF-8 string `payload` from a byte array
#[wasm_bindgen]
pub async fn parse_string_message(message: Vec<u8>) -> Result<IStringMessage, JsError> {
if message.len() < 2 {
return Err(JsError::new(
"Could not parse message, as less than 2 bytes long",
));
}
let kind = message[0];
let payload = String::from_utf8_lossy(&message[1..]).into_owned();
Ok(
serde_wasm_bindgen::to_value(&StringMessage { kind, payload })
.unwrap()
.unchecked_into::<IStringMessage>(),
)
}
#[cfg(test)]
mod tests {
use super::{create_binary_message_with_headers, parse_binary_payload};
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn test_binary_with_headers() {
let message_as_bytes = create_binary_message_with_headers(
42u8,
vec![0u8, 1u8, 2u8],
"test headers".to_string(),
);
// calculate header size
let headers = "test headers".as_bytes().to_vec();
let size = headers.len();
// the expected size
let expected_size = 12;
assert_eq!(size, expected_size);
assert_eq!(message_as_bytes[0], 42u8);
assert_eq!(message_as_bytes[1..9], 12u64.to_be_bytes());
assert_eq!(
message_as_bytes[9 + expected_size..9 + expected_size + 3],
vec![0u8, 1u8, 2u8]
);
let res = parse_binary_payload(&message_as_bytes);
assert_eq!(res.0, 42u8);
assert_eq!(res.1, "test headers".to_string());
assert_eq!(res.2, vec![0u8, 1u8, 2u8]);
}
#[wasm_bindgen_test]
fn test_binary_with_empty_headers() {
let message_as_bytes =
create_binary_message_with_headers(42u8, vec![0u8, 1u8, 2u8], "".to_string());
let expected_size = 0;
assert_eq!(message_as_bytes[0], 42u8);
assert_eq!(message_as_bytes[1..9], 0u64.to_be_bytes());
assert_eq!(
message_as_bytes[9 + expected_size..9 + expected_size + 3],
vec![0u8, 1u8, 2u8]
);
let res = parse_binary_payload(&message_as_bytes);
assert_eq!(res.0, 42u8);
assert_eq!(res.1, "".to_string());
assert_eq!(res.2, vec![0u8, 1u8, 2u8]);
}
}
@@ -0,0 +1,238 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
#[wasm_bindgen(typescript_custom_section)]
const TS_DEFS: &'static str = r#"
export interface EncodedPayload {
mimeType: string,
payload: Uint8Array;
headers: string,
}
"#;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = "EncodedPayload")]
pub type IEncodedPayload;
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EncodedPayload {
pub mime_type: String,
pub payload: Vec<u8>,
pub headers: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EncodedPayloadMetadata {
pub mime_type: String,
pub headers: Option<String>,
}
/// Encode a payload
#[wasm_bindgen]
pub fn encode_payload(mime_type: String, payload: Vec<u8>) -> Result<Vec<u8>, JsValue> {
encode_payload_with_headers(mime_type, payload, None)
}
/// Create a new binary message with a user-specified `kind`, and `headers` as a string.
#[wasm_bindgen]
pub fn encode_payload_with_headers(
mime_type: String,
payload: Vec<u8>,
headers: Option<String>,
) -> Result<Vec<u8>, JsValue> {
match serde_json::to_string(&EncodedPayloadMetadata { mime_type, headers }) {
Ok(metadata) => {
let metadata = metadata.as_bytes().to_vec();
let size = (metadata.len() as u64).to_be_bytes().to_vec();
Ok(vec![size, metadata, payload].concat())
}
Err(e) => Err(JsValue::from(JsError::new(
format!("Could not encode message: {}", e).as_str(),
))),
}
}
/// Parse the `kind` and byte array `payload` from a byte array
#[wasm_bindgen]
pub fn decode_payload(message: Vec<u8>) -> Result<IEncodedPayload, JsValue> {
if message.len() < 8 {
return Err(JsValue::from(JsError::new(
"Could not parse message, as less than 8 bytes long",
)));
}
match parse_payload(&message) {
Ok((metadata, payload)) => Ok(serde_wasm_bindgen::to_value(&EncodedPayload {
mime_type: metadata.mime_type,
payload: payload.to_vec(),
headers: metadata.headers,
})
.unwrap()
.unchecked_into::<IEncodedPayload>()),
Err(e) => Err(JsValue::from(JsError::new(
format!("Could not parse message: {}", e).as_str(),
))),
}
}
pub(crate) fn parse_payload(message: &[u8]) -> anyhow::Result<(EncodedPayloadMetadata, &[u8])> {
// 1st 8 bytes are the size (as u64 big endian)
let mut size = [0u8; 8];
size.clone_from_slice(&message[0..8]);
let size = u64::from_be_bytes(size) as usize;
// then the metadata
let metadata = String::from_utf8_lossy(&message[8..8 + size]).into_owned();
let metadata: EncodedPayloadMetadata = serde_json::from_str(metadata.as_str())?;
// finally the payload
let payload = &message[8 + size..];
Ok((metadata, payload))
}
/// Try parse a UTF-8 string from an array of bytes
#[wasm_bindgen]
pub fn parse_utf8_string(payload: Vec<u8>) -> String {
String::from_utf8_lossy(&payload).into_owned()
}
/// Converts a UTF-8 string into an array of bytes
///
/// This method is provided as a replacement for the mess of `atob`
/// (https://developer.mozilla.org/en-US/docs/Web/API/atob) helpers provided by browsers and NodeJS.
///
/// Feel free to use `atob` if you know you won't have problems with polyfills or encoding issues.
#[wasm_bindgen]
pub fn utf8_string_to_byte_array(message: String) -> Vec<u8> {
message.into_bytes()
}
#[cfg(test)]
mod tests {
use super::*;
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
async fn test_encode_payload_with_headers() {
let message_as_bytes = encode_payload_with_headers(
"text/plain".to_string(),
vec![0u8, 1u8, 2u8],
Some("test headers".to_string()),
)
.unwrap();
// the expected message size
let size = message_as_bytes.len();
let expected_size = 61;
assert_eq!(size, expected_size);
let expected_header_size = 50usize;
assert_eq!(
message_as_bytes[0..8],
(expected_header_size as u64).to_be_bytes()
);
assert_eq!(
message_as_bytes[8 + expected_header_size..8 + expected_header_size + 3],
vec![0u8, 1u8, 2u8]
);
let res = parse_payload(&message_as_bytes).unwrap();
assert_eq!(res.0.mime_type, "text/plain");
assert_eq!(res.0.headers.unwrap(), "test headers".to_string());
assert_eq!(res.1, vec![0u8, 1u8, 2u8]);
}
#[wasm_bindgen_test]
async fn test_encode_payload_with_empty_headers() {
let message_as_bytes =
encode_payload_with_headers("text/plain".to_string(), vec![0u8, 1u8, 2u8], None)
.unwrap();
// the expected message size
let size = message_as_bytes.len();
let expected_size = 51;
assert_eq!(size, expected_size);
let expected_header_size = 40usize;
assert_eq!(
message_as_bytes[0..8],
(expected_header_size as u64).to_be_bytes()
);
assert_eq!(
message_as_bytes[8 + expected_header_size..8 + expected_header_size + 3],
vec![0u8, 1u8, 2u8]
);
let res = parse_payload(&message_as_bytes).unwrap();
assert_eq!(res.0.mime_type, "text/plain");
assert_eq!(res.0.headers, None);
assert_eq!(res.1, vec![0u8, 1u8, 2u8]);
}
#[wasm_bindgen_test]
async fn test_encode_payload_with_empty_headers_and_empty_mime_type() {
let message_as_bytes =
encode_payload_with_headers("".to_string(), vec![0u8, 1u8, 2u8], None).unwrap();
// the expected message size
let size = message_as_bytes.len();
let expected_size = 41;
assert_eq!(size, expected_size);
let expected_header_size = 30usize;
assert_eq!(
message_as_bytes[0..8],
(expected_header_size as u64).to_be_bytes()
);
assert_eq!(
message_as_bytes[8 + expected_header_size..8 + expected_header_size + 3],
vec![0u8, 1u8, 2u8]
);
let res = parse_payload(&message_as_bytes).unwrap();
assert_eq!(res.0.mime_type, "");
assert_eq!(res.0.headers, None);
assert_eq!(res.1, vec![0u8, 1u8, 2u8]);
}
#[wasm_bindgen_test]
async fn test_encode_payload_with_all_empty() {
let empty: Vec<u8> = vec![];
let message_as_bytes =
encode_payload_with_headers("".to_string(), empty.clone(), None).unwrap();
// the expected message size
let size = message_as_bytes.len();
let expected_size = 38;
assert_eq!(size, expected_size);
let expected_header_size = 30usize;
assert_eq!(
message_as_bytes[0..8],
(expected_header_size as u64).to_be_bytes()
);
assert_eq!(
message_as_bytes[8 + expected_header_size..8 + expected_header_size],
empty
);
let res = parse_payload(&message_as_bytes).unwrap();
assert_eq!(res.0.mime_type, "");
assert_eq!(res.0.headers, None);
assert_eq!(res.1, empty);
}
}
+4 -2
View File
@@ -3,12 +3,14 @@
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
pub mod binary_message_helper;
#[cfg(target_arch = "wasm32")]
mod client;
#[cfg(target_arch = "wasm32")]
pub mod encoded_payload_helper;
#[cfg(target_arch = "wasm32")]
pub mod gateway_selector;
#[cfg(target_arch = "wasm32")]
pub mod validation;
#[wasm_bindgen]
pub fn set_panic_hook() {
+35
View File
@@ -0,0 +1,35 @@
use nymsphinx::addressing::clients::Recipient;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn validate_recipient(recipient: String) -> Result<(), JsError> {
match Recipient::try_from_base58_string(recipient) {
Ok(_) => Ok(()),
Err(e) => Err(JsError::new(format!("{}", e).as_str())),
}
}
#[cfg(test)]
mod tests {
use super::validate_recipient;
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn test_recipient_validation_ok() {
let res = validate_recipient("DyQmXnst5NGGjzUZxRC5Bjs5bd7CBF3xMpsSmbRiizr2.GH6YTBP2NXU3AVqd8WjiTMVyeMjunXMEsp7gVCMEJqpD@336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9".to_string());
assert!(res.is_ok())
}
#[wasm_bindgen_test]
fn test_recipient_validation_fails() {
assert!(validate_recipient(" DyQmXnst5NGGjzUZxRC5Bjs5bd7CBF3xMpsSmbRiizr2.GH6YTBP2NXU3AVqd8WjiTMVyeMjunXMEsp7gVCMEJqpD@336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9".to_string()).is_err());
assert!(validate_recipient(
"DyQmXnst5NGGjzUZxRC5BjbRiizr2.GH6YTBP2NXU3AVqd8WD@336yuXAeGEgedRfqTJZQH1bHv1SjCZYarc9"
.to_string()
)
.is_err());
assert!(validate_recipient("🙀🙀🙀🙀".to_string()).is_err());
assert!(validate_recipient("".to_string()).is_err());
assert!(validate_recipient(" ".to_string()).is_err());
}
}
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

+16
View File
@@ -0,0 +1,16 @@
# HTML Nym Mixnet Chat App bundled with Parcel
This is an example of using the Nym Mixnet to send text chat messages in an app bundled with Parcel 2.0.
You can use this example as a seed for a new project.
You will need to have the Rust WASM toolchain installed. Please [follow the instructions here](https://rustwasm.github.io/docs/book/game-of-life/setup.html)
to install `wasm-pack` and then run the sample.
Try out the chat app by running:
```
yarn
yarn start
```
@@ -0,0 +1,52 @@
{
"name": "@nymproject/sdk-example-plain-html-parcel",
"description": "An example project that uses WASM and plain HTML bundled with Parcel v2",
"version": "1.0.0",
"license": "Apache-2.0",
"source": "src/index.html",
"browserslist": "> 0.5%, last 2 versions, not dead",
"dependencies": {
"@nymproject/sdk": "1"
},
"devDependencies": {
"@nymproject/eslint-config-react-typescript": "^1.0.0",
"@types/jest": "^27.0.1",
"@types/node": "^16.7.13",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"eslint": "^8.10.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.1.0",
"mini-css-extract-plugin": "^2.2.2",
"npm-run-all": "^4.1.5",
"prettier": "^2.5.1",
"ts-jest": "^27.0.5",
"typescript": "^4.6.2"
},
"scripts": {
"prestart": "yarn build:dependencies",
"start": "npx parcel",
"start:only-this": "npx parcel",
"build:dependencies": "run-s build:dependencies:nym-client-wasm build:dependencies:ts-packages build:dependencies:sdk",
"build:dependencies:nym-client-wasm": "../../packages/nym-client-wasm/scripts/build.sh",
"build:dependencies:ts-packages": "cd ../../../.. && yarn && yarn build",
"build:dependencies:sdk": "cd ../../packages/sdk && yarn build",
"prebuild": "yarn build:dependencies",
"build": "npx parcel build",
"build:only-this": "npx parcel build --log-level info",
"build:serve": "npx serve dist",
"test": "jest",
"test:watch": "jest --watch",
"tsc": "tsc",
"tsc:watch": "tsc --watch",
"lint": "eslint src",
"lint:fix": "eslint src --fix"
}
}
@@ -0,0 +1,62 @@
import { NymMixnetClient, MimeTypes } from '@nymproject/sdk';
/**
* Create a Sphinx packet and send it to the mixnet through the gateway node.
*
* Message and recipient are taken from the values in the user interface.
*
* @param {Client} nymClient the nym client to use for message sending
*/
export async function sendMessageTo(nym: NymMixnetClient) {
const message = (document.getElementById('message') as HTMLFormElement).value;
const recipient = (document.getElementById('recipient') as HTMLFormElement).value;
await nym.client.send({ payload: { message, mimeType: MimeTypes.TextPlain }, recipient });
displaySend(message);
}
/**
* Display messages that have been sent up the websocket. Colours them blue.
*
* @param {string} message
*/
function displaySend(message: string) {
const timestamp = new Date().toISOString().substr(11, 12);
const sendDiv = document.createElement('div');
const paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: blue');
const paragraphContent = document.createTextNode(`${timestamp} sent >>> ${message}`);
paragraph.appendChild(paragraphContent);
sendDiv.appendChild(paragraph);
document.getElementById('output')?.appendChild(sendDiv);
}
/**
* Display received text messages in the browser. Colour them green.
*
* @param {string} message
*/
export function displayReceived(message: string) {
const content = message;
const timestamp = new Date().toLocaleTimeString();
const receivedDiv = document.createElement('div');
const paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: green');
const paragraphContent = document.createTextNode(`${timestamp} received >>> ${content}`);
// const paragraphContent = document.createTextNode(timestamp + " received >>> " + content + ((replySurb != null) ? "Reply SURB was attached here (but we can't do anything with it yet" : " (NO REPLY-SURB AVAILABLE)"))
paragraph.appendChild(paragraphContent);
receivedDiv.appendChild(paragraph);
document.getElementById('output')?.appendChild(receivedDiv);
}
/**
* Display the nymClient's sender address in the user interface
*
* @param {Client} nymClient
*/
export function displaySenderAddress(address: string) {
(document.getElementById('sender') as HTMLFormElement).value = address;
}
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nym WebAssembly Demo</title>
<script type="module" src="./index.ts"></script>
</head>
<body>
<p>
<label>Sender: </label><input disabled="true" size="85" type="text" id="sender" value="">
</p>
<p>
<label>Recipient: </label><input size="85" type="text" id="recipient" value="">
</p>
<p>
<label>Message: </label><input type="text" id="message" value="Hello mixnet!">
</p>
<p>
<button id="send-button">Send</button>
</p>
<p>Send messages from your browser, through the mixnet, and to the recipient using the "send" button.</p>
<p><span style='color: blue;'>Sent</span> messages show in blue, <span style='color: green;'>received</span>
messages show in green.</p>
<hr>
<p>
<div id="output"></div>
</p>
</body>
</html>
@@ -0,0 +1,53 @@
import { createNymMixnetClient, NymMixnetClient } from '@nymproject/sdk';
import { displayReceived, sendMessageTo, displaySenderAddress } from './dom-utils';
let nym: NymMixnetClient | null = null;
/**
* The main entry point
*/
async function main() {
nym = await createNymMixnetClient();
if (!nym) {
console.error('Oh no! Could not create client');
return;
}
const nymApiUrl = 'https://validator.nymtech.net/api';
const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM';
// subscribe to connect event, so that we can show the client's address
nym.events.subscribeToConnected((e) => {
if (e.args.address) {
displaySenderAddress(e.args.address);
}
});
// subscribe to message received events and show any string messages received
nym.events.subscribeToTextMessageReceivedEvent((e) => {
displayReceived(e.args.payload);
});
const sendButton: HTMLButtonElement = document.querySelector('#send-button') as HTMLButtonElement;
if (sendButton) {
sendButton.onclick = function () {
if (nym) {
sendMessageTo(nym);
}
};
}
// start up the client
await nym.client.start({
clientId: 'My awesome client',
nymApiUrl,
preferredGatewayIdentityKey,
});
}
// wait for the html to load
window.addEventListener('DOMContentLoaded', () => {
// let's do this!
main();
});
@@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"jsx": "react-jsx",
"outDir": "./dist"
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
],
"exclude": [
"node_modules",
"build",
"dist"
]
}
+4 -1
View File
@@ -4,6 +4,9 @@ This is an example of using the Nym Mixnet to send text chat messages, with opti
You can use this example as a seed for a new project.
You will need to have the Rust WASM toolchain installed. Please [follow the instructions here](https://rustwasm.github.io/docs/book/game-of-life/setup.html)
to install `wasm-pack` and then run the sample.
Try out the chat app by running:
```
@@ -20,7 +23,7 @@ a WASM library that builds and encrypts Sphinx packets in the browser to send ov
The WASM code encrypts each layer of the Sphinx packet in the browser, before sending the Sphinx packet over a websocket to the ingress gateway:
![Sphinx packet](../docs/sphinx2.svg)
![Sphinx packet](../docs/sphinx.svg)
@@ -54,6 +54,7 @@
"scripts": {
"prestart": "yarn build:dependencies",
"start": "webpack serve --progress --port 3000",
"start:only-this": "webpack serve --progress --port 3000",
"build:dependencies": "run-s build:dependencies:nym-client-wasm build:dependencies:ts-packages build:dependencies:sdk",
"build:dependencies:nym-client-wasm": "../../packages/nym-client-wasm/scripts/build.sh",
"build:dependencies:ts-packages": "cd ../../../.. && yarn && yarn build",
@@ -1,4 +1,4 @@
import { NymMixnetClient } from '@nymproject/sdk';
import { NymMixnetClient, MimeTypes } from '@nymproject/sdk';
/**
* Create a Sphinx packet and send it to the mixnet through the gateway node.
@@ -8,11 +8,11 @@ import { NymMixnetClient } from '@nymproject/sdk';
* @param {Client} nymClient the nym client to use for message sending
*/
export async function sendMessageTo(nym: NymMixnetClient) {
const payload = (document.getElementById('message') as HTMLFormElement).value;
const message = (document.getElementById('message') as HTMLFormElement).value;
const recipient = (document.getElementById('recipient') as HTMLFormElement).value;
await nym.client.sendMessage({ payload, recipient });
displaySend(payload);
await nym.client.send({ payload: { message, mimeType: MimeTypes.TextPlain }, recipient });
displaySend(message);
}
/**
@@ -14,13 +14,8 @@ async function main() {
return;
}
// // mixnet v1
// const nymApiUrl = 'https://validator.nymtech.net/api';
// const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM';
// mixnet v2
const nymApiUrl = 'https://qwerty-validator-api.qa.nymte.ch/api'; // "http://localhost:8081";
const preferredGatewayIdentityKey = undefined; // '36vfvEyBzo5cWEFbnP7fqgY39kFw9PQhvwzbispeNaxL';
const nymApiUrl = 'https://validator.nymtech.net/api';
const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM';
// subscribe to connect event, so that we can show the client's address
nym.events.subscribeToConnected((e) => {
@@ -10,6 +10,9 @@ You can use this example as a seed for a new project, and it uses:
- Web Workers
- Material UI (MUI)
You will need to have the Rust WASM toolchain installed. Please [follow the instructions here](https://rustwasm.github.io/docs/book/game-of-life/setup.html)
to install `wasm-pack` and then run the sample.
Try out the chat app by running:
```
@@ -143,13 +143,13 @@ export const Content: FCWithChildren = () => {
if (events) {
const unsubcribe = events.subscribeToBinaryMessageReceivedEvent((e) => {
// the headers will be JSON (see the mixnet context for how they are created), so parse them
const headers = parseBinaryMessageHeaders(e.args.headers);
const headers = e.args.headers ? parseBinaryMessageHeaders(e.args.headers) : undefined;
const blob = new Blob([new Uint8Array(e.args.payload)], { type: headers.mimeType });
const blob = new Blob([new Uint8Array(e.args.payload)], { type: headers?.mimeType });
log.current.push({
kind: 'rx',
timestamp: new Date(),
filename: headers.filename,
filename: headers?.filename,
fileDownloadUrl: URL.createObjectURL(blob),
filesize: e.args.payload.length,
});
@@ -1,5 +1,5 @@
import * as React from 'react';
import { createNymMixnetClient, IWebWorkerEvents, NymClientConfig, NymMixnetClient } from '@nymproject/sdk';
import { createNymMixnetClient, IWebWorkerEvents, MimeTypes, NymClientConfig, NymMixnetClient } from '@nymproject/sdk';
export interface BinaryMessageHeaders {
filename: string;
@@ -67,7 +67,10 @@ export const MixnetContextProvider: FCWithChildren = ({ children }) => {
console.error('Nym client has not initialised. Please wrap in useEffect on `isReady` prop of this context.');
return;
}
await nym.current.client.sendMessage(args);
await nym.current.client.send({
recipient: args.recipient,
payload: { message: args.payload, mimeType: MimeTypes.TextPlain },
});
};
const sendBinaryMessage = async (args: { payload: Uint8Array; recipient: string; headers: BinaryMessageHeaders }) => {
@@ -76,8 +79,10 @@ export const MixnetContextProvider: FCWithChildren = ({ children }) => {
return;
}
// convert headers to JSON
const sendArgs = { ...args, headers: JSON.stringify(args.headers) };
await nym.current.client.sendBinaryMessage(sendArgs);
await nym.current.client.send({
recipient: args.recipient,
payload: { message: args.payload, mimeType: 'application/octet-stream', headers: JSON.stringify(args.headers) },
});
};
const value = React.useMemo<State>(
@@ -9,12 +9,12 @@ cd "$(dirname "$0")"
cd ..
# clear out any files and suppress missing file errors
rm nym_client_wasm* package.json || true
rm -f nym_client_wasm* package.json || true
# let wasm-pack build the files and put them in the output location rather than `./pkg`
cd ../../../../clients/webassembly
wasm-pack build --scope nymproject --target no-modules --out-dir ../../sdk/typescript/packages/nym-client-wasm
wasm-pack build --scope nymproject --target web --out-dir ../../sdk/typescript/packages/nym-client-wasm
# clean up some files that come with the build
cd ../../sdk/typescript/packages/nym-client-wasm
rm README.md LICENSE_APACHE
rm README.md LICENSE_APACHE
+15
View File
@@ -49,6 +49,21 @@ Send a message to another user (you will need to know their address at a Gateway
await nym.client.sendMessage({ payload, recipient });
```
### Building from source
You will need to have the Rust WASM toolchain installed. Please [follow the instructions here](https://rustwasm.github.io/docs/book/game-of-life/setup.html)
to install `wasm-pack`.
Make sure you have either Node 16 LTS or Node 18 LTS installed and are using it to do the build.
Run these commands to build the SDK:
```
yarn
yarn build
```
The output bundle will be created in the `dist` directory.
### Packaging
If you're a Nym platform developer who's made changes to the Rust (or JS) files and wants to re-publish the package to NPM, here's how you do it:
+21 -12
View File
@@ -3,14 +3,11 @@
"version": "1.1.4",
"license": "Apache-2.0",
"author": "Nym Technologies SA",
"main": "dist/index.js",
"main": "dist/cjs/index.js",
"module": "dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist/worker.js",
"dist/nym_client_wasm.d.ts",
"dist/nym_client_wasm.js",
"dist/nym_client_wasm_bg.wasm",
"dist/nym_client_wasm_bg.wasm.d.ts",
"dist/**/*"
],
"exports": {
@@ -27,10 +24,14 @@
"build:dependencies:ts-packages": "cd ../../../.. && yarn && yarn build",
"build:dependencies:nym-client-wasm": "../nym-client-wasm/scripts/build.sh",
"prebuild": "yarn build:dependencies",
"build": "tsc",
"postbuild": "cp ../nym-client-wasm/nym_client_wasm* dist/mixnet/wasm && yarn copy:readme",
"build:only-this": "tsc",
"postbuild:only-this": "cp ../nym-client-wasm/nym_client_wasm* dist/mixnet/wasm",
"build": "yarn build:only-this",
"build:only-this": "run-s build:worker build:tsc build:worker:move build:rollup:cjs",
"postbuild:only-this": "yarn copy:readme",
"build:tsc": "tsc",
"build:worker": "rollup src/mixnet/wasm/worker.ts -c rollup.config.mjs",
"build:rollup:cjs": "rollup -c rollup-cjs.config.mjs",
"postbuild:worker": "rm -rf dist/index.d.ts dist/coconut dist/mixnet && mkdir -p dist/mixnet/wasm",
"build:worker:move": "rm dist/mixnet/wasm/worker.js && mv dist/worker.js dist/mixnet/wasm",
"copy:readme": "cp README.md dist"
},
"dependencies": {
@@ -38,10 +39,13 @@
},
"devDependencies": {
"@nymproject/eslint-config-react-typescript": "^1.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-terser": "^0.2.1",
"@rollup/plugin-typescript": "^10.0.1",
"@rollup/plugin-wasm": "^6.1.1",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"rimraf": "^3.0.2",
"typescript": "^4.8.4",
"eslint": "^8.10.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^16.1.0",
@@ -52,6 +56,11 @@
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.2",
"eslint-plugin-react-hooks": "^4.3.0"
"eslint-plugin-react-hooks": "^4.3.0",
"rimraf": "^3.0.2",
"rollup": "^3.9.1",
"rollup-plugin-base64": "^1.0.1",
"rollup-plugin-web-worker-loader": "^1.6.1",
"typescript": "^4.8.4"
}
}
@@ -0,0 +1,28 @@
import typescript from '@rollup/plugin-typescript';
import resolve from '@rollup/plugin-node-resolve';
import { wasm } from '@rollup/plugin-wasm';
import replace from '@rollup/plugin-replace';
const extensions = [
'.js', '.jsx', '.ts', '.tsx',
];
export default {
input: 'src/index.ts',
output: {
dir: 'dist/cjs',
format: 'cjs',
},
plugins: [
resolve({ extensions }),
// this is some nasty monkey patching that removes the WASM URL (because it is handled by the `wasm` plugin)
replace({
values: { 'input = new URL(\'nym_client_wasm_bg.wasm\', import.meta.url);': 'input = undefined;' },
delimiters: ['', ''],
preventAssignment: true,
}),
// force the wasm plugin to embed the wasm bundle - this means no downstream bundlers have to worry about handling it
wasm({ maxFileSize: 10000000, targetEnv: 'browser' }),
typescript({ compilerOptions: { outDir: 'dist/cjs', declaration: false } }),
],
};
@@ -0,0 +1,28 @@
import typescript from '@rollup/plugin-typescript';
import resolve from '@rollup/plugin-node-resolve';
import { wasm } from '@rollup/plugin-wasm';
import replace from '@rollup/plugin-replace';
const extensions = [
'.js', '.jsx', '.ts', '.tsx',
];
export default {
input: 'src/index.ts',
output: {
dir: 'dist',
format: 'es',
},
plugins: [
resolve({ extensions }),
// this is some nasty monkey patching that removes the WASM URL (because it is handled by the `wasm` plugin)
replace({
values: { 'input = new URL(\'nym_client_wasm_bg.wasm\', import.meta.url);': 'input = undefined;' },
delimiters: ['', ''],
preventAssignment: true,
}),
// force the wasm plugin to embed the wasm bundle - this means no downstream bundlers have to worry about handling it
wasm({ maxFileSize: 10000000, targetEnv: 'browser' }),
typescript(),
],
};
@@ -8,6 +8,7 @@ import {
LoadedEvent,
StringMessageReceivedEvent,
BinaryMessageReceivedEvent,
MimeTypes,
} from './types';
/**
@@ -22,7 +23,9 @@ export interface NymMixnetClient {
* Create a client to send and receive traffic from the Nym mixnet.
*
*/
export const createNymMixnetClient = async (): Promise<NymMixnetClient> => {
export const createNymMixnetClient = async (options?: {
mimeTypes?: string[] | MimeTypes[];
}): Promise<NymMixnetClient> => {
// create a web worker that runs the WASM client on another thread and wait until it signals that it is ready
// eslint-disable-next-line @typescript-eslint/no-use-before-define
const worker = await createWorker();
@@ -89,6 +92,11 @@ export const createNymMixnetClient = async (): Promise<NymMixnetClient> => {
// let comlink handle interop with the web worker
const client = Comlink.wrap<IWebWorker>(worker);
// set any options
if (options?.mimeTypes) {
await client.setTextMimeTypes(options.mimeTypes);
}
// pass the client interop and subscription manage back to the caller
return {
client,
@@ -1,10 +1,25 @@
/// <reference path="../../../../nym-client-wasm/nym_client_wasm.d.ts" />
import { Debug } from '@nymproject/nym-client-wasm';
export type OnStringMessageFn = (message: string) => void;
/**
* Some common mime types, however, you can always just specify the mime-type as a string
*/
export enum MimeTypes {
ApplicationOctetStream = 'application/octet-stream',
TextPlain = 'text/plain',
ApplicationJson = 'application/json',
}
export type OnBinaryMessageFn = (message: Uint8Array) => void;
export interface Payload {
message: string | Uint8Array;
export type OnConnectFn = (address?: string) => void;
mimeType?: MimeTypes | string;
headers?: string;
}
export type OnPayloadFn = (payload: Payload) => void;
export type OnRawPayloadFn = (payload: Uint8Array) => void;
export type EventHandlerFn<E> = (e: E) => void | Promise<void>;
@@ -36,14 +51,16 @@ export interface NymClientConfig {
/**
* Optional. Settings for the WASM client.
*/
debug?: wasm_bindgen.Debug;
debug?: Debug;
}
export interface IWebWorker {
start: (config: NymClientConfig) => void;
stop: () => void;
selfAddress: () => string | undefined;
sendMessage: (args: { payload: string; recipient: string }) => void;
sendBinaryMessage: (args: { payload: Uint8Array; recipient: string; headers?: string }) => void;
setTextMimeTypes: (mimeTypes: string[]) => void;
getTextMimeTypes: () => string[];
send: (args: { payload: Payload; recipient: string; replySurbs?: number }) => void;
}
export enum EventKinds {
@@ -70,17 +87,19 @@ export interface ConnectedEvent {
export interface StringMessageReceivedEvent {
kind: EventKinds.StringMessageReceived;
args: {
kind: number;
mimeType: MimeTypes;
payload: string;
payloadRaw: Uint8Array;
headers?: string;
};
}
export interface BinaryMessageReceivedEvent {
kind: EventKinds.BinaryMessageReceived;
args: {
kind: number;
mimeType: MimeTypes;
payload: Uint8Array;
headers: string;
headers?: string;
};
}
@@ -1,69 +1,94 @@
/* eslint-disable no-console,no-restricted-globals */
/// <reference path="../../../../nym-client-wasm/nym_client_wasm.d.ts" />
/**
* NB: URL syntax is used so that bundlers like webpack can load this package's code when inside the final bundle
* the files from ../../../../nym-client-wasm will be copied into the dist directory of this package, so all import
* paths are _relative to the output directory_ of this package (`dist`) - don't get confused!
*/
import * as Comlink from 'comlink';
//
// Rollup will replace wasmBytes with a function that loads the WASM bundle from a base64 string embedded in the output.
//
// Doing it this way, saves having to support a large variety of bundlers and their quirks.
//
// @ts-ignore
import wasmBytes from '@nymproject/nym-client-wasm/nym_client_wasm_bg.wasm';
import init, {
NymClient,
NymClientBuilder,
Config,
get_gateway,
default_debug,
decode_payload,
parse_utf8_string,
utf8_string_to_byte_array,
encode_payload_with_headers,
} from '@nymproject/nym-client-wasm';
import type {
BinaryMessageReceivedEvent,
ConnectedEvent,
IWebWorker,
LoadedEvent,
OnStringMessageFn,
OnBinaryMessageFn,
OnConnectFn,
StringMessageReceivedEvent,
BinaryMessageReceivedEvent,
NymClientConfig,
OnRawPayloadFn,
StringMessageReceivedEvent,
} from './types';
import { EventKinds } from './types';
import { EventKinds, MimeTypes } from './types';
// web workers are only allowed to load external scripts as the load
importScripts(new URL('./nym_client_wasm.js', import.meta.url));
// importScripts(new URL('./nym_client_wasm.js', import.meta.url));
// eslint-disable-next-line import/extensions
console.log('[Nym WASM client] Starting Nym WASM web worker...');
// again, construct a URL that can be used by a bundler to repackage the WASM binary
const wasmUrl = new URL('./nym_client_wasm_bg.wasm', import.meta.url);
/**
* Helper method to send typed messages.
* @param event The strongly typed message to send back to the calling thread.
*/
const postMessageWithType = <E>(event: E) => self.postMessage(event);
// ------------ the 1st byte of messages is the kind from the list below ------------
const PAYLOAD_KIND_TEXT = 0;
const PAYLOAD_KIND_BINARY = 1;
/**
* This class holds the state of the Nym WASM client and provides any interop needed.
*/
class ClientWrapper {
client: wasm_bindgen.NymClient | null = null;
client: NymClient | null = null;
builder: NymClientBuilder | null = null;
mimeTypes: string[] = [MimeTypes.TextPlain, MimeTypes.ApplicationJson];
/**
* Creates the WASM client and initialises it.
*/
init = (
config: wasm_bindgen.Config,
onConnectHandler: OnConnectFn,
onStringMessageHandler?: OnStringMessageFn,
onBinaryMessageHandler?: OnBinaryMessageFn,
) => {
this.client = new wasm_bindgen.NymClient(config);
if (onBinaryMessageHandler) {
this.client.set_on_binary_message(onBinaryMessageHandler);
}
init = (config: Config, onRawPayloadHandler?: OnRawPayloadFn) => {
const onMessageHandler = (message: Uint8Array) => {
try {
if (onRawPayloadHandler) {
onRawPayloadHandler(message);
}
} catch (e) {
console.error('Unhandled exception in `ClientWrapper.onRawPayloadHandler`: ', e);
}
};
// NB: because we set the `kind` byte in the message payload first, we don't need to bother to try to parse
// all messages as string
// if (onStringMessageHandler) {
// this.client.set_on_message(onStringMessageHandler);
// }
this.builder = new NymClientBuilder(config, onMessageHandler);
};
/**
* Sets the mime-types that will be parsed for UTF-8 string content.
*
* @param mimeTypes An array of mime-types to treat as having string content.
*/
setTextMimeTypes = (mimeTypes: string[]) => {
this.mimeTypes = mimeTypes;
};
/**
* Gest the mime-types that are considered as string and will be automatically converted to byte arrays.
*/
getTextMimeTypes = () => this.mimeTypes;
/**
* Returns the address of this client.
*/
@@ -80,47 +105,51 @@ class ClientWrapper {
* Connects to the gateway and starts the client sending traffic.
*/
start = async () => {
if (!this.client) {
console.error('Client has not been initialised. Please call `init` first.');
if (!this.builder) {
console.error('Client config has not been initialised. Please call `init` first.');
return;
}
// this is current limitation of wasm in rust - for async methods you can't take self by reference...
// I'm trying to figure out if I can somehow hack my way around it, but for time being you have to re-assign
// the object (it's the same one)
this.client = await this.client.start();
this.client = await this.builder.start_client();
};
sendMessage = async ({ payload, recipient }: { recipient: string; payload: string }) => {
/**
* Stops the client and cleans up.
*/
stop = () => {
if (!this.client) {
console.error('Client has not been initialised. Please call `init` first.');
return;
}
const message = wasm_bindgen.create_binary_message_from_string(PAYLOAD_KIND_TEXT, payload);
this.client = await this.client.send_binary_message(message, recipient);
this.client.free();
this.client = null;
};
sendBinaryMessage = async ({
send = async ({
payload,
recipient,
headers,
replySurbs = 0,
}: {
recipient: string;
payload: Uint8Array;
headers?: string;
replySurbs?: number;
}) => {
if (!this.client) {
console.error('Client has not been initialised. Please call `init` first.');
return;
}
const message = wasm_bindgen.create_binary_message_with_headers(PAYLOAD_KIND_BINARY, payload, headers || '');
this.client = await this.client.send_binary_message(message, recipient);
// TODO: currently we don't do anything with the result, it needs some typing and exposed back on the main thread
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const res = await this.client.send_anonymous_message(payload, recipient, replySurbs);
};
}
// load WASM binary
wasm_bindgen(wasmUrl)
.then((importResult) => {
init(wasmBytes())
.then((importResult: any) => {
// sets up better stack traces in case of in-rust panics
importResult.set_panic_hook();
@@ -129,48 +158,34 @@ wasm_bindgen(wasmUrl)
const startHandler = async (config: NymClientConfig) => {
// fetch the gateway details (randomly chosen if no preferred gateway is set)
const gatewayEndpoint = await wasm_bindgen.get_gateway(
config.nymApiUrl,
config.preferredGatewayIdentityKey,
);
const gatewayEndpoint = await get_gateway(config.nymApiUrl, config.preferredGatewayIdentityKey);
// set a different gatewayListener in order to avoid workaround ws over https error
if (config.gatewayListener)
gatewayEndpoint.gateway_listener = config.gatewayListener;
if (config.gatewayListener) gatewayEndpoint.gateway_listener = config.gatewayListener;
// create the client, passing handlers for events
wrapper.init(
new wasm_bindgen.Config(
config.clientId,
config.nymApiUrl,
gatewayEndpoint,
config.debug || wasm_bindgen.default_debug(),
),
() => {
console.log();
},
undefined,
new Config(config.clientId, config.nymApiUrl, gatewayEndpoint, config.debug || default_debug()),
async (message) => {
try {
const { kind, payload, headers } = await wasm_bindgen.parse_binary_message_with_headers(message);
switch (kind) {
case PAYLOAD_KIND_TEXT: {
const stringMessage = await wasm_bindgen.parse_string_message_with_headers(message);
postMessageWithType<StringMessageReceivedEvent>({
kind: EventKinds.StringMessageReceived,
args: { kind, payload: stringMessage.payload },
});
break;
}
case PAYLOAD_KIND_BINARY:
postMessageWithType<BinaryMessageReceivedEvent>({
kind: EventKinds.BinaryMessageReceived,
args: { kind, payload, headers: headers || '' },
});
break;
default:
console.error('Could not determine message kind from 1st byte of message', { message, kind, payload });
const decodedPayload = decode_payload(message);
const { payload, headers } = decodedPayload;
const mimeType = decodedPayload.mimeType as MimeTypes;
if (wrapper.getTextMimeTypes().includes(mimeType)) {
const stringMessage = parse_utf8_string(payload);
postMessageWithType<StringMessageReceivedEvent>({
kind: EventKinds.StringMessageReceived,
args: { mimeType, payload: stringMessage, payloadRaw: payload, headers },
});
return;
}
postMessageWithType<BinaryMessageReceivedEvent>({
kind: EventKinds.BinaryMessageReceived,
args: { mimeType, payload, headers },
});
} catch (e) {
console.error('Failed to parse binary message', e);
}
@@ -191,14 +206,43 @@ wasm_bindgen(wasmUrl)
console.log('[Nym WASM client] Starting...', { config });
startHandler(config).catch((e) => console.error('[Nym WASM client] Failed to start', e));
},
stop() {
wrapper.stop();
},
selfAddress() {
return wrapper.selfAddress();
},
sendMessage(args) {
wrapper.sendMessage(args).catch((e) => console.error('[Nym WASM client] Failed to send message', e));
setTextMimeTypes(mimeTypes) {
wrapper.setTextMimeTypes(mimeTypes);
},
sendBinaryMessage(args) {
wrapper.sendBinaryMessage(args).catch((e) => console.error('[Nym WASM client] Failed to send message', e));
getTextMimeTypes() {
return wrapper.getTextMimeTypes();
},
send(args) {
const {
recipient,
replySurbs,
payload: { mimeType, headers },
} = args;
let payloadBytes = new Uint8Array();
if (mimeType && wrapper.getTextMimeTypes().includes(mimeType) && typeof args.payload.message === 'string') {
payloadBytes = utf8_string_to_byte_array(args.payload.message);
} else if (typeof args.payload.message !== 'string') {
payloadBytes = args.payload.message;
} else {
console.error(
'[Nym WASM client] Payload is a string. It should be a UintArray, or the mime-type should be set with `setTextMimeTypes` for auto-conversion',
);
return;
}
const payload = encode_payload_with_headers(
mimeType || MimeTypes.ApplicationOctetStream,
payloadBytes,
headers,
);
wrapper
.send({ payload, recipient, replySurbs })
.catch((e) => console.error('[Nym WASM client] Failed to send message', e));
},
};
+4054 -69
View File
File diff suppressed because it is too large Load Diff