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 let root = options.cwd.clone();
126 p.set("cwd", Func::from(move || root.clone()))?;
127
128 let next_tick = ctx.eval::<Value<'_>, _>(
130 "((cb, ...a) => { if (typeof cb !== 'function') throw new TypeError('callback required'); \
131 queueMicrotask(() => cb(...a)); })",
132 )?;
133 p.set("nextTick", next_tick)?;
134
135 for (name, level) in [("stdout", "log"), ("stderr", "error")] {
141 let stream = Object::new(ctx.clone())?;
142 let f = rquickjs::Function::new(
143 ctx.clone(),
144 move |c: Ctx<'_>, chunk: Value<'_>| -> rquickjs::Result<bool> {
145 let s = chunk
146 .as_string()
147 .and_then(|v| v.to_string().ok())
148 .or_else(|| chunk.as_number().map(|n| n.to_string()))
149 .unwrap_or_default();
150 let s = s.strip_suffix('\n').unwrap_or(&s).to_string();
151 let console: Object<'_> = c.globals().get("console")?;
152 let sink: rquickjs::Function<'_> = console.get(level)?;
153 sink.call::<_, ()>((s,))?;
154 Ok(true)
155 },
156 )?;
157 stream.set("write", f)?;
158 stream.set("isTTY", false)?;
159 p.set(name, stream)?;
160 }
161
162 let start = crate::web::performance::monotonic_base();
172 let hrtime = rquickjs::Function::new(ctx.clone(), move |prev: Rest<Value<'_>>| -> Vec<i64> {
173 let now = start.elapsed();
174 let (mut s, mut n) = (
175 i64::try_from(now.as_secs()).unwrap_or(i64::MAX),
176 i64::from(now.subsec_nanos()),
177 );
178 if let Some(arr) = prev.0.first().and_then(|v| v.as_array()) {
179 let ps = arr.get::<i64>(0).unwrap_or(0);
180 let pn = arr.get::<i64>(1).unwrap_or(0);
181 s -= ps;
182 n -= pn;
183 if n < 0 {
184 s -= 1;
185 n += 1_000_000_000;
186 }
187 }
188 vec![s, n]
189 })?;
190 let bigint = rquickjs::Function::new(ctx.clone(), move |c| hrtime_bigint(c, start))?;
193 hrtime.set("bigint", bigint)?;
194 p.set("hrtime", hrtime)?;
195
196 p.set(
199 "exit",
200 Func::from(|code: Rest<Value<'_>>| -> rquickjs::Result<()> {
201 let c = code.0.first().and_then(rquickjs::Value::as_int).unwrap_or(0);
202 Err(rquickjs::Error::new_from_js_message(
203 "process.exit",
204 "Error",
205 format!("process.exit({c}) is not available: this runtime is embedded in a host process"),
206 ))
207 }),
208 )?;
209
210 let permission = Object::new(ctx.clone())?;
216 permission.set(
217 "has",
218 Func::from(|ctx: Ctx<'_>, scope: String, reference: Rest<Value<'_>>| -> rquickjs::Result<bool> {
219 let reference = reference
220 .0
221 .first()
222 .and_then(|v| v.as_string())
223 .and_then(|s| s.to_string().ok());
224 crate::permissions::has(&ctx, &scope, reference.as_deref())
225 }),
226 )?;
227 permission.set(
228 "drop",
229 Func::from(|ctx: Ctx<'_>, scope: String, reference: Rest<Value<'_>>| -> rquickjs::Result<()> {
230 let reference = reference
231 .0
232 .first()
233 .and_then(|v| v.as_string())
234 .and_then(|s| s.to_string().ok());
235 crate::permissions::drop(&ctx, &scope, reference.as_deref())
236 }),
237 )?;
238 freeze(ctx, &permission)?;
239 p.set("permission", permission)?;
240
241 g.set("process", p)?;
242 Ok(())
243}
244
245fn hrtime_bigint(ctx: Ctx<'_>, start: Instant) -> rquickjs::Result<Value<'_>> {
248 let nanos = u64::try_from(start.elapsed().as_nanos()).unwrap_or(u64::MAX);
249 Ok(rquickjs::BigInt::from_u64(ctx, nanos)?.into_value())
250}
251
252fn freeze<'js>(ctx: &Ctx<'js>, obj: &Object<'js>) -> rquickjs::Result<()> {
253 let freeze: rquickjs::Function<'js> = ctx.globals().get::<_, Object<'js>>("Object")?.get("freeze")?;
254 freeze.call::<_, Value<'js>>((obj.clone(),))?;
255 Ok(())
256}