Skip to main content

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}