use std::time::Instant;
use rquickjs::function::{Func, Rest};
use rquickjs::{Ctx, Object, Result, Value};
pub const PROCESS_MEMBERS: &[&str] = &[
"argv",
"argv0",
"arch",
"cwd",
"env",
"exit",
"hrtime",
"nextTick",
"permission",
"pid",
"platform",
"release",
"stderr",
"stdout",
"version",
"versions",
];
#[derive(Debug, Clone, Default)]
pub struct ProcessOptions {
pub env: Vec<(String, String)>,
pub cwd: String,
pub argv: Vec<String>,
}
pub fn process_object<'js>(ctx: &Ctx<'js>) -> Result<Object<'js>> {
ctx.globals().get("process")
}
pub fn install(ctx: &Ctx<'_>, options: &ProcessOptions) -> rquickjs::Result<()> {
let g = ctx.globals();
let p = Object::new(ctx.clone())?;
let identity = crate::identity::get(ctx);
let env = Object::new(ctx.clone())?;
for (k, v) in &options.env {
env.set(k.as_str(), v.as_str())?;
}
freeze(ctx, &env)?;
p.set("env", env)?;
p.set("platform", crate::utils::sysinfo::PLATFORM)?;
p.set("arch", crate::utils::sysinfo::ARCH)?;
p.set("version", format!("v{}", identity.version))?;
let versions = Object::new(ctx.clone())?;
versions.set(identity.name.as_str(), identity.version.as_str())?;
versions.set("ferrijs", env!("CARGO_PKG_VERSION"))?;
versions.set("quickjs", crate::identity::quickjs_version())?;
freeze(ctx, &versions)?;
p.set("versions", versions)?;
let release = Object::new(ctx.clone())?;
release.set("name", identity.name.as_str())?;
freeze(ctx, &release)?;
p.set("release", release)?;
let argv = rquickjs::Array::new(ctx.clone())?;
argv.set(0, identity.name.as_str())?;
for (i, arg) in options.argv.iter().enumerate() {
argv.set(i + 1, arg.as_str())?;
}
p.set("argv", argv)?;
p.set("argv0", identity.name.as_str())?;
p.set("pid", i64::from(std::process::id()))?;
crate::node::path::set_cwd(ctx, &options.cwd);
let root = options.cwd.clone();
p.set("cwd", Func::from(move || root.clone()))?;
let next_tick = ctx.eval::<Value<'_>, _>(
"((cb, ...a) => { if (typeof cb !== 'function') throw new TypeError('callback required'); \
queueMicrotask(() => cb(...a)); })",
)?;
p.set("nextTick", next_tick)?;
for (name, level) in [("stdout", "log"), ("stderr", "error")] {
let stream = Object::new(ctx.clone())?;
let f = rquickjs::Function::new(
ctx.clone(),
move |c: Ctx<'_>, chunk: Value<'_>| -> rquickjs::Result<bool> {
let s = chunk
.as_string()
.and_then(|v| v.to_string().ok())
.or_else(|| chunk.as_number().map(|n| n.to_string()))
.unwrap_or_default();
let s = s.strip_suffix('\n').unwrap_or(&s).to_string();
let console: Object<'_> = c.globals().get("console")?;
let sink: rquickjs::Function<'_> = console.get(level)?;
sink.call::<_, ()>((s,))?;
Ok(true)
},
)?;
stream.set("write", f)?;
stream.set("isTTY", false)?;
p.set(name, stream)?;
}
let start = crate::web::performance::monotonic_base();
let hrtime = rquickjs::Function::new(ctx.clone(), move |prev: Rest<Value<'_>>| -> Vec<i64> {
let now = start.elapsed();
let (mut s, mut n) = (
i64::try_from(now.as_secs()).unwrap_or(i64::MAX),
i64::from(now.subsec_nanos()),
);
if let Some(arr) = prev.0.first().and_then(|v| v.as_array()) {
let ps = arr.get::<i64>(0).unwrap_or(0);
let pn = arr.get::<i64>(1).unwrap_or(0);
s -= ps;
n -= pn;
if n < 0 {
s -= 1;
n += 1_000_000_000;
}
}
vec![s, n]
})?;
let bigint = rquickjs::Function::new(ctx.clone(), move |c| hrtime_bigint(c, start))?;
hrtime.set("bigint", bigint)?;
p.set("hrtime", hrtime)?;
p.set(
"exit",
Func::from(|code: Rest<Value<'_>>| -> rquickjs::Result<()> {
let c = code.0.first().and_then(rquickjs::Value::as_int).unwrap_or(0);
Err(rquickjs::Error::new_from_js_message(
"process.exit",
"Error",
format!("process.exit({c}) is not available: this runtime is embedded in a host process"),
))
}),
)?;
let permission = Object::new(ctx.clone())?;
permission.set(
"has",
Func::from(|ctx: Ctx<'_>, scope: String, reference: Rest<Value<'_>>| -> rquickjs::Result<bool> {
let reference = reference
.0
.first()
.and_then(|v| v.as_string())
.and_then(|s| s.to_string().ok());
crate::permissions::has(&ctx, &scope, reference.as_deref())
}),
)?;
permission.set(
"drop",
Func::from(|ctx: Ctx<'_>, scope: String, reference: Rest<Value<'_>>| -> rquickjs::Result<()> {
let reference = reference
.0
.first()
.and_then(|v| v.as_string())
.and_then(|s| s.to_string().ok());
crate::permissions::drop(&ctx, &scope, reference.as_deref())
}),
)?;
freeze(ctx, &permission)?;
p.set("permission", permission)?;
g.set("process", p)?;
Ok(())
}
fn hrtime_bigint(ctx: Ctx<'_>, start: Instant) -> rquickjs::Result<Value<'_>> {
let nanos = u64::try_from(start.elapsed().as_nanos()).unwrap_or(u64::MAX);
Ok(rquickjs::BigInt::from_u64(ctx, nanos)?.into_value())
}
fn freeze<'js>(ctx: &Ctx<'js>, obj: &Object<'js>) -> rquickjs::Result<()> {
let freeze: rquickjs::Function<'js> = ctx.globals().get::<_, Object<'js>>("Object")?.get("freeze")?;
freeze.call::<_, Value<'js>>((obj.clone(),))?;
Ok(())
}