firebase_rs_sdk/platform/
environment.rs1use std::env;
4use std::fs;
5#[cfg(not(target_arch = "wasm32"))]
6use std::path::Path;
7
8use serde_json::{Map, Value};
9
10fn firebase_defaults() -> Option<Value> {
12 defaults_from_env()
13 .or_else(defaults_from_path)
14 .or_else(defaults_from_global)
15}
16
17fn defaults_from_env() -> Option<Value> {
18 let raw = env::var("__FIREBASE_DEFAULTS__").ok()?;
19 parse_json_value(raw)
20}
21
22fn defaults_from_path() -> Option<Value> {
23 let path = env::var("__FIREBASE_DEFAULTS_PATH").ok()?;
24 let content = fs::read_to_string(path).ok()?;
25 parse_json_value(content)
26}
27
28#[cfg(all(target_arch = "wasm32", feature = "wasm-web"))]
29fn defaults_from_global() -> Option<Value> {
30 use wasm_bindgen::JsValue;
31
32 let global = js_sys::global();
33 let value = js_sys::Reflect::get(&global, &JsValue::from_str("__FIREBASE_DEFAULTS__")).ok()?;
34 if value.is_null() || value.is_undefined() {
35 return None;
36 }
37 let serialized = js_sys::JSON::stringify(&value).ok()?.as_string()?;
38 serde_json::from_str(&serialized).ok()
39}
40
41#[cfg(not(all(target_arch = "wasm32", feature = "wasm-web")))]
42fn defaults_from_global() -> Option<Value> {
43 None
44}
45
46fn parse_json_value(raw: String) -> Option<Value> {
47 serde_json::from_str::<Value>(&raw).ok()
48}
49
50fn parse_config_source(raw: &str) -> Option<Value> {
51 if let Ok(json) = serde_json::from_str::<Value>(raw) {
52 if json.is_object() {
53 return Some(json);
54 }
55 }
56
57 if let Some(path) = treat_as_path(raw) {
58 if let Ok(contents) = fs::read_to_string(&path) {
59 if let Ok(json) = serde_json::from_str::<Value>(&contents) {
60 if json.is_object() {
61 return Some(json);
62 }
63 }
64 }
65 }
66
67 parse_key_value_config(raw)
68}
69
70#[cfg(not(target_arch = "wasm32"))]
71fn treat_as_path(raw: &str) -> Option<String> {
72 if raw.contains('=') {
73 return None;
74 }
75 let trimmed = raw.trim();
76 let path = Path::new(trimmed);
77 if path.exists() {
78 Some(trimmed.to_string())
79 } else {
80 None
81 }
82}
83
84#[cfg(target_arch = "wasm32")]
85fn treat_as_path(_raw: &str) -> Option<String> {
86 None
87}
88
89fn parse_key_value_config(raw: &str) -> Option<Value> {
90 let mut map = Map::new();
91 for entry in raw.split(',') {
92 let mut parts = entry.splitn(2, '=');
93 let key = parts.next()?.trim();
94 let value = parts.next()?.trim();
95 if key.is_empty() || value.is_empty() {
96 continue;
97 }
98 map.insert(key.to_string(), Value::String(value.to_string()));
99 }
100 if map.is_empty() {
101 None
102 } else {
103 Some(Value::Object(map))
104 }
105}
106
107fn firebase_config_from_env() -> Option<Value> {
108 if let Ok(raw) = env::var("FIREBASE_CONFIG") {
109 if let Some(value) = parse_config_source(&raw) {
110 return Some(value);
111 }
112 }
113
114 if let Ok(raw) = env::var("FIREBASE_OPTIONS") {
115 if let Some(value) = parse_config_source(&raw) {
116 return Some(value);
117 }
118 }
119
120 if let Ok(raw) = env::var("FIREBASE_WEBAPP_CONFIG") {
121 if let Some(value) = parse_config_source(&raw) {
122 return Some(value);
123 }
124 }
125
126 None
127}
128
129pub fn default_app_config_json() -> Option<Map<String, Value>> {
131 if let Some(defaults) = firebase_defaults() {
132 if let Some(config) = defaults.get("config").and_then(Value::as_object) {
133 return Some(config.clone());
134 }
135 }
136
137 firebase_config_from_env()?.as_object().cloned()
138}
139
140fn force_environment() -> Option<String> {
141 firebase_defaults()
142 .and_then(|defaults| defaults.get("forceEnvironment").cloned())
143 .or_else(|| env::var("FIREBASE_ENV_FORCE").ok().map(Value::String))
144 .and_then(|value| match value {
145 Value::String(text) => Some(text.to_lowercase()),
146 _ => None,
147 })
148}
149
150pub fn is_browser() -> bool {
152 if let Some(forced) = force_environment() {
153 return forced == "browser";
154 }
155
156 #[cfg(all(target_arch = "wasm32", feature = "wasm-web"))]
157 {
158 use wasm_bindgen::JsCast;
159 js_sys::global().dyn_into::<web_sys::Window>().is_ok()
160 }
161
162 #[cfg(not(all(target_arch = "wasm32", feature = "wasm-web")))]
163 {
164 false
165 }
166}
167
168pub fn is_web_worker() -> bool {
170 if let Some(forced) = force_environment() {
171 if forced == "browser" {
172 return false;
173 }
174 }
175
176 #[cfg(all(target_arch = "wasm32", feature = "wasm-web"))]
177 {
178 use wasm_bindgen::JsCast;
179 js_sys::global()
180 .dyn_into::<web_sys::WorkerGlobalScope>()
181 .is_ok()
182 }
183
184 #[cfg(not(all(target_arch = "wasm32", feature = "wasm-web")))]
185 {
186 false
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn parses_key_value_configs() {
196 let value = parse_key_value_config("apiKey=foo,projectId=my-proj").unwrap();
197 let map = value.as_object().unwrap();
198 assert_eq!(map.get("apiKey").unwrap().as_str(), Some("foo"));
199 assert_eq!(map.get("projectId").unwrap().as_str(), Some("my-proj"));
200 }
201
202 #[test]
203 fn parse_config_source_accepts_files_and_json() {
204 let json = parse_config_source("{\"apiKey\":\"foo\"}").unwrap();
205 assert_eq!(json["apiKey"], "foo");
206
207 let mut path = std::env::temp_dir();
208 path.push(format!(
209 "firebase_rs_sdk_test_{}.json",
210 std::time::SystemTime::now()
211 .duration_since(std::time::UNIX_EPOCH)
212 .unwrap()
213 .as_nanos()
214 ));
215 fs::write(&path, "{\"projectId\":\"demo\"}").unwrap();
216 let path_str = path.to_string_lossy().to_string();
217 let file_json = parse_config_source(&path_str).unwrap();
218 assert_eq!(file_json["projectId"], "demo");
219 let _ = fs::remove_file(path);
220 }
221}