Skip to main content

workflow_core/
runtime.rs

1//! Provides functions that allow to detect the runtime environment. These functions can be used to
2//! detect whether the code is running in a browser, node.js or native OS, the type of the underlying OS
3//! (Windows,Linux,MacOS,*BSD) as well as the type of a web environment (Browser or NWJS).
4//! This is useful for an application of an API to detect which environment it is operating under
5//! and subsequently restrict the functionality to the capabilities to this environment.
6
7use cfg_if::cfg_if;
8use std::sync::OnceLock;
9
10cfg_if! {
11    if #[cfg(target_arch = "wasm32")]{
12        use js_sys::Reflect;
13        use wasm_bindgen::prelude::JsValue;
14
15        #[derive(Clone, Copy)]
16        struct JavaScriptRuntime {
17            // NodeJs environment
18            nodejs : bool,
19            // NWJS environment
20            nwjs : bool,
21            // Electron environment (browser or renderer)
22            electron : bool,
23            // Browser-capable environment
24            browser : bool,
25            // Pure web environment (no NodeJs, no NWJS, no Electron)
26            web : bool,
27        }
28
29        #[inline(always)]
30        fn exists(property: &str) -> bool {
31            js_sys::Reflect::get(&js_sys::global(), &property.into()).map(|v|!v.is_falsy()).unwrap_or(false)
32        }
33
34        fn exists_prop(object : &JsValue, property: &str) -> bool {
35            js_sys::Reflect::get(object, &property.into()).map(|v|!v.is_falsy()).unwrap_or(false)
36        }
37
38        #[inline]
39        fn detect() -> &'static JavaScriptRuntime {
40            static JAVASCRIPT_RUNTIME: OnceLock<JavaScriptRuntime> = OnceLock::new();
41            JAVASCRIPT_RUNTIME.get_or_init(|| {
42                let global = js_sys::global();
43
44                let mut browser = exists("window") && exists("document") && exists("location") && exists("navigator");
45
46                let process = Reflect::get(&global, &"process".into());
47                let versions = process
48                    .clone()
49                    .and_then(|process|Reflect::get(&process, &"versions".into()));
50
51                let nodejs = versions
52                    .clone()
53                    .map(|versions|exists_prop(&versions, "node")).unwrap_or(false);
54
55                let electron = versions
56                    .clone()
57                    .map(|versions|exists_prop(&versions, "electron")).unwrap_or(false);
58
59
60                if electron
61                    && let Ok(process_type) = process.and_then(|process|Reflect::get(&process, &"type".into())) {
62                        browser = process_type.as_string().map(|v|v.as_str() == "renderer").unwrap_or(false);
63                    }
64
65                let nwjs = Reflect::get(&global, &"nw".into())
66                    .map(|nw|exists_prop(&nw, "Window")).unwrap_or(false);
67
68                let web = !nodejs && !nwjs && !electron;
69
70                JavaScriptRuntime {
71                    nodejs,
72                    nwjs,
73                    electron,
74                    browser,
75                    web,
76                }
77            })
78        }
79
80        /// Helper to test whether the application is running under
81        /// NodeJs-compatible environment.
82        pub fn is_node() -> bool {
83            detect().nodejs
84        }
85
86        /// Helper to test whether the application is running under
87        /// NW environment.
88        pub fn is_nw() -> bool {
89            detect().nwjs
90        }
91
92        /// Helper to test whether the application is running under
93        /// Electron.
94        pub fn is_electron() -> bool {
95            detect().electron
96        }
97
98        /// Helper to test whether the application is running under
99        /// Electron backend.
100        pub fn is_electron_server() -> bool {
101            detect().electron && !detect().browser
102        }
103
104        /// Helper to test whether the application is running under
105        /// Electron backend.
106        pub fn is_electron_client() -> bool {
107            detect().electron && detect().browser
108        }
109
110        /// Identifies web-capable environment (browser, NWJS window, Electron client)
111        pub fn is_web_capable() -> bool {
112            detect().browser
113        }
114
115        /// Helper to test whether the application is running under
116        /// in a regular browser environment (not NodeJs and not NW).
117        pub fn is_web()->bool{
118            detect().web
119        }
120
121        /// Helper to test whether the application is running
122        /// in a cross-origin isolated browser environment (Flutter).
123        #[inline(always)]
124        pub fn is_cross_origin_isolated()->bool{
125            static CROSS_ORIGIN: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
126            *CROSS_ORIGIN.get_or_init(|| {
127                js_sys::Reflect::get(&js_sys::global(), &"crossOriginIsolated".into())
128                .map(|v| !v.is_falsy())
129                .unwrap_or(false)
130            })
131        }
132    }else{
133
134        /// Helper to test whether the application is running under
135        /// NodeJs-compatible environment.
136        pub fn is_node() -> bool {
137            false
138        }
139
140        /// Helper to test whether the application is running under
141        /// NW environment.
142        pub fn is_nw() -> bool {
143            false
144        }
145
146        /// Helper to test whether the application is running under
147        /// Electron.
148        pub fn is_electron() -> bool {
149            false
150        }
151
152        /// Helper to test whether the application is running under
153        /// Electron backend.
154        pub fn is_electron_server() -> bool {
155            false
156        }
157
158        /// Helper to test whether the application is running under
159        /// Electron backend.
160        pub fn is_electron_client() -> bool {
161            false
162        }
163
164        /// Identifies web-capable environment (browser, NWJS window, Electron client)
165        pub fn is_web_capable() -> bool {
166            false
167        }
168
169        /// Helper to test whether the application is running under
170        /// in a regular browser environment.
171        pub fn is_web()->bool {
172            false
173        }
174
175        /// Helper to test whether the application is running
176        /// in a cross-origin isolated browser environment (Flutter).
177        #[inline(always)]
178        pub fn is_cross_origin_isolated()->bool{
179            false
180        }
181    }
182}
183
184/// Helper to test whether the application is running under
185/// Solana OS.
186pub fn is_solana() -> bool {
187    cfg_if! {
188        if #[cfg(target_arch = "bpf")]{
189            true
190        }else{
191            false
192        }
193    }
194}
195
196/// Helper to test (at runtime) whether the
197/// application is running under WASM32 architecture.
198pub fn is_wasm() -> bool {
199    cfg_if! {
200        if #[cfg(target_arch = "wasm32")]{
201            true
202        }else{
203            false
204        }
205    }
206}
207
208/// Helper to test whether the application is running under
209/// native runtime which is not a Solana OS and architecture is not WASM32
210pub fn is_native() -> bool {
211    cfg_if! {
212        if #[cfg(any(target_arch = "bpf", target_arch = "wasm32"))] {
213            false
214        }else{
215            true
216        }
217    }
218}
219
220/// application runtime info
221#[derive(Debug)]
222pub enum Runtime {
223    /// Native (non-WASM) execution environment.
224    Native,
225    /// Solana BPF on-chain runtime.
226    Solana,
227    /// NW.js desktop application runtime.
228    NW,
229    /// Node.js runtime (WASM).
230    Node,
231    /// Browser / web runtime (WASM).
232    Web,
233}
234
235impl From<&Runtime> for String {
236    fn from(value: &Runtime) -> Self {
237        match value {
238            Runtime::Native => "Native",
239            Runtime::Solana => "Solana",
240            Runtime::NW => "NW",
241            Runtime::Node => "Node",
242            Runtime::Web => "Web",
243        }
244        .to_string()
245    }
246}
247
248impl std::fmt::Display for Runtime {
249    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250        let str: String = self.into();
251        f.write_str(&str)
252    }
253}
254
255impl Runtime {
256    /// get Runtime object
257    pub fn get() -> Self {
258        if is_solana() {
259            Runtime::Solana
260        } else if is_wasm() {
261            if is_nw() {
262                Runtime::NW
263            } else if is_node() {
264                Runtime::Node
265            } else {
266                Runtime::Web
267            }
268        } else {
269            Runtime::Native
270        }
271    }
272}
273
274/// Operating system / platform on which the application is running.
275#[derive(Debug, Clone, PartialEq, Eq)]
276pub enum Platform {
277    /// Microsoft Windows.
278    Windows,
279    /// Apple macOS.
280    MacOS,
281    /// Linux.
282    Linux,
283    /// FreeBSD.
284    FreeBSD,
285    /// OpenBSD.
286    OpenBSD,
287    /// NetBSD.
288    NetBSD,
289    /// Google Android.
290    Android,
291    /// Apple iOS.
292    IOS,
293    /// Platform could not be determined.
294    Unknown,
295    /// Some other platform, identified by the contained name string.
296    Other(String),
297}
298
299impl Platform {
300    /// Determines the platform under Node.js by reading the `process.platform`
301    /// global.
302    pub fn from_node() -> Self {
303        let process = js_sys::Reflect::get(&js_sys::global(), &"process".into())
304            .expect("Unable to get nodejs process global");
305        let platform = js_sys::Reflect::get(&process, &"platform".into())
306            .expect("Unable to get nodejs process.platform");
307
308        match platform
309            .as_string()
310            .expect("nodejs process.platform is not a string")
311            .as_str()
312        {
313            "win32" => Platform::Windows,
314            "darwin" => Platform::MacOS,
315            "linux" => Platform::Linux,
316            "openbsd" => Platform::OpenBSD,
317            "freebsd" => Platform::FreeBSD,
318            v => Platform::Other(v.to_string()),
319        }
320    }
321
322    /// Determines the platform under a browser environment by inspecting the
323    /// `navigator.userAgent` string.
324    pub fn from_web() -> Self {
325        let window = match web_sys::window() {
326            Some(window) => window,
327            _ => {
328                return Platform::Unknown;
329            }
330        };
331
332        let user_agent = match window.navigator().user_agent() {
333            Ok(user_agent) => user_agent.to_lowercase(),
334            _ => {
335                return Platform::Unknown;
336            }
337        };
338
339        if user_agent.contains("win") {
340            Platform::Windows
341        } else if user_agent.contains("mac") {
342            Platform::MacOS
343        } else if user_agent.contains("linux") {
344            Platform::Linux
345        } else if user_agent.contains("android") {
346            Platform::Android
347        } else if user_agent.contains("ios")
348            || user_agent.contains("iphone")
349            || user_agent.contains("ipad")
350        {
351            Platform::IOS
352        } else if user_agent.contains("freebsd") {
353            Platform::FreeBSD
354        } else if user_agent.contains("openbsd") {
355            Platform::OpenBSD
356        } else if user_agent.contains("netbsd") {
357            Platform::NetBSD
358        } else {
359            Platform::Unknown
360        }
361    }
362}
363
364static PLATFORM: OnceLock<Platform> = OnceLock::new();
365
366/// Detects and returns the [`Platform`] the application is running on, caching
367/// the result on first call.
368pub fn platform() -> Platform {
369    PLATFORM
370        .get_or_init(|| {
371            cfg_if! {
372                if #[cfg(target_os = "windows")] {
373                    Platform::Windows
374                } else if #[cfg(target_os = "macos")] {
375                    Platform::MacOS
376                } else if #[cfg(target_os = "linux")] {
377                    Platform::Linux
378                } else if #[cfg(target_os = "android")] {
379                    Platform::Android
380                } else if #[cfg(target_os = "ios")] {
381                    Platform::IOS
382                } else if #[cfg(target_arch = "wasm32")] {
383                    if is_node() {
384                        Platform::from_node()
385                    } else {
386                        Platform::from_web()
387                    }
388                }
389            }
390        })
391        .clone()
392}
393
394/// Returns `true` if the current platform is Windows.
395pub fn is_windows() -> bool {
396    cfg_if! {
397        if #[cfg(target_os = "windows")] {
398            true
399        } else {
400            platform() == Platform::Windows
401        }
402    }
403}
404
405/// Returns `true` if the current platform is macOS.
406pub fn is_macos() -> bool {
407    cfg_if! {
408        if #[cfg(target_os = "macos")] {
409            true
410        } else {
411            platform() == Platform::MacOS
412        }
413    }
414}
415
416/// Returns `true` if the current platform is Linux.
417pub fn is_linux() -> bool {
418    cfg_if! {
419        if #[cfg(target_os = "linux")] {
420            true
421        } else {
422            platform() == Platform::Linux
423        }
424    }
425}
426
427/// Returns `true` if the current platform is FreeBSD.
428pub fn is_freebsd() -> bool {
429    cfg_if! {
430        if #[cfg(target_os = "freebsd")] {
431            true
432        } else {
433            platform() == Platform::FreeBSD
434        }
435    }
436}
437
438/// Returns `true` if the current platform is OpenBSD.
439pub fn is_openbsd() -> bool {
440    cfg_if! {
441        if #[cfg(target_os = "openbsd")] {
442            true
443        } else {
444            platform() == Platform::OpenBSD
445        }
446    }
447}
448
449/// Returns `true` if the current platform is NetBSD.
450pub fn is_netbsd() -> bool {
451    cfg_if! {
452        if #[cfg(target_os = "netbsd")] {
453            true
454        } else {
455            platform() == Platform::NetBSD
456        }
457    }
458}
459
460/// Returns `true` if the current platform is iOS.
461pub fn is_ios() -> bool {
462    platform() == Platform::IOS
463}
464
465/// Returns `true` if the current platform is Android.
466pub fn is_android() -> bool {
467    platform() == Platform::Android
468}
469
470/// Returns `true` if the current platform is a Unix-like OS (macOS, Linux, or a BSD).
471pub fn is_unix() -> bool {
472    is_macos() || is_linux() || is_freebsd() || is_openbsd() || is_netbsd()
473}
474
475/// Returns `true` if the current platform is a mobile OS (iOS or Android).
476pub fn is_mobile() -> bool {
477    is_ios() || is_android()
478}
479
480/// Returns `true` if the application is running as a Chrome browser extension.
481pub fn is_chrome_extension() -> bool {
482    cfg_if! {
483        if #[cfg(target_arch = "wasm32")] {
484
485            static IS_CHROME_EXTENSION : OnceLock<bool> = OnceLock::new();
486
487            *IS_CHROME_EXTENSION.get_or_init(||{
488                if is_web() {
489                    js_sys::Reflect::get(&js_sys::global(), &"location".into())
490                    .and_then(|location| { js_sys::Reflect::get(&location, &"protocol".into()) })
491                    .map(|protocol|protocol == "chrome-extension:")
492                    .unwrap_or(false)
493                } else {
494                    false
495                }
496            })
497
498        } else {
499            false
500        }
501    }
502}