Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 317806005a | |||
| 5723078413 | |||
| 9777da0964 | |||
| bee8638572 | |||
| be7504b036 | |||
| cf342108c3 | |||
| 69f5a0d6ba | |||
| 548dfed713 |
@@ -41,3 +41,4 @@ storybook-static
|
|||||||
envs/qwerty.env
|
envs/qwerty.env
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
nym-connect/Cargo.lock
|
nym-connect/Cargo.lock
|
||||||
|
.parcel-cache
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ futures = "0.3"
|
|||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
anyhow = "1.0"
|
||||||
serde-wasm-bindgen = "0.4"
|
serde-wasm-bindgen = "0.4"
|
||||||
tokio = { version = "1.24.1", features = ["sync"] }
|
tokio = { version = "1.24.1", features = ["sync"] }
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
|
|||||||
@@ -16,11 +16,7 @@ They should be implemented soon. You can build your applications, but don't rely
|
|||||||
|
|
||||||
## Using it
|
## Using it
|
||||||
|
|
||||||
See the [Nym docs](https://nymtech.net/docs).
|
See the [SDK directory](../../sdk/typescript/examples) for examples on how to use it and the NPM packages available.
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
@@ -36,8 +32,13 @@ To be clear, this is not something that most JS developers need to worry about,
|
|||||||
|
|
||||||
### Packaging
|
### 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
|
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)
|
2. go the `sdk/typescript` directory (off the project root)
|
||||||
3. `cd pkg && npm publish --access=public` will publish your changed package to NPM
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,12 +3,14 @@
|
|||||||
|
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
pub mod binary_message_helper;
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
mod client;
|
mod client;
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pub mod encoded_payload_helper;
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub mod gateway_selector;
|
pub mod gateway_selector;
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pub mod validation;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn set_panic_hook() {
|
pub fn set_panic_hook() {
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 11 KiB |
@@ -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,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 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:
|
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:
|
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:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"prestart": "yarn build:dependencies",
|
"prestart": "yarn build:dependencies",
|
||||||
"start": "webpack serve --progress --port 3000",
|
"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": "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:nym-client-wasm": "../../packages/nym-client-wasm/scripts/build.sh",
|
||||||
"build:dependencies:ts-packages": "cd ../../../.. && yarn && yarn build",
|
"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.
|
* 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
|
* @param {Client} nymClient the nym client to use for message sending
|
||||||
*/
|
*/
|
||||||
export async function sendMessageTo(nym: NymMixnetClient) {
|
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;
|
const recipient = (document.getElementById('recipient') as HTMLFormElement).value;
|
||||||
|
|
||||||
await nym.client.sendMessage({ payload, recipient });
|
await nym.client.send({ payload: { message, mimeType: MimeTypes.TextPlain }, recipient });
|
||||||
displaySend(payload);
|
displaySend(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,13 +14,8 @@ async function main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// // mixnet v1
|
const nymApiUrl = 'https://validator.nymtech.net/api';
|
||||||
// const nymApiUrl = 'https://validator.nymtech.net/api';
|
const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM';
|
||||||
// const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM';
|
|
||||||
|
|
||||||
// mixnet v2
|
|
||||||
const nymApiUrl = 'https://qwerty-validator-api.qa.nymte.ch/api'; // "http://localhost:8081";
|
|
||||||
const preferredGatewayIdentityKey = undefined; // '36vfvEyBzo5cWEFbnP7fqgY39kFw9PQhvwzbispeNaxL';
|
|
||||||
|
|
||||||
// subscribe to connect event, so that we can show the client's address
|
// subscribe to connect event, so that we can show the client's address
|
||||||
nym.events.subscribeToConnected((e) => {
|
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
|
- Web Workers
|
||||||
- Material UI (MUI)
|
- 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:
|
Try out the chat app by running:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -143,13 +143,13 @@ export const Content: FCWithChildren = () => {
|
|||||||
if (events) {
|
if (events) {
|
||||||
const unsubcribe = events.subscribeToBinaryMessageReceivedEvent((e) => {
|
const unsubcribe = events.subscribeToBinaryMessageReceivedEvent((e) => {
|
||||||
// the headers will be JSON (see the mixnet context for how they are created), so parse them
|
// 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({
|
log.current.push({
|
||||||
kind: 'rx',
|
kind: 'rx',
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
filename: headers.filename,
|
filename: headers?.filename,
|
||||||
fileDownloadUrl: URL.createObjectURL(blob),
|
fileDownloadUrl: URL.createObjectURL(blob),
|
||||||
filesize: e.args.payload.length,
|
filesize: e.args.payload.length,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react';
|
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 {
|
export interface BinaryMessageHeaders {
|
||||||
filename: string;
|
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.');
|
console.error('Nym client has not initialised. Please wrap in useEffect on `isReady` prop of this context.');
|
||||||
return;
|
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 }) => {
|
const sendBinaryMessage = async (args: { payload: Uint8Array; recipient: string; headers: BinaryMessageHeaders }) => {
|
||||||
@@ -76,8 +79,10 @@ export const MixnetContextProvider: FCWithChildren = ({ children }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// convert headers to JSON
|
// convert headers to JSON
|
||||||
const sendArgs = { ...args, headers: JSON.stringify(args.headers) };
|
await nym.current.client.send({
|
||||||
await nym.current.client.sendBinaryMessage(sendArgs);
|
recipient: args.recipient,
|
||||||
|
payload: { message: args.payload, mimeType: 'application/octet-stream', headers: JSON.stringify(args.headers) },
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const value = React.useMemo<State>(
|
const value = React.useMemo<State>(
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ cd "$(dirname "$0")"
|
|||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
# clear out any files and suppress missing file errors
|
# 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`
|
# let wasm-pack build the files and put them in the output location rather than `./pkg`
|
||||||
cd ../../../../clients/webassembly
|
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
|
# clean up some files that come with the build
|
||||||
cd ../../sdk/typescript/packages/nym-client-wasm
|
cd ../../sdk/typescript/packages/nym-client-wasm
|
||||||
|
|||||||
@@ -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 });
|
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
|
### 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 (or JS) files and wants to re-publish the package to NPM, here's how you do it:
|
||||||
|
|||||||
@@ -3,14 +3,11 @@
|
|||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"author": "Nym Technologies SA",
|
"author": "Nym Technologies SA",
|
||||||
"main": "dist/index.js",
|
"main": "dist/cjs/index.js",
|
||||||
|
"module": "dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"dist/worker.js",
|
"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/**/*"
|
"dist/**/*"
|
||||||
],
|
],
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -27,10 +24,14 @@
|
|||||||
"build:dependencies:ts-packages": "cd ../../../.. && yarn && yarn build",
|
"build:dependencies:ts-packages": "cd ../../../.. && yarn && yarn build",
|
||||||
"build:dependencies:nym-client-wasm": "../nym-client-wasm/scripts/build.sh",
|
"build:dependencies:nym-client-wasm": "../nym-client-wasm/scripts/build.sh",
|
||||||
"prebuild": "yarn build:dependencies",
|
"prebuild": "yarn build:dependencies",
|
||||||
"build": "tsc",
|
"build": "yarn build:only-this",
|
||||||
"postbuild": "cp ../nym-client-wasm/nym_client_wasm* dist/mixnet/wasm && yarn copy:readme",
|
"build:only-this": "run-s build:worker build:tsc build:worker:move build:rollup:cjs",
|
||||||
"build:only-this": "tsc",
|
"postbuild:only-this": "yarn copy:readme",
|
||||||
"postbuild:only-this": "cp ../nym-client-wasm/nym_client_wasm* dist/mixnet/wasm",
|
"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"
|
"copy:readme": "cp README.md dist"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -38,10 +39,13 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
"@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/eslint-plugin": "^5.13.0",
|
||||||
"@typescript-eslint/parser": "^5.13.0",
|
"@typescript-eslint/parser": "^5.13.0",
|
||||||
"rimraf": "^3.0.2",
|
|
||||||
"typescript": "^4.8.4",
|
|
||||||
"eslint": "^8.10.0",
|
"eslint": "^8.10.0",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-config-airbnb-typescript": "^16.1.0",
|
"eslint-config-airbnb-typescript": "^16.1.0",
|
||||||
@@ -52,6 +56,11 @@
|
|||||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"eslint-plugin-react": "^7.29.2",
|
"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,
|
LoadedEvent,
|
||||||
StringMessageReceivedEvent,
|
StringMessageReceivedEvent,
|
||||||
BinaryMessageReceivedEvent,
|
BinaryMessageReceivedEvent,
|
||||||
|
MimeTypes,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,7 +23,9 @@ export interface NymMixnetClient {
|
|||||||
* Create a client to send and receive traffic from the Nym mixnet.
|
* 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
|
// 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
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||||
const worker = await createWorker();
|
const worker = await createWorker();
|
||||||
@@ -89,6 +92,11 @@ export const createNymMixnetClient = async (): Promise<NymMixnetClient> => {
|
|||||||
// let comlink handle interop with the web worker
|
// let comlink handle interop with the web worker
|
||||||
const client = Comlink.wrap<IWebWorker>(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
|
// pass the client interop and subscription manage back to the caller
|
||||||
return {
|
return {
|
||||||
client,
|
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>;
|
export type EventHandlerFn<E> = (e: E) => void | Promise<void>;
|
||||||
|
|
||||||
@@ -36,14 +51,16 @@ export interface NymClientConfig {
|
|||||||
/**
|
/**
|
||||||
* Optional. Settings for the WASM client.
|
* Optional. Settings for the WASM client.
|
||||||
*/
|
*/
|
||||||
debug?: wasm_bindgen.Debug;
|
debug?: Debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWebWorker {
|
export interface IWebWorker {
|
||||||
start: (config: NymClientConfig) => void;
|
start: (config: NymClientConfig) => void;
|
||||||
|
stop: () => void;
|
||||||
selfAddress: () => string | undefined;
|
selfAddress: () => string | undefined;
|
||||||
sendMessage: (args: { payload: string; recipient: string }) => void;
|
setTextMimeTypes: (mimeTypes: string[]) => void;
|
||||||
sendBinaryMessage: (args: { payload: Uint8Array; recipient: string; headers?: string }) => void;
|
getTextMimeTypes: () => string[];
|
||||||
|
send: (args: { payload: Payload; recipient: string; replySurbs?: number }) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EventKinds {
|
export enum EventKinds {
|
||||||
@@ -70,17 +87,19 @@ export interface ConnectedEvent {
|
|||||||
export interface StringMessageReceivedEvent {
|
export interface StringMessageReceivedEvent {
|
||||||
kind: EventKinds.StringMessageReceived;
|
kind: EventKinds.StringMessageReceived;
|
||||||
args: {
|
args: {
|
||||||
kind: number;
|
mimeType: MimeTypes;
|
||||||
payload: string;
|
payload: string;
|
||||||
|
payloadRaw: Uint8Array;
|
||||||
|
headers?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BinaryMessageReceivedEvent {
|
export interface BinaryMessageReceivedEvent {
|
||||||
kind: EventKinds.BinaryMessageReceived;
|
kind: EventKinds.BinaryMessageReceived;
|
||||||
args: {
|
args: {
|
||||||
kind: number;
|
mimeType: MimeTypes;
|
||||||
payload: Uint8Array;
|
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
|
* 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
|
* 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!
|
* paths are _relative to the output directory_ of this package (`dist`) - don't get confused!
|
||||||
*/
|
*/
|
||||||
import * as Comlink from 'comlink';
|
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 {
|
import type {
|
||||||
|
BinaryMessageReceivedEvent,
|
||||||
ConnectedEvent,
|
ConnectedEvent,
|
||||||
IWebWorker,
|
IWebWorker,
|
||||||
LoadedEvent,
|
LoadedEvent,
|
||||||
OnStringMessageFn,
|
|
||||||
OnBinaryMessageFn,
|
|
||||||
OnConnectFn,
|
|
||||||
StringMessageReceivedEvent,
|
|
||||||
BinaryMessageReceivedEvent,
|
|
||||||
NymClientConfig,
|
NymClientConfig,
|
||||||
|
OnRawPayloadFn,
|
||||||
|
StringMessageReceivedEvent,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { EventKinds } from './types';
|
import { EventKinds, MimeTypes } from './types';
|
||||||
|
|
||||||
// web workers are only allowed to load external scripts as the load
|
// 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...');
|
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.
|
* Helper method to send typed messages.
|
||||||
* @param event The strongly typed message to send back to the calling thread.
|
* @param event The strongly typed message to send back to the calling thread.
|
||||||
*/
|
*/
|
||||||
const postMessageWithType = <E>(event: E) => self.postMessage(event);
|
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.
|
* This class holds the state of the Nym WASM client and provides any interop needed.
|
||||||
*/
|
*/
|
||||||
class ClientWrapper {
|
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.
|
* Creates the WASM client and initialises it.
|
||||||
*/
|
*/
|
||||||
init = (
|
init = (config: Config, onRawPayloadHandler?: OnRawPayloadFn) => {
|
||||||
config: wasm_bindgen.Config,
|
const onMessageHandler = (message: Uint8Array) => {
|
||||||
onConnectHandler: OnConnectFn,
|
try {
|
||||||
onStringMessageHandler?: OnStringMessageFn,
|
if (onRawPayloadHandler) {
|
||||||
onBinaryMessageHandler?: OnBinaryMessageFn,
|
onRawPayloadHandler(message);
|
||||||
) => {
|
}
|
||||||
this.client = new wasm_bindgen.NymClient(config);
|
} catch (e) {
|
||||||
if (onBinaryMessageHandler) {
|
console.error('Unhandled exception in `ClientWrapper.onRawPayloadHandler`: ', e);
|
||||||
this.client.set_on_binary_message(onBinaryMessageHandler);
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// NB: because we set the `kind` byte in the message payload first, we don't need to bother to try to parse
|
this.builder = new NymClientBuilder(config, onMessageHandler);
|
||||||
// all messages as string
|
|
||||||
// if (onStringMessageHandler) {
|
|
||||||
// this.client.set_on_message(onStringMessageHandler);
|
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* Returns the address of this client.
|
||||||
*/
|
*/
|
||||||
@@ -80,47 +105,51 @@ class ClientWrapper {
|
|||||||
* Connects to the gateway and starts the client sending traffic.
|
* Connects to the gateway and starts the client sending traffic.
|
||||||
*/
|
*/
|
||||||
start = async () => {
|
start = async () => {
|
||||||
if (!this.client) {
|
if (!this.builder) {
|
||||||
console.error('Client has not been initialised. Please call `init` first.');
|
console.error('Client config has not been initialised. Please call `init` first.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is current limitation of wasm in rust - for async methods you can't take self by reference...
|
// 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
|
// 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)
|
// 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) {
|
if (!this.client) {
|
||||||
console.error('Client has not been initialised. Please call `init` first.');
|
console.error('Client has not been initialised. Please call `init` first.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const message = wasm_bindgen.create_binary_message_from_string(PAYLOAD_KIND_TEXT, payload);
|
this.client.free();
|
||||||
this.client = await this.client.send_binary_message(message, recipient);
|
this.client = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
sendBinaryMessage = async ({
|
send = async ({
|
||||||
payload,
|
payload,
|
||||||
recipient,
|
recipient,
|
||||||
headers,
|
replySurbs = 0,
|
||||||
}: {
|
}: {
|
||||||
recipient: string;
|
recipient: string;
|
||||||
payload: Uint8Array;
|
payload: Uint8Array;
|
||||||
headers?: string;
|
replySurbs?: number;
|
||||||
}) => {
|
}) => {
|
||||||
if (!this.client) {
|
if (!this.client) {
|
||||||
console.error('Client has not been initialised. Please call `init` first.');
|
console.error('Client has not been initialised. Please call `init` first.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const message = wasm_bindgen.create_binary_message_with_headers(PAYLOAD_KIND_BINARY, payload, headers || '');
|
// TODO: currently we don't do anything with the result, it needs some typing and exposed back on the main thread
|
||||||
this.client = await this.client.send_binary_message(message, recipient);
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const res = await this.client.send_anonymous_message(payload, recipient, replySurbs);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// load WASM binary
|
// load WASM binary
|
||||||
wasm_bindgen(wasmUrl)
|
init(wasmBytes())
|
||||||
.then((importResult) => {
|
.then((importResult: any) => {
|
||||||
// sets up better stack traces in case of in-rust panics
|
// sets up better stack traces in case of in-rust panics
|
||||||
importResult.set_panic_hook();
|
importResult.set_panic_hook();
|
||||||
|
|
||||||
@@ -129,48 +158,34 @@ wasm_bindgen(wasmUrl)
|
|||||||
|
|
||||||
const startHandler = async (config: NymClientConfig) => {
|
const startHandler = async (config: NymClientConfig) => {
|
||||||
// fetch the gateway details (randomly chosen if no preferred gateway is set)
|
// fetch the gateway details (randomly chosen if no preferred gateway is set)
|
||||||
const gatewayEndpoint = await wasm_bindgen.get_gateway(
|
const gatewayEndpoint = await get_gateway(config.nymApiUrl, config.preferredGatewayIdentityKey);
|
||||||
config.nymApiUrl,
|
|
||||||
config.preferredGatewayIdentityKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
// set a different gatewayListener in order to avoid workaround ws over https error
|
// set a different gatewayListener in order to avoid workaround ws over https error
|
||||||
if (config.gatewayListener)
|
if (config.gatewayListener) gatewayEndpoint.gateway_listener = config.gatewayListener;
|
||||||
gatewayEndpoint.gateway_listener = config.gatewayListener;
|
|
||||||
|
|
||||||
// create the client, passing handlers for events
|
// create the client, passing handlers for events
|
||||||
wrapper.init(
|
wrapper.init(
|
||||||
new wasm_bindgen.Config(
|
new Config(config.clientId, config.nymApiUrl, gatewayEndpoint, config.debug || default_debug()),
|
||||||
config.clientId,
|
|
||||||
config.nymApiUrl,
|
|
||||||
gatewayEndpoint,
|
|
||||||
config.debug || wasm_bindgen.default_debug(),
|
|
||||||
),
|
|
||||||
() => {
|
|
||||||
console.log();
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
async (message) => {
|
async (message) => {
|
||||||
try {
|
try {
|
||||||
const { kind, payload, headers } = await wasm_bindgen.parse_binary_message_with_headers(message);
|
const decodedPayload = decode_payload(message);
|
||||||
switch (kind) {
|
const { payload, headers } = decodedPayload;
|
||||||
case PAYLOAD_KIND_TEXT: {
|
const mimeType = decodedPayload.mimeType as MimeTypes;
|
||||||
const stringMessage = await wasm_bindgen.parse_string_message_with_headers(message);
|
|
||||||
postMessageWithType<StringMessageReceivedEvent>({
|
if (wrapper.getTextMimeTypes().includes(mimeType)) {
|
||||||
kind: EventKinds.StringMessageReceived,
|
const stringMessage = parse_utf8_string(payload);
|
||||||
args: { kind, payload: stringMessage.payload },
|
|
||||||
});
|
postMessageWithType<StringMessageReceivedEvent>({
|
||||||
break;
|
kind: EventKinds.StringMessageReceived,
|
||||||
}
|
args: { mimeType, payload: stringMessage, payloadRaw: payload, headers },
|
||||||
case PAYLOAD_KIND_BINARY:
|
});
|
||||||
postMessageWithType<BinaryMessageReceivedEvent>({
|
return;
|
||||||
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 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postMessageWithType<BinaryMessageReceivedEvent>({
|
||||||
|
kind: EventKinds.BinaryMessageReceived,
|
||||||
|
args: { mimeType, payload, headers },
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to parse binary message', e);
|
console.error('Failed to parse binary message', e);
|
||||||
}
|
}
|
||||||
@@ -191,14 +206,43 @@ wasm_bindgen(wasmUrl)
|
|||||||
console.log('[Nym WASM client] Starting...', { config });
|
console.log('[Nym WASM client] Starting...', { config });
|
||||||
startHandler(config).catch((e) => console.error('[Nym WASM client] Failed to start', e));
|
startHandler(config).catch((e) => console.error('[Nym WASM client] Failed to start', e));
|
||||||
},
|
},
|
||||||
|
stop() {
|
||||||
|
wrapper.stop();
|
||||||
|
},
|
||||||
selfAddress() {
|
selfAddress() {
|
||||||
return wrapper.selfAddress();
|
return wrapper.selfAddress();
|
||||||
},
|
},
|
||||||
sendMessage(args) {
|
setTextMimeTypes(mimeTypes) {
|
||||||
wrapper.sendMessage(args).catch((e) => console.error('[Nym WASM client] Failed to send message', e));
|
wrapper.setTextMimeTypes(mimeTypes);
|
||||||
},
|
},
|
||||||
sendBinaryMessage(args) {
|
getTextMimeTypes() {
|
||||||
wrapper.sendBinaryMessage(args).catch((e) => console.error('[Nym WASM client] Failed to send message', e));
|
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));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||