1use std::{collections::HashMap, future::Future, pin::Pin, rc::Rc};
2
3use crate::{
4 driver_module::{event_emitter::EventEmitter, js_value::JsValue},
5 fetch::request_builder::RequestBody,
6 get_driver,
7 struct_mut::ValueMut,
8 transaction, DropResource, FetchMethod, FetchResult, FutureBox, InstantType, JsJson,
9 JsJsonObjectBuilder, WebsocketConnection, WebsocketMessage,
10};
11
12use super::{
13 api_dom_access::DomAccess, arguments::Arguments, callbacks::CallbackStore,
14 panic_message::PanicMessage,
15};
16
17enum ConsoleLogLevel {
18 Debug,
19 Info,
20 Log,
21 Warn,
22 Error,
23}
24
25impl ConsoleLogLevel {
26 pub fn get_str(&self) -> &'static str {
27 match self {
28 Self::Debug => "debug",
29 Self::Info => "info",
30 Self::Log => "log",
31 Self::Warn => "warn",
32 Self::Error => "error",
33 }
34 }
35}
36
37#[derive(Clone)]
38pub struct ApiImport {
39 pub panic_message: PanicMessage,
40 pub fn_dom_access: fn(ptr: u32, size: u32) -> u32,
41
42 pub(crate) arguments: Arguments,
43 pub(crate) callback_store: CallbackStore,
44
45 pub on_fetch_start: EventEmitter<()>,
46 pub on_fetch_stop: EventEmitter<()>,
47}
48
49impl Default for ApiImport {
50 fn default() -> Self {
51 use super::external_api::api::safe_wrappers::{
52 safe_dom_access as fn_dom_access, safe_panic_message as panic_message,
53 };
54
55 let panic_message = PanicMessage::new(panic_message);
56
57 ApiImport {
58 panic_message,
59 fn_dom_access,
60 arguments: Arguments::default(),
61 callback_store: CallbackStore::new(),
62 on_fetch_start: EventEmitter::default(),
63 on_fetch_stop: EventEmitter::default(),
64 }
65 }
66}
67
68impl ApiImport {
69 pub fn show_panic_message(&self, message: String) {
70 self.panic_message.show(message);
71 }
72
73 fn console_4(&self, kind: ConsoleLogLevel, arg1: &str, arg2: &str, arg3: &str, arg4: &str) {
74 self.dom_access()
75 .root("window")
76 .get("console")
77 .call(
78 kind.get_str(),
79 vec![
80 JsValue::str(arg1),
81 JsValue::str(arg2),
82 JsValue::str(arg3),
83 JsValue::str(arg4),
84 ],
85 )
86 .exec();
87 }
88
89 pub fn console_debug_4(&self, arg1: &str, arg2: &str, arg3: &str, arg4: &str) {
90 self.console_4(ConsoleLogLevel::Debug, arg1, arg2, arg3, arg4)
91 }
92
93 pub fn console_log_4(&self, arg1: &str, arg2: &str, arg3: &str, arg4: &str) {
94 self.console_4(ConsoleLogLevel::Log, arg1, arg2, arg3, arg4)
95 }
96
97 pub fn console_info_4(&self, arg1: &str, arg2: &str, arg3: &str, arg4: &str) {
98 self.console_4(ConsoleLogLevel::Info, arg1, arg2, arg3, arg4)
99 }
100
101 pub fn console_warn_4(&self, arg1: &str, arg2: &str, arg3: &str, arg4: &str) {
102 self.console_4(ConsoleLogLevel::Warn, arg1, arg2, arg3, arg4)
103 }
104
105 pub fn console_error_4(&self, arg1: &str, arg2: &str, arg3: &str, arg4: &str) {
106 self.console_4(ConsoleLogLevel::Error, arg1, arg2, arg3, arg4)
107 }
108
109 pub fn cookie_get(&self, cname: &str) -> String {
110 let result = self
111 .dom_access()
112 .api()
113 .get("cookie")
114 .call("get", vec![JsValue::str(cname)])
115 .fetch();
116
117 if let JsValue::String(value) = result {
118 value
119 } else {
120 log::error!("cookie_get -> params decode error -> result={result:?}");
121 String::from("")
122 }
123 }
124
125 pub fn cookie_get_json(&self, cname: &str) -> JsJson {
126 if self.is_browser() {
127 let result = self
128 .dom_access()
129 .api()
130 .get("cookie")
131 .call("get_json", vec![JsValue::str(cname)])
132 .fetch();
133
134 if result != JsValue::Null {
135 if let JsValue::Json(value) = result {
136 return value;
137 }
138 log::error!("cookie_get_json -> params decode error -> result={result:?}");
139 }
140 }
141 JsJson::Null
142 }
143
144 pub fn cookie_set(&self, cname: &str, cvalue: &str, expires_in: u64) {
145 if self.is_browser() {
146 self.dom_access()
147 .api()
148 .get("cookie")
149 .call(
150 "set",
151 vec![
152 JsValue::str(cname),
153 JsValue::str(cvalue),
154 JsValue::U64(expires_in),
155 ],
156 )
157 .exec();
158 } else {
159 log::warn!("Can't set cookie on server side");
160 }
161 }
162
163 pub fn cookie_set_json(&self, cname: &str, cvalue: JsJson, expires_in: u64) {
164 self.dom_access()
165 .api()
166 .get("cookie")
167 .call(
168 "set_json",
169 vec![
170 JsValue::str(cname),
171 JsValue::Json(cvalue),
172 JsValue::U64(expires_in),
173 ],
174 )
175 .exec();
176 }
177
178 pub fn interval_set<F: Fn() + 'static>(&self, duration: u32, callback: F) -> DropResource {
179 let (callback_id, drop_callback) = self.callback_store.register(move |_| {
180 callback();
181 JsValue::Undefined
182 });
183
184 let result = self
185 .dom_access()
186 .api()
187 .get("interval")
188 .call(
189 "interval_set",
190 vec![JsValue::U32(duration), JsValue::U64(callback_id.as_u64())],
191 )
192 .fetch();
193
194 let timer_id = if let JsValue::I32(timer_id) = result {
195 timer_id
196 } else {
197 log::error!("interval_set -> expected i32 -> result={result:?}");
198 0
199 };
200
201 let api = self.clone();
202
203 DropResource::new(move || {
204 api.dom_access()
205 .api()
206 .get("interval")
207 .call("interval_clear", vec![JsValue::I32(timer_id)])
208 .exec();
209
210 drop_callback.off();
211 })
212 }
213
214 pub fn timeout_set<F: Fn() + 'static>(&self, duration: u32, callback: F) -> DropResource {
215 let (callback_id, drop_callback) = self.callback_store.register(move |_| {
216 callback();
217 JsValue::Undefined
218 });
219
220 let result = self
221 .dom_access()
222 .api()
223 .get("interval")
224 .call(
225 "timeout_set",
226 vec![JsValue::U32(duration), JsValue::U64(callback_id.as_u64())],
227 )
228 .fetch();
229
230 let timer_id = if let JsValue::I32(timer_id) = result {
231 timer_id
232 } else {
233 log::error!("timeout_set -> expected i32 -> result={result:?}");
234 0
235 };
236
237 let api = self.clone();
238
239 DropResource::new(move || {
240 api.dom_access()
241 .api()
242 .get("interval")
243 .call("interval_clear", vec![JsValue::I32(timer_id)])
244 .exec();
245
246 drop_callback.off();
247 })
248 }
249
250 pub fn set_timeout_and_detach<F: Fn() + 'static>(&self, duration: u32, callback: F) {
251 let drop_box: Rc<ValueMut<Option<DropResource>>> = Rc::new(ValueMut::new(None));
252
253 let callback_with_drop = {
254 let drop_box = drop_box.clone();
255
256 move || {
257 callback();
258 drop_box.set(None);
259 }
260 };
261
262 let drop = self.timeout_set(duration, callback_with_drop);
263 drop_box.set(Some(drop));
264 }
265
266 pub fn instant_now(&self) -> InstantType {
267 self.utc_now() as InstantType
268 }
269
270 pub fn utc_now(&self) -> i64 {
271 let result = self
272 .dom_access()
273 .root("window")
274 .get("Date")
275 .call("now", vec![])
276 .fetch();
277
278 match result {
279 JsValue::I64(time) => time,
280 JsValue::F64(time) => time as i64,
281 _ => {
282 self.panic_message
283 .show(format!("api.utc_now -> incorrect result {result:?}"));
284 0_i64
285 }
286 }
287 }
288
289 pub fn timezone_offset(&self) -> i32 {
290 let result = self
291 .dom_access()
292 .api()
293 .call("getTimezoneOffset", vec![])
294 .fetch();
295
296 if let JsValue::I32(result) = result {
297 result * -60
300 } else {
301 self.panic_message.show(format!(
302 "api.timezone_offset -> incorrect result {result:?}"
303 ));
304 0
305 }
306 }
307
308 pub fn history_back(&self) {
309 self.dom_access()
310 .root("window")
311 .get("history")
312 .call("back", Vec::new())
313 .exec();
314 }
315
316 pub fn get_hash_location(&self) -> String {
321 let result = self
322 .dom_access()
323 .api()
324 .get("hashRouter")
325 .call("get", Vec::new())
326 .fetch();
327
328 if let JsValue::String(value) = result {
329 value
330 } else {
331 log::error!("hashRouter -> params decode error -> result={result:?}");
332 String::from("")
333 }
334 }
335
336 pub fn push_hash_location(&self, new_hash: &str) {
337 self.dom_access()
338 .api()
339 .get("hashRouter")
340 .call("push", vec![JsValue::str(new_hash)])
341 .exec();
342 }
343
344 pub fn on_hash_change<F: Fn(String) + 'static>(&self, callback: F) -> DropResource {
345 let (callback_id, drop_callback) = self.callback_store.register(move |data| {
346 let new_hash = if let JsValue::String(new_hash) = data {
347 new_hash
348 } else {
349 log::error!("on_hash_route_change -> string was expected -> {data:?}");
350 String::from("")
351 };
352
353 transaction(|_| {
354 callback(new_hash);
355 });
356
357 JsValue::Undefined
358 });
359
360 self.dom_access()
361 .api()
362 .get("hashRouter")
363 .call("add", vec![JsValue::U64(callback_id.as_u64())])
364 .exec();
365
366 let api = self.clone();
367
368 DropResource::new(move || {
369 api.dom_access()
370 .api()
371 .get("hashRouter")
372 .call("remove", vec![JsValue::U64(callback_id.as_u64())])
373 .exec();
374
375 drop_callback.off();
376 })
377 }
378
379 pub fn get_history_location(&self) -> String {
384 let result = self
385 .dom_access()
386 .api()
387 .get("historyLocation")
388 .call("get", Vec::new())
389 .fetch();
390
391 if let JsValue::String(value) = result {
392 self.route_from_public(value)
393 } else {
394 log::error!("historyLocation -> params decode error -> result={result:?}");
395 String::from("")
396 }
397 }
398
399 pub fn push_history_location(&self, new_path: &str) {
400 self.dom_access()
401 .api()
402 .get("historyLocation")
403 .call("push", vec![JsValue::str(new_path)])
404 .exec();
405 }
406
407 pub fn replace_history_location(&self, new_hash: &str) {
408 self.dom_access()
409 .api()
410 .get("historyLocation")
411 .call("replace", vec![JsValue::str(new_hash)])
412 .exec();
413 }
414
415 pub fn on_history_change<F: Fn(String) + 'static>(&self, callback: F) -> DropResource {
416 let myself = self.clone();
417 let (callback_id, drop_callback) = self.callback_store.register(move |data| {
418 let new_local_path = if let JsValue::String(new_path) = data {
419 myself.route_from_public(new_path.clone())
420 } else {
421 log::error!("on_history_change -> string was expected -> {data:?}");
422 String::from("")
423 };
424
425 transaction(|_| {
426 callback(new_local_path);
427 });
428
429 JsValue::Undefined
430 });
431
432 self.dom_access()
433 .api()
434 .get("historyLocation")
435 .call("add", vec![JsValue::U64(callback_id.as_u64())])
436 .exec();
437
438 let api = self.clone();
439
440 DropResource::new(move || {
441 api.dom_access()
442 .api()
443 .get("historyLocation")
444 .call("remove", vec![JsValue::U64(callback_id.as_u64())])
445 .exec();
446
447 drop_callback.off();
448 })
449 }
450
451 pub fn fetch(
455 &self,
456 method: FetchMethod,
457 url: String,
458 headers: Option<HashMap<String, String>>,
459 body: Option<RequestBody>,
460 ) -> Pin<Box<dyn Future<Output = FetchResult> + 'static>> {
461 let (sender, receiver) = FutureBox::new();
462
463 self.on_fetch_start.trigger(());
464
465 let on_fetch_stop = self.on_fetch_stop.clone();
466
467 let callback_id = self.callback_store.register_once(move |params| {
468 let params = params.convert(|mut params| {
469 let success = params.get_bool("success")?;
470 let status = params.get_u32("status")?;
471 let response = params.get_any("response")?;
472 params.expect_no_more()?;
473
474 if let JsValue::Json(json) = response {
475 return Ok((success, status, RequestBody::Json(json)));
476 }
477
478 if let JsValue::String(text) = response {
479 return Ok((success, status, RequestBody::Text(text)));
480 }
481
482 if let JsValue::Vec(buffer) = response {
483 return Ok((success, status, RequestBody::Binary(buffer)));
484 }
485
486 let name = response.typename();
487 Err(format!(
488 "Expected json or string or vec<u8>, received={name}"
489 ))
490 });
491
492 match params {
493 Ok((success, status, response)) => {
494 get_driver().transaction(|_| {
495 let response = match success {
496 true => Ok((status, response)),
497 false => Err(format!("{response:#?}")),
498 };
499 sender.publish(response);
500 on_fetch_stop.trigger(());
501 });
502 }
503 Err(error) => {
504 log::error!("export_fetch_callback -> params decode error -> {error}");
505 on_fetch_stop.trigger(());
506 }
507 }
508
509 JsValue::Undefined
510 });
511
512 let headers = {
513 let mut headers_builder = JsJsonObjectBuilder::default();
514
515 if let Some(headers) = headers {
516 for (key, value) in headers.into_iter() {
517 headers_builder = headers_builder.insert(key, value);
518 }
519 }
520
521 headers_builder.get()
522 };
523
524 self.dom_access()
525 .api()
526 .get("fetch")
527 .call(
528 "fetch_send_request",
529 vec![
530 JsValue::U64(callback_id.as_u64()),
531 JsValue::String(method.to_str()),
532 JsValue::String(url),
533 JsValue::Json(headers),
534 match body {
535 Some(RequestBody::Text(body)) => JsValue::String(body),
536 Some(RequestBody::Json(json)) => JsValue::Json(json),
537 Some(RequestBody::Binary(bin)) => JsValue::Vec(bin),
538 None => JsValue::Undefined,
539 },
540 ],
541 )
542 .exec();
543
544 Box::pin(receiver)
545 }
546
547 #[must_use]
548 pub fn websocket<F: Fn(WebsocketMessage) + 'static>(
549 &self,
550 host: impl Into<String>,
551 callback: F,
552 ) -> DropResource {
553 let host: String = host.into();
554
555 let api = self.clone();
556
557 let (callback_id, drop_callback) =
558 self.callback_store
559 .register_with_id(move |callback_id, data| {
560 if let JsValue::True = data {
561 let connection = WebsocketConnection::new(api.clone(), callback_id);
562 let connection = WebsocketMessage::Connection(connection);
563 callback(connection);
564 return JsValue::Undefined;
565 }
566
567 if let JsValue::String(message) = data {
568 callback(WebsocketMessage::Message(message));
569 return JsValue::Undefined;
570 }
571
572 if let JsValue::False = data {
573 callback(WebsocketMessage::Close);
574 return JsValue::Undefined;
575 }
576
577 log::error!("websocket - unsupported message type received");
578 JsValue::Undefined
579 });
580
581 self.websocket_register_callback(host.as_str(), callback_id.as_u64());
582
583 DropResource::new({
584 let api = self.clone();
585
586 move || {
587 api.websocket_unregister_callback(callback_id.as_u64());
588 drop_callback.off();
589 }
590 })
591 }
592
593 fn websocket_register_callback(&self, host: &str, callback_id: u64) {
594 self.dom_access()
595 .api()
596 .get("websocket")
597 .call(
598 "websocket_register_callback",
599 vec![JsValue::String(host.to_string()), JsValue::U64(callback_id)],
600 )
601 .exec();
602 }
603
604 fn websocket_unregister_callback(&self, callback_id: u64) {
605 self.dom_access()
606 .api()
607 .get("websocket")
608 .call(
609 "websocket_unregister_callback",
610 vec![JsValue::U64(callback_id)],
611 )
612 .exec();
613 }
614
615 pub fn websocket_send_message(&self, callback_id: u64, message: &str) {
616 self.dom_access()
617 .api()
618 .get("websocket")
619 .call(
620 "websocket_send_message",
621 vec![
622 JsValue::U64(callback_id),
623 JsValue::String(message.to_string()),
624 ],
625 )
626 .exec();
627 }
628
629 pub fn dom_bulk_update(&self, value: JsJson) {
630 self.dom_access()
631 .api()
632 .get("dom")
633 .call("dom_bulk_update", vec![JsValue::Json(value)])
634 .exec();
635 }
636
637 pub fn dom_access(&self) -> DomAccess {
638 DomAccess::new(
639 self.panic_message,
640 self.arguments.clone(),
641 self.fn_dom_access,
642 )
643 }
644
645 pub fn get_random(&self, min: u32, max: u32) -> u32 {
646 let result = self
647 .dom_access()
648 .api()
649 .call("getRandom", vec![JsValue::U32(min), JsValue::U32(max)])
650 .fetch();
651
652 if let JsValue::I32(result) = result {
653 result as u32
654 } else {
655 self.panic_message
656 .show(format!("api.get_random -> incorrect result {result:?}"));
657 min
658 }
659 }
660
661 pub fn is_browser(&self) -> bool {
662 let result = self
663 .dom_access()
664 .api()
665 .call("isBrowser", Vec::new())
666 .fetch();
667
668 if let JsValue::True = result {
669 return true;
670 }
671
672 if let JsValue::False = result {
673 return false;
674 }
675
676 log::error!("logical value expected");
677 false
678 }
679
680 pub fn get_env(&self, name: String) -> Option<String> {
681 let result = self
682 .dom_access()
683 .api()
684 .call("get_env", vec![JsValue::String(name)])
685 .fetch();
686
687 if let JsValue::Null = result {
688 return None;
689 }
690
691 if let JsValue::String(value) = result {
692 return Some(value);
693 }
694
695 log::error!("get_env: string or null was expected");
696 None
697 }
698
699 pub fn route_from_public(&self, path: impl Into<String>) -> String {
700 let path: String = path.into();
701 if self.is_browser() {
702 let mount_point = self.get_env("vertigo-mount-point".to_string()).unwrap_or_else(|| "/".to_string());
704 if mount_point != "/" {
705 path.trim_start_matches(&mount_point).to_string()
706 } else {
707 path
708 }
709 } else {
710 path
712 }
713 }
714
715 pub fn plain_response(&self, body: String) {
717 if self.is_browser() {
718 return;
719 }
720
721 self.dom_access()
722 .synthetic("plain_response", JsValue::String(body))
723 .exec();
724 }
725
726 pub fn set_status(&self, status: u16) {
728 if self.is_browser() {
729 return;
730 }
731
732 self.dom_access()
733 .synthetic("set_status", JsValue::U32(status as u32))
734 .exec();
735 }
736}