#![allow(warnings)]
use deno_core::{cppgc, Extension, JsRuntime, PollEventLoopOptions, RuntimeOptions, op2, *};
use deno_error::JsErrorBox;
use std::path;
use std::{path::Path, string, vec};
use deno_core::v8;
use base64::{Engine as _, engine::general_purpose,alphabet};
use tokio::time::{sleep, Duration};
use tokio::net::TcpListener;
use std::borrow::Cow;
use std::cell::RefCell;
use std::rc::Rc;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use tokio::sync::Mutex;
#[derive(serde::Serialize, serde::Deserialize)]
struct TextEncoder;
unsafe impl GarbageCollected for TextEncoder {
fn trace(&self, _tracer: &mut v8::cppgc::Visitor) {}
fn get_name(&self) -> &'static std::ffi::CStr {
std::ffi::CStr::from_bytes_with_nul(b"TextEncoder\0").unwrap()
}
}
#[op2]
fn text_encoder_new<'s, 'i>(
scope: &mut v8::PinScope<'s, 'i>,
) -> v8::Local<'s, v8::Object> {
cppgc::make_cppgc_object(scope, TextEncoder {})
}
#[op2]
#[buffer]
fn text_encoder_encode(
#[cppgc] _encoder: &TextEncoder,
#[string] input: String,
) -> Vec<u8> {
input.into_bytes()
}
extension!(
text_encoder_extension,
ops = [
text_encoder_new,
text_encoder_encode,
],
esm_entry_point = "internal:text_encoder_bootstrap",
esm = [
dir "src/js",
"internal:text_encoder_bootstrap" = "TextEncoder.js",
],
);
#[derive(serde::Serialize, serde::Deserialize)]
struct TextDecoder;
unsafe impl GarbageCollected for TextDecoder {
fn trace(&self, _tracer: &mut v8::cppgc::Visitor) {}
fn get_name(&self) -> &'static std::ffi::CStr {
std::ffi::CStr::from_bytes_with_nul(b"TextDecoder\0").unwrap()
}
}
#[op2]
fn text_decoder_new<'s, 'i>(
scope: &mut v8::PinScope<'s, 'i>,
) -> v8::Local<'s, v8::Object> {
cppgc::make_cppgc_object(scope, TextDecoder {})
}
#[op2]
#[string]
fn text_decoder_decode(
#[cppgc] _decoder: &TextDecoder,
#[buffer] input: &[u8],
) -> String {
String::from_utf8(input.to_vec()).unwrap()
}
extension!(
text_decoder_extension,
ops = [
text_decoder_new,
text_decoder_decode,
],
esm_entry_point = "internal:text_decoder_bootstrap",
esm = [
dir "src/js",
"internal:text_decoder_bootstrap" = "TextDecoder.js",
],
);
#[op2]
#[string]
fn to_base64(
#[buffer] input: &[u8],
#[string] alphabet_type: String,
omit_padding: bool,
) -> String {
let alphabet = if alphabet_type == "base64url" {
alphabet::URL_SAFE
} else {
alphabet::STANDARD
};
let config = if omit_padding {
general_purpose::NO_PAD
} else {
general_purpose::PAD
};
let engine = general_purpose::GeneralPurpose::new(&alphabet, config);
engine.encode(input)
}
#[op2]
#[buffer]
fn from_base64(
#[string] input: String,
#[string] alphabet_type: String,
omit_padding: bool,
) -> Result<Vec<u8>, JsErrorBox> {
if !input.is_ascii() {
return Err(JsErrorBox::generic("Input must be ASCII Base64"));
}
let alphabet = if alphabet_type == "base64url" {
alphabet::URL_SAFE
} else {
alphabet::STANDARD
};
println!("alphabet: {:?}", alphabet_type);
let engine = general_purpose::GeneralPurpose::new(&alphabet, general_purpose::PAD);
match engine.decode(input.trim()) {
Ok(bytes) => Ok(bytes),
Err(err) =>{
println!("Base64 decode error: {}", err);
return Err(JsErrorBox::generic("Invalid Base64 input"));
}
}
}
extension!(
base64_ext,
ops = [to_base64, from_base64],
esm_entry_point = "internal:base64_bootstrap",
esm = [
dir "src/js",
"internal:base64_bootstrap" = "Base64.js",
],
);
#[op2(async(deferred), fast)]
pub async fn op_sleep_interval(
ms: u32,
) {
sleep(Duration::from_millis(ms as u64)).await;
}
#[op2(async(deferred), fast)]
pub async fn op_sleep_timeout(
ms: u32,
) {
sleep(Duration::from_millis(ms as u64)).await;
}
struct TcpListenerResource(TcpListener);
impl Resource for TcpListenerResource {
fn name(&self) -> Cow<str> {
"tcpListener".into()
}
}
extension!(
setinterval_ext,
ops = [op_sleep_interval],
esm_entry_point = "internal:setinterval_bootstrap",
esm = [
dir "src/js",
"internal:setinterval_bootstrap" = "SetInterval.js",
],
);
extension!(
settimeout_ext,
ops = [op_sleep_timeout],
esm_entry_point = "internal:settimeout_bootstrap",
esm = [
dir "src/js",
"internal:settimeout_bootstrap" = "SetTimeout.js",
],
);
#[op2(fast)]
#[smi] pub fn op_net_listen(
state: &mut OpState,
#[string] addr: String,
) -> Result<ResourceId, JsErrorBox> {
let std_listener = std::net::TcpListener::bind(addr)
.map_err(|e| JsErrorBox::generic(format!("failed to bind: {}", e)))?;
std_listener.set_nonblocking(true)
.map_err(|e| JsErrorBox::generic(format!("failed to set non-blocking: {}", e)))?;
let listener = TcpListener::from_std(std_listener).map_err(|e| JsErrorBox::generic(format!("failed to create TcpListener: {}", e)))?;
let rid = state.resource_table.add(TcpListenerResource(listener));
Ok(rid)
}
extension!(
event_emitter_ext,
esm_entry_point = "internal:event_emitter_bootstrap",
esm = [
dir "src/js",
"internal:event_emitter_bootstrap" = "EventEmitter.js",
],
);
struct TcpStreamResource {
rd: tokio::sync::Mutex<tokio::net::tcp::OwnedReadHalf>,
wr: tokio::sync::Mutex<tokio::net::tcp::OwnedWriteHalf>,
}
impl Resource for TcpStreamResource {
fn name(&self) -> Cow<str> {
"tcpStream".into()
}
}
#[op2(async(deferred))]
#[smi]
pub async fn op_net_accept(
state: Rc<RefCell<OpState>>,
rid: ResourceId,
) -> Result<ResourceId, JsErrorBox> {
let listener = state.borrow().resource_table.get::<TcpListenerResource>(rid).map_err(|e| JsErrorBox::generic(format!("failed to get TcpListenerResource: {}", e)))?;
let (stream, _) = listener.0.accept().await
.map_err(|e| JsErrorBox::generic(e.to_string()))?;
let (rd, wr) = stream.into_split();
let rid = state.borrow_mut().resource_table.add(TcpStreamResource {
rd: tokio::sync::Mutex::new(rd),
wr: tokio::sync::Mutex::new(wr),
});
Ok(rid)
}
#[op2(fast)]
pub fn op_net_close(
state: &mut OpState,
#[smi] rid: ResourceId,
) -> Result<(), JsErrorBox> {
state.resource_table.close(rid)
.map_err(|_| JsErrorBox::generic(format!("Resource ID {} not found", rid)))?;
Ok(())
}
#[op2(async(deferred))]
#[smi]
pub async fn op_net_read(
state: Rc<RefCell<OpState>>,
#[smi] rid: ResourceId,
#[buffer] mut buf: JsBuffer, ) -> Result<u32, JsErrorBox> {
let resource = state
.borrow()
.resource_table
.get::<TcpStreamResource>(rid)
.map_err(|e| JsErrorBox::generic(e.to_string()))?;
let mut stream = resource.rd.lock().await;
use tokio::io::AsyncReadExt;
let nread = stream.read(&mut buf).await
.map_err(|e| JsErrorBox::generic(e.to_string()))?;
Ok(nread as u32)
}
#[op2(async(deferred))]
#[smi]
pub async fn op_net_write(
state: Rc<RefCell<OpState>>,
#[smi] rid: ResourceId,
#[buffer] buf: JsBuffer,
) -> Result<u32, JsErrorBox> {
let resource = state
.borrow()
.resource_table
.get::<TcpStreamResource>(rid)
.map_err(|e| JsErrorBox::generic(e.to_string()))?;
let mut stream = resource.wr.lock().await;
stream
.write_all(buf.as_ref())
.await
.map_err(|e| JsErrorBox::generic(e.to_string()))?;
Ok(buf.len() as u32)
}
#[op2]
#[serde] pub fn op_net_server_address(
state: &mut OpState,
#[smi] rid: ResourceId,
) -> Result<serde_json::Value, JsErrorBox> {
let listener = state.resource_table.get::<TcpListenerResource>(rid)
.map_err(|e| JsErrorBox::generic(e.to_string()))?;
let addr = listener.0.local_addr()
.map_err(|e| JsErrorBox::generic(e.to_string()))?;
Ok(serde_json::json!({
"address": addr.ip().to_string(),
"port": addr.port(),
"family": if addr.is_ipv4() { "IPv4" } else { "IPv6" }
}))
}
#[op2(async(deferred), fast)]
#[smi]
pub async fn op_net_connect(
state: Rc<RefCell<OpState>>,
#[string] addr: String,
) -> Result<ResourceId, JsErrorBox> {
let stream = tokio::net::TcpStream::connect(addr).await
.map_err(|e| JsErrorBox::generic(format!("Connect error: {}", e)))?;
let (rd, wr) = stream.into_split();
let rid = state.borrow_mut().resource_table.add(TcpStreamResource {
rd: tokio::sync::Mutex::new(rd),
wr: tokio::sync::Mutex::new(wr),
});
Ok(rid)
}
extension!(
net_ext,
deps = [event_emitter_ext],
ops = [op_net_listen, op_net_accept, op_net_close, op_net_read, op_net_write, op_net_server_address, op_net_connect],
esm_entry_point = "internal:net_bootstrap",
esm = [
dir "src/js",
"internal:net_bootstrap" = "Net.js",
],
);
pub fn run_js(path_name: String, additional_extensions: Option<Vec<Extension>>) {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let path_name_static: &'static str = Box::leak(path_name.into_boxed_str());
let path = Path::new(path_name_static);
let code: &'static str = Box::leak(std::fs::read_to_string(path).unwrap().into_boxed_str());
let add_extensions = additional_extensions.unwrap_or_default();
let mut extentions: Vec<Extension> = vec![
text_encoder_extension::init(),
text_decoder_extension::init(),
base64_ext::init(),
event_emitter_ext::init(),
net_ext::init(),
];
extentions.extend(add_extensions);
rt.block_on(async move {
let text_encoder_ext = text_encoder_extension::init();
let text_decoder_ext = text_decoder_extension::init();
let base64_ext = base64_ext::init();
let event_emitter_ext = event_emitter_ext::init();
let net_ext = net_ext::init();
let mut runtime = JsRuntime::new(RuntimeOptions {
extensions:extentions,
..Default::default()
});
runtime.execute_script(path.to_str().unwrap(), code).unwrap();
runtime
.run_event_loop(PollEventLoopOptions::default())
.await
.unwrap();
println!("Hello, world!");
});
}