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                    if 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
66                let nwjs = Reflect::get(&global, &"nw".into())
67                    .map(|nw|exists_prop(&nw, "Window")).unwrap_or(false);
68
69                let web = !nodejs && !nwjs && !electron;
70
71                JavaScriptRuntime {
72                    nodejs,
73                    nwjs,
74                    electron,
75                    browser,
76                    web,
77                }
78            })
79        }
80
81        /// Helper to test whether the application is running under
82        /// NodeJs-compatible environment.
83        pub fn is_node() -> bool {
84            detect().nodejs
85        }
86
87        /// Helper to test whether the application is running under
88        /// NW environment.
89        pub fn is_nw() -> bool {
90            detect().nwjs
91        }
92
93        /// Helper to test whether the application is running under
94        /// Electron.
95        pub fn is_electron() -> bool {
96            detect().electron
97        }
98
99        /// Helper to test whether the application is running under
100        /// Electron backend.
101        pub fn is_electron_server() -> bool {
102            detect().electron && !detect().browser
103        }
104
105        /// Helper to test whether the application is running under
106        /// Electron backend.
107        pub fn is_electron_client() -> bool {
108            detect().electron && detect().browser
109        }
110
111        /// Identifies web-capable environment (browser, NWJS window, Electron client)
112        pub fn is_web_capable() -> bool {
113            detect().browser
114        }
115
116        /// Helper to test whether the application is running under
117        /// in a regular browser environment (not NodeJs and not NW).
118        pub fn is_web()->bool{
119            detect().web
120        }
121
122        /// Helper to test whether the application is running
123        /// in a cross-origin isolated browser environment (Flutter).
124        #[inline(always)]
125        pub fn is_cross_origin_isolated()->bool{
126            static CROSS_ORIGIN: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
127            *CROSS_ORIGIN.get_or_init(|| {
128                js_sys::Reflect::get(&js_sys::global(), &"crossOriginIsolated".into())
129                .map(|v| !v.is_falsy())
130                .unwrap_or(false)
131            })
132        }
133    }else{
134
135        /// Helper to test whether the application is running under
136        /// NodeJs-compatible environment.
137        pub fn is_node() -> bool {
138            false
139        }
140
141        /// Helper to test whether the application is running under
142        /// NW environment.
143        pub fn is_nw() -> bool {
144            false
145        }
146
147        /// Helper to test whether the application is running under
148        /// Electron.
149        pub fn is_electron() -> bool {
150            false
151        }
152
153        /// Helper to test whether the application is running under
154        /// Electron backend.
155        pub fn is_electron_server() -> bool {
156            false
157        }
158
159        /// Helper to test whether the application is running under
160        /// Electron backend.
161        pub fn is_electron_client() -> bool {
162            false
163        }
164
165        /// Identifies web-capable environment (browser, NWJS window, Electron client)
166        pub fn is_web_capable() -> bool {
167            false
168        }
169
170        /// Helper to test whether the application is running under
171        /// in a regular browser environment.
172        pub fn is_web()->bool {
173            false
174        }
175
176        /// Helper to test whether the application is running
177        /// in a cross-origin isolated browser environment (Flutter).
178        #[inline(always)]
179        pub fn is_cross_origin_isolated()->bool{
180            false
181        }
182    }
183}
184
185/// Helper to test whether the application is running under
186/// Solana OS.
187pub fn is_solana() -> bool {
188    cfg_if! {
189        if #[cfg(target_arch = "bpf")]{
190            true
191        }else{
192            false
193        }
194    }
195}
196
197/// Helper to test (at runtime) whether the
198/// application is running under WASM32 architecture.
199pub fn is_wasm() -> bool {
200    cfg_if! {
201        if #[cfg(target_arch = "wasm32")]{
202            true
203        }else{
204            false
205        }
206    }
207}
208
209/// Helper to test whether the application is running under
210/// native runtime which is not a Solana OS and architecture is not WASM32
211pub fn is_native() -> bool {
212    cfg_if! {
213        if #[cfg(any(target_arch = "bpf", target_arch = "wasm32"))] {
214            false
215        }else{
216            true
217        }
218    }
219}
220
221/// application runtime info
222#[derive(Debug)]
223pub enum Runtime {
224    Native,
225    Solana,
226    NW,
227    Node,
228    Web,
229}
230
231impl From<&Runtime> for String {
232    fn from(value: &Runtime) -> Self {
233        match value {
234            Runtime::Native => "Native",
235            Runtime::Solana => "Solana",
236            Runtime::NW => "NW",
237            Runtime::Node => "Node",
238            Runtime::Web => "Web",
239        }
240        .to_string()
241    }
242}
243
244impl std::fmt::Display for Runtime {
245    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
246        let str: String = self.into();
247        f.write_str(&str)
248    }
249}
250
251impl Runtime {
252    /// get Runtime object
253    pub fn get() -> Self {
254        if is_solana() {
255            Runtime::Solana
256        } else if is_wasm() {
257            if is_nw() {
258                Runtime::NW
259            } else if is_node() {
260                Runtime::Node
261            } else {
262                Runtime::Web
263            }
264        } else {
265            Runtime::Native
266        }
267    }
268}
269
270#[derive(Debug, Clone, PartialEq, Eq)]
271pub enum Platform {
272    Windows,
273    MacOS,
274    Linux,
275    FreeBSD,
276    OpenBSD,
277    NetBSD,
278    Android,
279    IOS,
280    Unknown,
281    Other(String),
282}
283
284impl Platform {
285    pub fn from_node() -> Self {
286        let process = js_sys::Reflect::get(&js_sys::global(), &"process".into())
287            .expect("Unable to get nodejs process global");
288        let platform = js_sys::Reflect::get(&process, &"platform".into())
289            .expect("Unable to get nodejs process.platform");
290
291        let platform = match platform
292            .as_string()
293            .expect("nodejs process.platform is not a string")
294            .as_str()
295        {
296            "win32" => Platform::Windows,
297            "darwin" => Platform::MacOS,
298            "linux" => Platform::Linux,
299            "openbsd" => Platform::OpenBSD,
300            "freebsd" => Platform::FreeBSD,
301            v => Platform::Other(v.to_string()),
302        };
303
304        platform
305    }
306
307    pub fn from_web() -> Self {
308        let window = if let Some(window) = web_sys::window() {
309            window
310        } else {
311            return Platform::Unknown;
312        };
313
314        let user_agent = if let Ok(user_agent) = window.navigator().user_agent() {
315            user_agent.to_lowercase()
316        } else {
317            return Platform::Unknown;
318        };
319
320        if user_agent.contains("win") {
321            Platform::Windows
322        } else if user_agent.contains("mac") {
323            Platform::MacOS
324        } else if user_agent.contains("linux") {
325            Platform::Linux
326        } else if user_agent.contains("android") {
327            Platform::Android
328        } else if user_agent.contains("ios")
329            || user_agent.contains("iphone")
330            || user_agent.contains("ipad")
331        {
332            Platform::IOS
333        } else if user_agent.contains("freebsd") {
334            Platform::FreeBSD
335        } else if user_agent.contains("openbsd") {
336            Platform::OpenBSD
337        } else if user_agent.contains("netbsd") {
338            Platform::NetBSD
339        } else {
340            Platform::Unknown
341        }
342    }
343}
344
345static PLATFORM: OnceLock<Platform> = OnceLock::new();
346
347pub fn platform() -> Platform {
348    PLATFORM
349        .get_or_init(|| {
350            cfg_if! {
351                if #[cfg(target_os = "windows")] {
352                    Platform::Windows
353                } else if #[cfg(target_os = "macos")] {
354                    Platform::MacOS
355                } else if #[cfg(target_os = "linux")] {
356                    Platform::Linux
357                } else if #[cfg(target_os = "android")] {
358                    Platform::Android
359                } else if #[cfg(target_os = "ios")] {
360                    Platform::IOS
361                } else if #[cfg(target_arch = "wasm32")] {
362                    if is_node() {
363                        Platform::from_node()
364                    } else {
365                        Platform::from_web()
366                    }
367                }
368            }
369        })
370        .clone()
371}
372
373pub fn is_windows() -> bool {
374    cfg_if! {
375        if #[cfg(target_os = "windows")] {
376            true
377        } else {
378            platform() == Platform::Windows
379        }
380    }
381}
382
383pub fn is_macos() -> bool {
384    cfg_if! {
385        if #[cfg(target_os = "macos")] {
386            true
387        } else {
388            platform() == Platform::MacOS
389        }
390    }
391}
392
393pub fn is_linux() -> bool {
394    cfg_if! {
395        if #[cfg(target_os = "linux")] {
396            true
397        } else {
398            platform() == Platform::Linux
399        }
400    }
401}
402
403pub fn is_freebsd() -> bool {
404    cfg_if! {
405        if #[cfg(target_os = "freebsd")] {
406            true
407        } else {
408            platform() == Platform::FreeBSD
409        }
410    }
411}
412
413pub fn is_openbsd() -> bool {
414    cfg_if! {
415        if #[cfg(target_os = "openbsd")] {
416            true
417        } else {
418            platform() == Platform::OpenBSD
419        }
420    }
421}
422
423pub fn is_netbsd() -> bool {
424    cfg_if! {
425        if #[cfg(target_os = "netbsd")] {
426            true
427        } else {
428            platform() == Platform::NetBSD
429        }
430    }
431}
432
433pub fn is_ios() -> bool {
434    platform() == Platform::IOS
435}
436
437pub fn is_android() -> bool {
438    platform() == Platform::Android
439}
440
441pub fn is_unix() -> bool {
442    is_macos() || is_linux() || is_freebsd() || is_openbsd() || is_netbsd()
443}
444
445pub fn is_mobile() -> bool {
446    is_ios() || is_android()
447}
448
449pub fn is_chrome_extension() -> bool {
450    cfg_if! {
451        if #[cfg(target_arch = "wasm32")] {
452
453            static IS_CHROME_EXTENSION : OnceLock<bool> = OnceLock::new();
454
455            *IS_CHROME_EXTENSION.get_or_init(||{
456                if is_web() {
457                    js_sys::Reflect::get(&js_sys::global(), &"location".into())
458                    .and_then(|location| { js_sys::Reflect::get(&location, &"protocol".into()) })
459                    .map(|protocol|protocol == "chrome-extension:")
460                    .unwrap_or(false)
461                } else {
462                    false
463                }
464            })
465
466        } else {
467            false
468        }
469    }
470}