1use std::time::Instant;
16
17use rquickjs::function::{Func, Rest};
18use rquickjs::{Ctx, Object, Result, Value};
19
20pub const PROCESS_MEMBERS: &[&str] = &[
23 "argv",
24 "argv0",
25 "arch",
26 "cwd",
27 "env",
28 "exit",
29 "hrtime",
30 "nextTick",
31 "permission",
32 "pid",
33 "platform",
34 "release",
35 "stderr",
36 "stdout",
37 "version",
38 "versions",
39];
40
41#[derive(Debug, Clone, Default)]
43pub struct ProcessOptions {
44 pub env: Vec<(String, String)>,
48 pub cwd: String,
52 pub argv: Vec<String>,
56}
57
58pub fn process_object<'js>(ctx: &Ctx<'js>) -> Result<Object<'js>> {
64 ctx.globals().get("process")
65}
66
67pub fn install(ctx: &Ctx<'_>, options: &ProcessOptions) -> rquickjs::Result<()> {
76 let g = ctx.globals();
77 let p = Object::new(ctx.clone())?;
78 let identity = crate::identity::get(ctx);
79
80 let env = Object::new(ctx.clone())?;
82 for (k, v) in &options.env {
83 env.set(k.as_str(), v.as_str())?;
84 }
85 freeze(ctx, &env)?;
88 p.set("env", env)?;
89
90 p.set("platform", crate::utils::sysinfo::PLATFORM)?;
95 p.set("arch", crate::utils::sysinfo::ARCH)?;
96 p.set("version", format!("v{}", identity.version))?;
101 let versions = Object::new(ctx.clone())?;
102 versions.set(identity.name.as_str(), identity.version.as_str())?;
103 versions.set("ferrijs", env!("CARGO_PKG_VERSION"))?;
104 versions.set("quickjs", crate::identity::quickjs_version())?;
105 freeze(ctx, &versions)?;
106 p.set("versions", versions)?;
107 let release = Object::new(ctx.clone())?;
108 release.set("name", identity.name.as_str())?;
109 freeze(ctx, &release)?;
110 p.set("release", release)?;
111
112 let argv = rquickjs::Array::new(ctx.clone())?;
116 argv.set(0, identity.name.as_str())?;
117 for (i, arg) in options.argv.iter().enumerate() {
118 argv.set(i + 1, arg.as_str())?;
119 }
120 p.set("argv", argv)?;
121 p.set("argv0", identity.name.as_str())?;
122 p.set("pid", i64::from(std::process::id()))?;
123
124 crate::node::path::set_cwd(ctx, &options.cwd);
128 let root = options.cwd.clone();
129 p.set("cwd", Func::from(move || root.clone()))?;
130
131 let next_tick = ctx.eval::<Value<'_>, _>(
133 "((cb, ...a) => { if (typeof cb !== 'function') throw new TypeError('callback required'); \
134 queueMicrotask(() => cb(...a)); })",
135 )?;
136 p.set("nextTick", next_tick)?;
137
138 for (name, level) in [("stdout", "log"), ("stderr", "error")] {
144 let stream = Object::new(ctx.clone())?;
145 let f = rquickjs::Function::new(
146 ctx.clone(),
147 move |c: Ctx<'_>, chunk: Value<'_>| -> rquickjs::Result<bool> {
148 let s = chunk
149 .as_string()
150 .and_then(|v| v.to_string().ok())
151 .or_else(|| chunk.as_number().map(|n| n.to_string()))
152 .unwrap_or_default();
153 let s = s.strip_suffix('\n').unwrap_or(&s).to_string();
154 let console: Object<'_> = c.globals().get("console")?;
155 let sink: rquickjs::Function<'_> = console.get(level)?;
156 sink.call::<_, ()>((s,))?;
157 Ok(true)
158 },
159 )?;
160 stream.set("write", f)?;
161 stream.set("isTTY", false)?;
162 p.set(name, stream)?;
163 }
164
165 let start = crate::web::performance::monotonic_base();
175 let hrtime = rquickjs::Function::new(ctx.clone(), move |prev: Rest<Value<'_>>| -> Vec<i64> {
176 let now = start.elapsed();
177 let (mut s, mut n) = (
178 i64::try_from(now.as_secs()).unwrap_or(i64::MAX),
179 i64::from(now.subsec_nanos()),
180 );
181 if let Some(arr) = prev.0.first().and_then(|v| v.as_array()) {
182 let ps = arr.get::<i64>(0).unwrap_or(0);
183 let pn = arr.get::<i64>(1).unwrap_or(0);
184 s -= ps;
185 n -= pn;
186 if n < 0 {
187 s -= 1;
188 n += 1_000_000_000;
189 }
190 }
191 vec![s, n]
192 })?;
193 let bigint = rquickjs::Function::new(ctx.clone(), move |c| hrtime_bigint(c, start))?;
196 hrtime.set("bigint", bigint)?;
197 p.set("hrtime", hrtime)?;
198
199 p.set(
202 "exit",
203 Func::from(|code: Rest<Value<'_>>| -> rquickjs::Result<()> {
204 let c = code.0.first().and_then(rquickjs::Value::as_int).unwrap_or(0);
205 Err(rquickjs::Error::new_from_js_message(
206 "process.exit",
207 "Error",
208 format!("process.exit({c}) is not available: this runtime is embedded in a host process"),
209 ))
210 }),
211 )?;
212
213 let permission = Object::new(ctx.clone())?;
219 permission.set(
220 "has",
221 Func::from(|ctx: Ctx<'_>, scope: String, reference: Rest<Value<'_>>| -> rquickjs::Result<bool> {
222 let reference = reference
223 .0
224 .first()
225 .and_then(|v| v.as_string())
226 .and_then(|s| s.to_string().ok());
227 crate::permissions::has(&ctx, &scope, reference.as_deref())
228 }),
229 )?;
230 permission.set(
231 "drop",
232 Func::from(|ctx: Ctx<'_>, scope: String, reference: Rest<Value<'_>>| -> rquickjs::Result<()> {
233 let reference = reference
234 .0
235 .first()
236 .and_then(|v| v.as_string())
237 .and_then(|s| s.to_string().ok());
238 crate::permissions::drop(&ctx, &scope, reference.as_deref())
239 }),
240 )?;
241 freeze(ctx, &permission)?;
242 p.set("permission", permission)?;
243
244 g.set("process", p)?;
245 Ok(())
246}
247
248fn hrtime_bigint(ctx: Ctx<'_>, start: Instant) -> rquickjs::Result<Value<'_>> {
251 let nanos = u64::try_from(start.elapsed().as_nanos()).unwrap_or(u64::MAX);
252 Ok(rquickjs::BigInt::from_u64(ctx, nanos)?.into_value())
253}
254
255fn freeze<'js>(ctx: &Ctx<'js>, obj: &Object<'js>) -> rquickjs::Result<()> {
256 let freeze: rquickjs::Function<'js> = ctx.globals().get::<_, Object<'js>>("Object")?.get("freeze")?;
257 freeze.call::<_, Value<'js>>((obj.clone(),))?;
258 Ok(())
259}