1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
use crate::*;
/// Implementation of native bridge functionality.
impl UseEuvNativeBridge {
/// Creates native bridge state for accessing platform-native features.
///
/// Initializes all signals with default values and sets `available` to `false`.
/// The actual data is loaded asynchronously via `Self::load_data`.
///
/// # Returns
///
/// - `UseEuvNativeBridge`: The native bridge state.
pub fn use_bridge_state() -> UseEuvNativeBridge {
UseEuvNativeBridge::new(
App::use_signal(|| false),
App::use_signal(|| true),
App::use_signal(String::new),
)
}
/// Asynchronously loads native bridge data and updates the provided state signals.
///
/// First checks platform availability via `BridgeConfig::is_available`. If unavailable,
/// sets `available` to `false` and returns. Otherwise, invokes the
/// `resolve_bridge_group_permissions` command, then populates the corresponding
/// signal from the result. If the invoke fails, sets `available` to `false`
/// so the card is hidden.
///
/// # Arguments
///
/// - `Option<BridgeConfig>`: Optional custom bridge configuration.
pub fn load_data(self, config: Option<BridgeConfig>) {
if !BridgeConfig::is_available(config.as_ref()) {
self.get_available().set(false);
self.get_loading().set(false);
return;
}
let permissions_state: UseEuvNativeBridge = self;
let cfg: Option<BridgeConfig> = config;
spawn_local(async move {
let args_obj: Object = Object::new();
Reflect::set(
&args_obj,
&JsValue::from_str(BRIDGE_GROUP_KEY),
&JsValue::from_str(BRIDGE_GROUP_ALL),
)
.unwrap_or(false);
let permissions_result: Result<JsValue, String> = match BridgeConfig::invoke(
INVOKE_RESOLVE_BRIDGE_GROUP_PERMISSIONS,
Some(&args_obj),
cfg.as_ref(),
) {
Ok(promise) => {
let future: JsFuture = JsFuture::from(promise);
match future.await {
Ok(value) => Ok(value),
Err(error) => Err(format!("{error:?}")),
}
}
Err(error) => Err(error),
};
match permissions_result {
Ok(value) => {
let permissions_array: Vec<String> = value
.dyn_into::<Array>()
.map(|array: Array| {
array
.iter()
.filter_map(|item: JsValue| item.as_string())
.collect::<Vec<String>>()
})
.unwrap_or_default();
permissions_state
.get_permissions()
.set(permissions_array.join(", "));
permissions_state.get_available().set(true);
}
Err(_) => {
permissions_state.get_available().set(false);
}
}
permissions_state.get_loading().set(false);
});
}
}
/// Implementation of cache update functionality.
impl UseCacheUpdate {
/// Creates cache update state for tracking documentation build status.
///
/// Initializes `doc_status` to `false`, `version` to an empty string,
/// and `updating` to `false`. The actual data is loaded asynchronously
/// via `Self::load`.
///
/// # Returns
///
/// - `UseCacheUpdate`: The cache update state.
pub fn use_cache_state() -> UseCacheUpdate {
UseCacheUpdate::new(
App::use_signal(|| false),
App::use_signal(String::new),
App::use_signal(|| false),
)
}
/// Asynchronously fetches the docs.rs status JSON and invokes the bridge
/// `update_cache` command to synchronize the local cache.
///
/// First fetches `DOCS_STATUS_URL` and parses the `{ "doc_status": bool,
/// "version": string }` JSON payload into the provided state signals.
/// If the remote version is greater than `EUV_VERSION` and the bridge
/// is available, invokes the `update_cache` command via
/// `window.bridge.core.invoke("update_cache")` and updates the
/// `updating` signal accordingly.
///
/// # Arguments
///
/// - `&str`: The current version string for comparison.
/// - `Option<BridgeConfig>`: Optional custom bridge configuration.
pub fn load(self, current_version: &str, config: Option<BridgeConfig>) {
let doc_status_signal: Signal<bool> = self.get_doc_status();
let version_signal: Signal<String> = self.get_version();
let updating_signal: Signal<bool> = self.get_updating();
let version_to_compare: String = current_version.to_string();
let cfg: Option<BridgeConfig> = config;
spawn_local(async move {
let window_value: Window = window().expect("no global window exists");
let promise: Promise = window_value.fetch_with_str(DOCS_STATUS_URL);
let future: JsFuture = JsFuture::from(promise);
let response: JsValue = match future.await {
Ok(value) => value,
Err(error) => {
Console::error(&format!(
"Failed to fetch version status: {}",
error.as_string().unwrap_or_default()
));
return;
}
};
let response_value: Response = match response.dyn_into() {
Ok(value) => value,
Err(error) => {
Console::error(&format!(
"Failed to convert fetch response: {}",
error.as_string().unwrap_or_default()
));
return;
}
};
let text_promise: Promise = match response_value.text() {
Ok(promise) => promise,
Err(error) => {
Console::error(&format!(
"Failed to get response text promise: {}",
error.as_string().unwrap_or_default()
));
return;
}
};
let text_future: JsFuture = JsFuture::from(text_promise);
let text: JsValue = match text_future.await {
Ok(value) => value,
Err(error) => {
Console::error(&format!(
"Failed to read response text: {}",
error.as_string().unwrap_or_default()
));
return;
}
};
let text_string: String = text.as_string().unwrap_or_default();
Console::log(&text_string);
let parsed: DocsStatus =
serde_json::from_str::<DocsStatus>(&text_string).unwrap_or_default();
doc_status_signal.set(parsed.get_doc_status());
version_signal.set(parsed.get_version().clone());
if !matches!(
CompareVersion::compare_version(parsed.get_version(), &version_to_compare),
Ok(VersionLevel::Greater)
) {
Console::log(&format!(
"Current version v{version_to_compare} is already the latest version"
));
return;
}
if !BridgeConfig::is_available(cfg.as_ref()) {
return;
}
updating_signal.set(true);
if let Ok(promise) = BridgeConfig::invoke(INVOKE_UPDATE_CACHE, None, cfg.as_ref()) {
let future: JsFuture = JsFuture::from(promise);
match future.await {
Ok(result) => Console::log(&result.as_string().unwrap_or_default()),
Err(error) => Console::error(&error.as_string().unwrap_or_default()),
}
}
updating_signal.set(false);
});
}
}
/// Implementation of bridge configuration and invocation.
impl BridgeConfig {
/// Checks whether the bridge native bridge is available on the current platform.
///
/// Looks up `window.___.core` via `Reflect` to determine if the
/// bridge runtime is present. Returns `false` if the property chain does not exist
/// or if any reflection error occurs.
///
/// # Arguments
///
/// - `Option<&BridgeConfig>`: Optional custom bridge configuration.
///
/// # Returns
///
/// - `bool`: `true` if the bridge core module is available.
pub(crate) fn is_available(config: Option<&BridgeConfig>) -> bool {
let cfg: BridgeConfig =
config.map_or_else(BridgeConfig::default, |c: &BridgeConfig| c.clone());
let window_value: Window = window().expect("no global window exists");
let bridge_key: JsValue = JsValue::from_str(cfg.global_key);
let bridge_obj: JsValue = match Reflect::get(&window_value, &bridge_key) {
Ok(value) => value,
Err(_) => return false,
};
if bridge_obj.is_undefined() || bridge_obj.is_null() {
return false;
}
let core_key: JsValue = JsValue::from_str(cfg.core_key);
let core_obj: JsValue = match Reflect::get(&bridge_obj, &core_key) {
Ok(value) => value,
Err(_) => return false,
};
!core_obj.is_undefined() && !core_obj.is_null()
}
/// Invokes a bridge core command by name via `window.___.core.invoke`.
///
/// Resolves the `___` → `core` → `invoke` property chain on the global
/// `window` object, then calls `invoke` with the given command name and
/// optional arguments object. Returns the resulting `Promise`, or an
/// error string if any step in the reflection chain fails.
///
/// # Arguments
///
/// - `&str`: The bridge command name to invoke.
/// - `Option<&JsValue>`: Optional arguments object to pass to the command.
/// - `Option<&BridgeConfig>`: Optional custom bridge configuration.
///
/// # Returns
///
/// - `Result<Promise, String>`: The promise returned by the invoke call, or an error message.
pub(crate) fn invoke(
command: &str,
args: Option<&JsValue>,
config: Option<&BridgeConfig>,
) -> Result<Promise, String> {
let cfg: BridgeConfig =
config.map_or_else(BridgeConfig::default, |c: &BridgeConfig| c.clone());
let window_value: Window = window().expect("no global window exists");
let bridge_key: JsValue = JsValue::from_str(cfg.global_key);
let bridge_obj: JsValue = Reflect::get(&window_value, &bridge_key)
.map_err(|error: JsValue| format!("{error:?}"))?;
let core_key: JsValue = JsValue::from_str(cfg.core_key);
let core_obj: JsValue =
Reflect::get(&bridge_obj, &core_key).map_err(|error: JsValue| format!("{error:?}"))?;
let invoke_key: JsValue = JsValue::from_str(cfg.invoke_key);
let invoke_fn: JsValue =
Reflect::get(&core_obj, &invoke_key).map_err(|error: JsValue| format!("{error:?}"))?;
let invoke_function: Function = invoke_fn
.dyn_into::<Function>()
.map_err(|error: JsValue| format!("{error:?}"))?;
let command_value: JsValue = JsValue::from_str(command);
let result: JsValue = match args {
Some(arguments) => invoke_function
.call2(&core_obj, &command_value, arguments)
.map_err(|error: JsValue| format!("{error:?}"))?,
None => invoke_function
.call1(&core_obj, &command_value)
.map_err(|error: JsValue| format!("{error:?}"))?,
};
result
.dyn_into::<Promise>()
.map_err(|error: JsValue| format!("{error:?}"))
}
}