diff --git a/.cargo/config.toml b/.cargo/config.toml
index 35033f3..dc382b6 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -3,4 +3,4 @@ rustflags = ["--cfg=web_sys_unstable_apis"]
 
 # Enable for testing WASM-only stuff
 [build]
-#target = "wasm32-unknown-unknown"
+target = "wasm32-unknown-unknown"
diff --git a/Cargo.toml b/Cargo.toml
index aecea99..e72af94 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -45,13 +45,16 @@ getrandom = { version = "0.2", features = ["js"] }
 des = "0.8"
 cbc = "0.1"
 ecb = "0.1"
-tokio = { version = "1.36", features = ["sync"] }
 g2-unicode-jp = "0.4.1"
 thiserror = "1.0.57"
 phf = { version = "0.11.2", features = ["phf_macros", "macros"] }
 byteorder = "1.5.0"
 log = "0.4.22"
 
+[target.'cfg(not(target_family = "wasm"))'.dependencies]
+tokio = { version = "1.36", features = ["sync"] }
+
 [target.'cfg(target_family = "wasm")'.dependencies]
-gloo = { version = "0.11.0", features = ["futures", "worker"] }
-futures = "0.3.30"
+gloo = { version = "0.11", features = ["futures", "worker"] }
+futures = "0.3"
+serde = { version = "1.0", features = ["derive"] }
diff --git a/src/netmd/encryption.rs b/src/netmd/encryption.rs
index fb1b9ab..2c2f25a 100644
--- a/src/netmd/encryption.rs
+++ b/src/netmd/encryption.rs
@@ -1,21 +1,28 @@
 use cbc::cipher::block_padding::NoPadding;
 use cbc::cipher::{BlockDecryptMut, BlockEncryptMut, KeyInit, KeyIvInit};
 use rand::RngCore;
+use super::interface::DataEncryptorInput;
+
+#[cfg(not(target_family = "wasm"))]
 use std::thread;
+#[cfg(not(target_family = "wasm"))]
 use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
 
-use super::interface::DataEncryptorInput;
+#[cfg(target_family = "wasm")]
+use futures::{SinkExt, StreamExt};
+#[cfg(target_family = "wasm")]
+use gloo::worker::reactor::{reactor, ReactorBridge, ReactorScope};
 
 type DesEcbEnc = ecb::Decryptor<des::Des>;
 type DesCbcEnc = cbc::Encryptor<des::Des>;
 
+#[cfg(not(target_family = "wasm"))]
 pub fn new_thread_encryptor(
-    _input: DataEncryptorInput,
+    input: DataEncryptorInput,
 ) -> UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)> {
     let (tx, rx) = unbounded_channel::<(Vec<u8>, Vec<u8>, Vec<u8>)>();
-    let input = Box::from(_input);
 
-    thread::spawn(move || {
+    let _ = thread::spawn(move || {
         let mut iv = [0u8; 8];
 
         // Create the random key
@@ -76,3 +83,81 @@ pub fn new_thread_encryptor(
 
     rx
 }
+
+
+#[cfg(target_family = "wasm")]
+pub fn web_worker_encryptor(
+    input: DataEncryptorInput,
+) -> ReactorBridge<WebThread> {
+    use gloo::worker::Spawnable;
+
+    let bridge = WebThread::spawner().spawn("...");
+    bridge.send_input(input);
+
+    bridge
+}
+
+#[cfg(target_family = "wasm")]
+#[reactor]
+pub async fn WebThread(
+    mut scope: ReactorScope<DataEncryptorInput, (Vec<u8>, Vec<u8>, Vec<u8>)>
+) {
+    // Get the initial input data
+    let input = scope.next().await.unwrap();
+
+    let mut iv = [0u8; 8];
+
+    // Create the random key
+    let mut random_key = [0u8; 8];
+    rand::thread_rng().fill_bytes(&mut random_key);
+
+    // Encrypt it with the kek
+    let mut encrypted_random_key = random_key;
+    if let Err(x) = DesEcbEnc::new(&input.kek.into())
+        .decrypt_padded_mut::<NoPadding>(&mut encrypted_random_key)
+    {
+        panic!("Cannot create main key {:?}", x)
+    };
+
+    let default_chunk_size = match input.chunk_size {
+        0 => 0x00100000,
+        e => e,
+    };
+
+    let mut packet_count = 0u32;
+    let mut current_chunk_size;
+
+    let mut input_data = input.data.clone();
+    if (input_data.len() % input.frame_size) != 0 {
+        let padding_remaining = input.frame_size - (input_data.len() % input.frame_size);
+        input_data.extend(std::iter::repeat(0).take(padding_remaining));
+    }
+    let input_data_length = input_data.len();
+
+    let mut offset: usize = 0;
+    while offset < input_data_length {
+        if packet_count > 0 {
+            current_chunk_size = default_chunk_size;
+        } else {
+            current_chunk_size = default_chunk_size - 24;
+        }
+
+        current_chunk_size = std::cmp::min(current_chunk_size, input_data_length - offset);
+
+        let this_data_chunk = &mut input_data[offset..offset + current_chunk_size];
+        DesCbcEnc::new(&random_key.into(), &iv.into())
+            .encrypt_padded_mut::<NoPadding>(this_data_chunk, current_chunk_size)
+            .unwrap();
+
+        scope.send((
+            encrypted_random_key.to_vec(),
+            iv.to_vec(),
+            this_data_chunk.to_vec(),
+        )).await.unwrap();
+
+        iv.copy_from_slice(&this_data_chunk[this_data_chunk.len() - 8..]);
+
+        packet_count += 1;
+        offset += current_chunk_size;
+    }
+}
diff --git a/src/netmd/interface.rs b/src/netmd/interface.rs
index 6057276..1bcbc27 100644
--- a/src/netmd/interface.rs
+++ b/src/netmd/interface.rs
@@ -15,7 +15,6 @@ use std::collections::HashMap;
 use std::error::Error;
 use std::time::Duration;
 use thiserror::Error;
-use tokio::sync::mpsc::UnboundedReceiver;
 
 use super::base::NetMD;
 use super::utils::{cross_sleep, to_sjis};
@@ -1691,7 +1690,10 @@ impl NetMDInterface {
         frames: u32,
         pkt_size: u32,
         // key, iv, data
-        mut packets: UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)>,
+        #[cfg(not(target_family = "wasm"))]
+        mut packets: tokio::sync::mpsc::UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)>,
+        #[cfg(target_family = "wasm")]
+        mut packets: gloo::worker::reactor::ReactorBridge<super::encryption::WebThread>,
         hex_session_key: &[u8],
         progress_callback: F,
     ) -> Result<(u16, Vec<u8>, Vec<u8>), InterfaceError>
@@ -1730,7 +1732,15 @@ impl NetMDInterface {
         let mut written_bytes = 0;
         let mut packet_count = 0;
 
-        while let Some((key, iv, data)) = packets.recv().await {
+        while let Some((key, iv, data)) = {
+            {
+            #[cfg(not(target_family = "wasm"))]
+            packets.recv().await
+            }
+
+            #[cfg(target_family = "wasm")]
+            futures::StreamExt::next(&mut packets).await
+        } {
             let binpack = if packet_count == 0 {
                 let packed_length: Vec<u8> = pkt_size.to_be_bytes().to_vec();
                 [vec![0, 0, 0, 0], packed_length, key, iv, data].concat()
@@ -1742,7 +1752,6 @@ impl NetMDInterface {
             packet_count += 1;
             (progress_callback)(total_bytes, written_bytes);
             if total_bytes == written_bytes {
-                packets.close();
                 break;
             }
         }
@@ -1863,12 +1872,9 @@ pub struct MDTrack {
     pub data: Vec<u8>,
     pub chunk_size: usize,
     pub full_width_title: Option<String>,
-
-    #[allow(clippy::type_complexity)]
-    pub encrypt_packets_iterator:
-        Box<dyn Fn(DataEncryptorInput) -> UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)>>,
 }
 
+#[cfg_attr(target_family = "wasm", derive(serde::Serialize, serde::Deserialize))]
 pub struct DataEncryptorInput {
     pub kek: [u8; 8],
     pub frame_size: usize,
@@ -1921,8 +1927,19 @@ impl MDTrack {
         [0x14, 0xe3, 0x83, 0x4e, 0xe2, 0xd3, 0xcc, 0xa5]
     }
 
-    pub fn get_encrypting_iterator(&mut self) -> UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)> {
-        (self.encrypt_packets_iterator)(DataEncryptorInput {
+    #[cfg(not(target_family = "wasm"))]
+    pub fn get_encrypting_iterator(&mut self) -> tokio::sync::mpsc::UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)> {
+        super::encryption::new_thread_encryptor(DataEncryptorInput {
+            kek: self.get_kek(),
+            frame_size: self.frame_size(),
+            chunk_size: self.chunk_size(),
+            data: std::mem::take(&mut self.data),
+        })
+    }
+
+    #[cfg(target_family = "wasm")]
+    pub fn get_encrypting_iterator(&mut self) -> gloo::worker::reactor::ReactorBridge<super::encryption::WebThread> {
+        super::encryption::web_worker_encryptor(DataEncryptorInput {
             kek: self.get_kek(),
             frame_size: self.frame_size(),
             chunk_size: self.chunk_size(),