Skip to content

Commit

Permalink
initial wasm32 'support' (#443)
Browse files Browse the repository at this point in the history
* Give tungstenite types distinct names

* reorganize files

* Better feature locking, add wasm.rs

* Implement wasm Backend

* add wasm-bindgen-test

* Build & Test for wasm

* Add macos safari wasm test

* Add wasm32 target

* Add wasm.rs test

* Move wasm-pack installation before test execution

* Fix build on wasm32

* Fix examples depending on tokio::time

* fix clippy warn

* Add example wasm bindgen test

* Add wasm-bindgen to Cargo.toml

* Add wasm test configuration

* Install wasm-bindgen-cli on linux

* Add  wasm-bindgen-cli to macos

* Correct "vers" to "version"

* Attempt to locate correct geckodriver

* Run wasm tests first

* maybe this will fix ci :clueless:

* Move wasm-bindgen-cli install

* Add cargo-binstall installation script for
wasm-bindgen-cli

* Try using only one browser

* remove geckodriver

* Move all wasm related tests to macos

* Rename macOS test step for clarity

* Try out combined coverage report

* try different strategy to skip coverage on forks

* Revert "try different strategy to skip coverage on forks"

This reverts commit fb46ab8.

* Revert "Try out combined coverage report"

This reverts commit d34a813.
  • Loading branch information
bitfl0wer authored Nov 20, 2023
1 parent 5dbb3b1 commit 06f3046
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 35 deletions.
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.wasm32-unknown-unknown]
runner = 'wasm-bindgen-test-runner'
34 changes: 32 additions & 2 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ env:
CARGO_TERM_COLOR: always

jobs:
rust:
linux:

runs-on: ubuntu-latest

Expand All @@ -33,6 +33,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
cache-all-crates: "true"
prefix-key: "linux"
- name: Build, Test and Publish Coverage
run: |
if [ -n "${{ secrets.COVERALLS_REPO_TOKEN }}" ]; then
Expand All @@ -44,4 +45,33 @@ jobs:
cargo build --verbose --all-features
cargo test --verbose --all-features
fi
macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Clone spacebar server
run: |
git clone https://github.com/bitfl0wer/server.git
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
cache-dependency-path: server/package-lock.json
- name: Prepare and start Spacebar server
run: |
npm install
npm run setup
npm run start &
working-directory: ./server
- uses: Swatinem/rust-cache@v2
with:
cache-all-crates: "true"
prefix-key: "macos"
- name: Run WASM tests with Safari, Firefox, Chrome
run: |
rustup target add wasm32-unknown-unknown
curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
cargo binstall --no-confirm wasm-bindgen-cli --version "0.2.88" --force
SAFARIDRIVER=$(which safaridriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt"
GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt"
CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown --no-default-features --features="client, rt"
44 changes: 43 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@ sqlx = { version = "0.7.1", features = [
], optional = true }
safina-timer = "0.1.11"
rand = "0.8.5"
# TODO: Remove the below 2 imports for production!
ws_stream_wasm = "0.7.4"
pharos = "0.5.3"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rustls = "0.21.8"
Expand All @@ -70,10 +67,10 @@ hostname = "0.3.1"

[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.11", features = ["js"] }
tokio-tungstenite = { version = "0.20.1", default-features = false }
ws_stream_wasm = "0.7.4"
pharos = "0.5.3"


[dev-dependencies]
lazy_static = "1.4.0"
wasm-bindgen-test = "0.3.38"
wasm-bindgen = "0.2.88"
5 changes: 3 additions & 2 deletions examples/gateway_observers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use chorus::{
types::{GatewayIdentifyPayload, GatewayReady},
};
use std::{sync::Arc, time::Duration};
use tokio::{self, time::sleep};
use tokio::{self};

// This example creates a simple gateway connection and a basic observer struct

Expand Down Expand Up @@ -54,9 +54,10 @@ async fn main() {
let mut identify = GatewayIdentifyPayload::common();
identify.token = token;
gateway.send_identify(identify).await;
safina_timer::start_timer_thread();

// Do something on the main thread so we don't quit
loop {
sleep(Duration::MAX).await;
safina_timer::sleep_for(Duration::MAX).await
}
}
7 changes: 3 additions & 4 deletions examples/gateway_simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use std::time::Duration;

use chorus::gateway::Gateway;
use chorus::{self, types::GatewayIdentifyPayload};
use tokio::time::sleep;

/// This example creates a simple gateway connection and a session with an Identify event
#[tokio::main(flavor = "current_thread")]
Expand All @@ -11,7 +10,7 @@ async fn main() {
let websocket_url_spacebar = "wss://gateway.old.server.spacebar.chat/".to_string();

// Initiate the gateway connection, starting a listener in one thread and a heartbeat handler in another
let gateway = Gateway::spawn(websocket_url_spacebar).await.unwrap();
let _ = Gateway::spawn(websocket_url_spacebar).await.unwrap();

// At this point, we are connected to the server and are sending heartbeats, however we still haven't authenticated

Expand All @@ -27,10 +26,10 @@ async fn main() {
identify.token = token;

// Send off the event
gateway.send_identify(identify).await;
safina_timer::start_timer_thread();

// Do something on the main thread so we don't quit
loop {
sleep(Duration::MAX).await;
safina_timer::sleep_for(Duration::MAX).await
}
}
23 changes: 23 additions & 0 deletions src/gateway/backends/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#[cfg(all(not(target_arch = "wasm32"), feature = "client"))]
pub mod tungstenite;
#[cfg(all(not(target_arch = "wasm32"), feature = "client"))]
pub use tungstenite::*;

#[cfg(all(target_arch = "wasm32", feature = "client"))]
pub mod wasm;
#[cfg(all(target_arch = "wasm32", feature = "client"))]
pub use wasm::*;

#[cfg(all(not(target_arch = "wasm32"), feature = "client"))]
pub type Sink = tungstenite::TungsteniteSink;
#[cfg(all(not(target_arch = "wasm32"), feature = "client"))]
pub type Stream = tungstenite::TungsteniteStream;
#[cfg(all(not(target_arch = "wasm32"), feature = "client"))]
pub type WebSocketBackend = tungstenite::TungsteniteBackend;

#[cfg(all(target_arch = "wasm32", feature = "client"))]
pub type Sink = wasm::WasmSink;
#[cfg(all(target_arch = "wasm32", feature = "client"))]
pub type Stream = wasm::WasmStream;
#[cfg(all(target_arch = "wasm32", feature = "client"))]
pub type WebSocketBackend = wasm::WasmBackend;
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,21 @@ use tokio_tungstenite::{
connect_async_tls_with_config, tungstenite, Connector, MaybeTlsStream, WebSocketStream,
};

use super::GatewayMessage;
use crate::errors::GatewayError;
use crate::gateway::GatewayMessage;

#[derive(Debug, Clone)]
pub struct WebSocketBackend;
pub struct TungsteniteBackend;

// These could be made into inherent associated types when that's stabilized
pub type WsSink = SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, tungstenite::Message>;
pub type WsStream = SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>;
pub type TungsteniteSink =
SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, tungstenite::Message>;
pub type TungsteniteStream = SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>;

impl WebSocketBackend {
impl TungsteniteBackend {
pub async fn connect(
websocket_url: &str,
) -> Result<(WsSink, WsStream), crate::errors::GatewayError> {
) -> Result<(TungsteniteSink, TungsteniteStream), crate::errors::GatewayError> {
let mut roots = rustls::RootCertStore::empty();
for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs")
{
Expand Down
50 changes: 50 additions & 0 deletions src/gateway/backends/wasm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use futures_util::{
stream::{SplitSink, SplitStream},
StreamExt,
};

use ws_stream_wasm::*;

use crate::errors::GatewayError;
use crate::gateway::GatewayMessage;

#[derive(Debug, Clone)]
pub struct WasmBackend;

// These could be made into inherent associated types when that's stabilized
pub type WasmSink = SplitSink<WsStream, WsMessage>;
pub type WasmStream = SplitStream<WsStream>;

impl WasmBackend {
pub async fn connect(
websocket_url: &str,
) -> Result<(WasmSink, WasmStream), crate::errors::GatewayError> {
let (_, websocket_stream) = match WsMeta::connect(websocket_url, None).await {
Ok(stream) => Ok(stream),
Err(e) => Err(GatewayError::CannotConnect {
error: e.to_string(),
}),
}?;

Ok(websocket_stream.split())
}
}

impl From<GatewayMessage> for WsMessage {
fn from(message: GatewayMessage) -> Self {
Self::Text(message.0)
}
}

impl From<WsMessage> for GatewayMessage {
fn from(value: WsMessage) -> Self {
match value {
WsMessage::Text(text) => Self(text),
WsMessage::Binary(bin) => {
let mut text = String::new();
let _ = bin.iter().map(|v| text.push_str(&v.to_string()));
Self(text)
}
}
}
}
16 changes: 13 additions & 3 deletions src/gateway/gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use tokio::task;

use self::event::Events;
use super::*;
use super::{WsSink, WsStream};
use super::{Sink, Stream};
use crate::types::{
self, AutoModerationRule, AutoModerationRuleUpdate, Channel, ChannelCreate, ChannelDelete,
ChannelUpdate, Guild, GuildRoleCreate, GuildRoleUpdate, JsonField, RoleObject, SourceUrlField,
Expand All @@ -17,8 +17,8 @@ use crate::types::{
pub struct Gateway {
events: Arc<Mutex<Events>>,
heartbeat_handler: HeartbeatHandler,
websocket_send: Arc<Mutex<WsSink>>,
websocket_receive: WsStream,
websocket_send: Arc<Mutex<Sink>>,
websocket_receive: Stream,
kill_send: tokio::sync::broadcast::Sender<()>,
store: Arc<Mutex<HashMap<Snowflake, Arc<RwLock<ObservableObject>>>>>,
url: String,
Expand All @@ -37,7 +37,10 @@ impl Gateway {

// Wait for the first hello and then spawn both tasks so we avoid nested tasks
// This automatically spawns the heartbeat task, but from the main thread
#[cfg(not(target_arch = "wasm32"))]
let msg: GatewayMessage = websocket_receive.next().await.unwrap().unwrap().into();
#[cfg(target_arch = "wasm32")]
let msg: GatewayMessage = websocket_receive.next().await.unwrap().into();
let gateway_payload: types::GatewayReceivePayload = serde_json::from_str(&msg.0).unwrap();

if gateway_payload.op_code != GATEWAY_HELLO {
Expand Down Expand Up @@ -91,11 +94,18 @@ impl Gateway {
loop {
let msg = self.websocket_receive.next().await;

// PRETTYFYME: Remove inline conditional compiling
// This if chain can be much better but if let is unstable on stable rust
#[cfg(not(target_arch = "wasm32"))]
if let Some(Ok(message)) = msg {
self.handle_message(message.into()).await;
continue;
}
#[cfg(target_arch = "wasm32")]
if let Some(message) = msg {
self.handle_message(message.into()).await;
continue;
}

// We couldn't receive the next message or it was an error, something is wrong with the websocket, close
warn!("GW: Websocket is broken, stopping gateway");
Expand Down
2 changes: 1 addition & 1 deletion src/gateway/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::types::{self, Composite};
pub struct GatewayHandle {
pub url: String,
pub events: Arc<Mutex<Events>>,
pub websocket_send: Arc<Mutex<WsSink>>,
pub websocket_send: Arc<Mutex<Sink>>,
/// Tells gateway tasks to close
pub(super) kill_send: tokio::sync::broadcast::Sender<()>,
pub(crate) store: Arc<Mutex<HashMap<Snowflake, Arc<RwLock<ObservableObject>>>>>,
Expand Down
4 changes: 2 additions & 2 deletions src/gateway/heartbeat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub(super) struct HeartbeatHandler {
impl HeartbeatHandler {
pub fn new(
heartbeat_interval: Duration,
websocket_tx: Arc<Mutex<WsSink>>,
websocket_tx: Arc<Mutex<Sink>>,
kill_rc: tokio::sync::broadcast::Receiver<()>,
) -> Self {
let (send, receive) = tokio::sync::mpsc::channel(32);
Expand All @@ -49,7 +49,7 @@ impl HeartbeatHandler {
/// Can be killed by the kill broadcast;
/// If the websocket is closed, will die out next time it tries to send a heartbeat;
pub async fn heartbeat_task(
websocket_tx: Arc<Mutex<WsSink>>,
websocket_tx: Arc<Mutex<Sink>>,
heartbeat_interval: Duration,
mut receive: Receiver<HeartbeatThreadCommunication>,
mut kill_receive: tokio::sync::broadcast::Receiver<()>,
Expand Down
Loading

0 comments on commit 06f3046

Please sign in to comment.