ferrijs_std/identity.rs
1//! Which runtime a script believes it is running in.
2//!
3//! `process.version`, `process.release.name`, `process.argv0` and
4//! `navigator.userAgent` all name the runtime. Node's own values are
5//! not an option: a library that branches on `process.versions.node`
6//! would take a path this runtime cannot follow. The values here default
7//! to this crate's, and a host that ships its own binary sets its own
8//! before installing the globals, so a user-agent sniffer or a
9//! `process.release` check sees the binary the user is actually running.
10
11use rquickjs::{Ctx, JsLifetime};
12
13/// The runtime's name and version as scripts see them.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct Identity {
16 /// The binary's name (`process.release.name`, `process.argv0`, the
17 /// `navigator.userAgent` product token).
18 pub name: String,
19 /// Its version, without a leading `v`.
20 pub version: String,
21}
22
23impl Default for Identity {
24 fn default() -> Self {
25 Self {
26 name: "ferrijs".to_string(),
27 version: env!("CARGO_PKG_VERSION").to_string(),
28 }
29 }
30}
31
32impl Identity {
33 #[must_use]
34 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
35 Self {
36 name: name.into(),
37 version: version.into(),
38 }
39 }
40
41 /// `name/version`, the shape Node 21+ reports as `navigator.userAgent`.
42 #[must_use]
43 pub fn user_agent(&self) -> String {
44 format!("{}/{}", self.name, self.version)
45 }
46}
47
48/// The QuickJS engine version, as the engine itself reports it.
49#[must_use]
50pub fn quickjs_version() -> &'static str {
51 // SAFETY: `JS_GetVersion` returns a pointer to a static NUL-terminated
52 // string owned by the engine; it is never freed.
53 #[allow(unsafe_code)]
54 unsafe {
55 std::ffi::CStr::from_ptr(rquickjs::qjs::JS_GetVersion())
56 }
57 .to_str()
58 .unwrap_or("unknown")
59}
60
61struct IdentityUd(Identity);
62
63// SAFETY: owned strings only; no borrowed JS values, so re-stating the
64// unused `'js` lifetime is sound.
65#[allow(unsafe_code)]
66unsafe impl JsLifetime<'_> for IdentityUd {
67 type Changed<'to> = IdentityUd;
68}
69
70/// Record the identity every later `install` reads. Call before
71/// [`crate::init`] or [`crate::node::process::install`]; a second call
72/// replaces the first.
73pub fn set(ctx: &Ctx<'_>, identity: Identity) {
74 let _ = ctx.store_userdata(IdentityUd(identity));
75}
76
77/// The realm's identity, or the crate default when the host set none.
78#[must_use]
79pub fn get(ctx: &Ctx<'_>) -> Identity {
80 ctx.userdata::<IdentityUd>().map_or_else(Identity::default, |ud| ud.0.clone())
81}