1use js_sys::Reflect;
2use js_sys::global;
3use nu_protocol::Type;
4use nu_protocol::{
5 Category, IntoPipelineData, PipelineData, Record, ShellError, Signature, Value,
6 engine::{Command, EngineState, Stack},
7};
8use wasm_bindgen::JsValue;
9
10#[derive(Clone)]
11pub struct Sys;
12
13impl Command for Sys {
14 fn name(&self) -> &str {
15 "sys"
16 }
17
18 fn signature(&self) -> Signature {
19 Signature::build("sys")
20 .input_output_type(Type::Nothing, Type::record())
21 .category(Category::System)
22 }
23
24 fn description(&self) -> &str {
25 "return information about the browser environment (when running in wasm)"
26 }
27
28 fn run(
29 &self,
30 _engine_state: &EngineState,
31 _stack: &mut Stack,
32 call: &nu_protocol::engine::Call,
33 _input: PipelineData,
34 ) -> Result<PipelineData, ShellError> {
35 let head = call.head;
36 let mut rec = Record::new();
37
38 let g = global();
39
40 // navigator-derived fields
41 if let Ok(nav) = Reflect::get(&g, &JsValue::from_str("navigator")) {
42 if !nav.is_undefined() && !nav.is_null() {
43 let ua = Reflect::get(&nav, &JsValue::from_str("userAgent"))
44 .ok()
45 .and_then(|v| v.as_string())
46 .unwrap_or_default();
47 rec.push("user_agent", Value::string(ua, head));
48
49 let platform = Reflect::get(&nav, &JsValue::from_str("platform"))
50 .ok()
51 .and_then(|v| v.as_string())
52 .unwrap_or_default();
53 rec.push("platform", Value::string(platform, head));
54
55 let vendor = Reflect::get(&nav, &JsValue::from_str("vendor"))
56 .ok()
57 .and_then(|v| v.as_string())
58 .unwrap_or_default();
59 rec.push("vendor", Value::string(vendor, head));
60
61 let product = Reflect::get(&nav, &JsValue::from_str("product"))
62 .ok()
63 .and_then(|v| v.as_string())
64 .unwrap_or_default();
65 rec.push("product", Value::string(product, head));
66
67 let app_name = Reflect::get(&nav, &JsValue::from_str("appName"))
68 .ok()
69 .and_then(|v| v.as_string())
70 .unwrap_or_default();
71 rec.push("app_name", Value::string(app_name, head));
72
73 let language = Reflect::get(&nav, &JsValue::from_str("language"))
74 .ok()
75 .and_then(|v| v.as_string())
76 .unwrap_or_default();
77 rec.push("language", Value::string(language, head));
78
79 // booleans now use Value::bool
80 let online = Reflect::get(&nav, &JsValue::from_str("onLine"))
81 .ok()
82 .and_then(|v| v.as_bool())
83 .unwrap_or(false);
84 rec.push("online", Value::bool(online, head));
85
86 let cookie_enabled = Reflect::get(&nav, &JsValue::from_str("cookieEnabled"))
87 .ok()
88 .and_then(|v| v.as_bool())
89 .unwrap_or(false);
90 rec.push("cookie_enabled", Value::bool(cookie_enabled, head));
91
92 // numeric-ish fields
93 let hardware_concurrency =
94 Reflect::get(&nav, &JsValue::from_str("hardwareConcurrency"))
95 .ok()
96 .and_then(|v| v.as_f64())
97 .map(|f| f as i64);
98 if let Some(hc) = hardware_concurrency {
99 rec.push("hardware_concurrency", Value::int(hc, head));
100 }
101
102 let device_memory = Reflect::get(&nav, &JsValue::from_str("deviceMemory"))
103 .ok()
104 .and_then(|v| v.as_f64())
105 .map(|f| f as i64);
106 if let Some(dm) = device_memory {
107 rec.push("device_memory_gb", Value::int(dm, head));
108 }
109
110 let max_touch_points = Reflect::get(&nav, &JsValue::from_str("maxTouchPoints"))
111 .ok()
112 .and_then(|v| v.as_f64())
113 .map(|f| f as i64);
114 if let Some(tp) = max_touch_points {
115 rec.push("max_touch_points", Value::int(tp, head));
116 }
117
118 let dnt = Reflect::get(&nav, &JsValue::from_str("doNotTrack"))
119 .ok()
120 .and_then(|v| v.as_string())
121 .unwrap_or_default();
122 if !dnt.is_empty() {
123 rec.push("do_not_track", Value::string(dnt, head));
124 }
125 }
126 }
127
128 // screen dimensions (if available)
129 if let Ok(screen) = Reflect::get(&g, &JsValue::from_str("screen")) {
130 if !screen.is_undefined() && !screen.is_null() {
131 let width = Reflect::get(&screen, &JsValue::from_str("width"))
132 .ok()
133 .and_then(|v| v.as_f64())
134 .map(|f| f as i64);
135 let height = Reflect::get(&screen, &JsValue::from_str("height"))
136 .ok()
137 .and_then(|v| v.as_f64())
138 .map(|f| f as i64);
139 if let Some(w) = width {
140 rec.push("screen_width", Value::int(w, head));
141 }
142 if let Some(h) = height {
143 rec.push("screen_height", Value::int(h, head));
144 }
145 }
146 }
147
148 // performance.memory (optional)
149 if let Ok(perf) = Reflect::get(&g, &JsValue::from_str("performance")) {
150 if !perf.is_undefined() && !perf.is_null() {
151 if let Ok(mem) = Reflect::get(&perf, &JsValue::from_str("memory")) {
152 if !mem.is_undefined() && !mem.is_null() {
153 let used = Reflect::get(&mem, &JsValue::from_str("usedJSHeapSize"))
154 .ok()
155 .and_then(|v| v.as_f64())
156 .map(|f| f as i64);
157 let limit = Reflect::get(&mem, &JsValue::from_str("jsHeapSizeLimit"))
158 .ok()
159 .and_then(|v| v.as_f64())
160 .map(|f| f as i64);
161 let mut mrec = Record::new();
162 if let Some(u) = used {
163 mrec.push("used_js_heap_size", Value::filesize(u, head));
164 }
165 if let Some(l) = limit {
166 mrec.push("js_heap_size_limit", Value::filesize(l, head));
167 }
168 if !mrec.is_empty() {
169 rec.push("performance_memory", Value::record(mrec, head));
170 }
171 }
172 }
173 }
174 }
175
176 if rec.is_empty() {
177 rec.push(
178 "error",
179 Value::string("not running in a browser environment", head),
180 );
181 }
182
183 let date = compile_time::unix!();
184 let rustc = compile_time::rustc_version_str!();
185
186 rec.push("build_time", Value::int(date, head));
187 rec.push("rustc_version", Value::string(rustc, head));
188
189 Ok(Value::record(rec, head).into_pipeline_data())
190 }
191}