1use crate::namespace::Namespace;
55use async_std::sync::RwLock;
56use cache::Cache;
57use client_config::ClientConfig;
58use log::{debug, error, trace};
59use std::{collections::HashMap, sync::Arc, time::Duration};
60use wasm_bindgen::prelude::wasm_bindgen;
61
62cfg_if::cfg_if! {
63 if #[cfg(target_arch = "wasm32")] {
64 use wasm_bindgen_futures::spawn_local as spawn;
65 } else {
66 use async_std::task::spawn as spawn;
67 }
68}
69
70mod cache;
71
72pub mod client_config;
73pub mod namespace;
74
75#[derive(Debug, thiserror::Error)]
123pub enum Error {
124 #[error("Client is already running")]
130 AlreadyRunning,
131
132 #[error("Namespace error: {0}")]
137 Namespace(#[from] namespace::Error),
138
139 #[error("Cache error: {0}")]
144 Cache(#[from] cache::Error),
145}
146
147impl From<Error> for wasm_bindgen::JsValue {
148 fn from(error: Error) -> Self {
149 error.to_string().into()
150 }
151}
152
153cfg_if::cfg_if! {
159 if #[cfg(target_arch = "wasm32")] {
160 pub type EventListener = Arc<dyn Fn(Result<Namespace, Error>)>;
164 } else {
165 pub type EventListener = Arc<dyn Fn(Result<Namespace, Error>) + Send + Sync>;
173 }
174}
175
176#[wasm_bindgen]
254pub struct Client {
255 client_config: ClientConfig,
260
261 namespaces: Arc<RwLock<HashMap<String, Arc<Cache>>>>,
267
268 handle: Option<async_std::task::JoinHandle<()>>,
274
275 running: Arc<RwLock<bool>>,
280}
281
282impl Client {
283 pub(crate) async fn cache(&self, namespace: &str) -> Arc<Cache> {
293 let mut namespaces = self.namespaces.write().await;
294 let cache = namespaces.entry(namespace.to_string()).or_insert_with(|| {
295 trace!("Cache miss, creating cache for namespace {namespace}");
296 Arc::new(Cache::new(self.client_config.clone(), namespace))
297 });
298 cache.clone()
299 }
300
301 pub async fn add_listener(&self, namespace: &str, listener: EventListener) {
302 let mut namespaces = self.namespaces.write().await;
303 let cache = namespaces.entry(namespace.to_string()).or_insert_with(|| {
304 trace!("Cache miss, creating cache for namespace {namespace}");
305 Arc::new(Cache::new(self.client_config.clone(), namespace))
306 });
307 cache.add_listener(listener).await;
308 }
309
310 pub async fn namespace(&self, namespace: &str) -> Result<namespace::Namespace, Error> {
311 let cache = self.cache(namespace).await;
312 let value = cache.get_value().await?;
313 Ok(namespace::get_namespace(namespace, value)?)
314 }
315
316 pub async fn start(&mut self) -> Result<(), Error> {
333 let mut running = self.running.write().await;
334 if *running {
335 return Err(Error::AlreadyRunning);
336 }
337
338 *running = true;
339
340 let running = self.running.clone();
341 let namespaces = self.namespaces.clone();
342
343 let _handle = spawn(async move {
345 loop {
346 let running = running.read().await;
347 if !*running {
348 break;
349 }
350
351 let namespaces = namespaces.read().await;
352 for (namespace, cache) in namespaces.iter() {
354 if let Err(err) = cache.refresh().await {
355 error!("Failed to refresh cache for namespace {namespace}: {err:?}");
356 } else {
357 debug!("Successfully refreshed cache for namespace {namespace}");
358 }
359 }
360
361 async_std::task::sleep(Duration::from_secs(30)).await;
363 }
364 });
365
366 cfg_if::cfg_if! {
367 if #[cfg(target_arch = "wasm32")] {
368 self.handle = None;
369 } else {
370 self.handle = Some(_handle);
371 }
372 }
373
374 Ok(())
375 }
376
377 pub async fn stop(&mut self) {
387 let mut running = self.running.write().await;
388 *running = false;
389
390 cfg_if::cfg_if! {
391 if #[cfg(not(target_arch = "wasm32"))] {
392 if let Some(handle) = self.handle.take() {
393 handle.cancel().await;
394 }
395 }
396 }
397 }
398}
399
400#[wasm_bindgen]
401impl Client {
402 #[wasm_bindgen(constructor)]
412 pub fn new(client_config: ClientConfig) -> Self {
413 Self {
414 client_config,
415 namespaces: Arc::new(RwLock::new(HashMap::new())),
416 handle: None,
417 running: Arc::new(RwLock::new(false)),
418 }
419 }
420
421 #[cfg(target_arch = "wasm32")]
451 #[wasm_bindgen(js_name = "add_listener")]
452 pub async fn add_listener_wasm(&self, namespace: &str, js_listener: js_sys::Function) {
453 let js_listener_clone = js_listener.clone();
454
455 let event_listener: EventListener = Arc::new(move |result: Result<Namespace, Error>| {
456 let err_js_val: wasm_bindgen::JsValue;
457 let data_js_val: wasm_bindgen::JsValue;
458
459 match result {
460 Ok(value) => {
461 data_js_val = value.into();
462 err_js_val = wasm_bindgen::JsValue::UNDEFINED;
463 }
464 Err(cache_error) => {
465 err_js_val = cache_error.into();
466 data_js_val = wasm_bindgen::JsValue::UNDEFINED;
467 }
468 };
469
470 match js_listener_clone.call2(
472 &wasm_bindgen::JsValue::UNDEFINED,
473 &data_js_val,
474 &err_js_val,
475 ) {
476 Ok(_) => {
477 }
479 Err(e) => {
480 log::error!("JavaScript listener threw an error: {:?}", e);
482 }
483 }
484 });
485
486 self.add_listener(namespace, event_listener).await; }
488
489 #[cfg(target_arch = "wasm32")]
490 #[wasm_bindgen(js_name = "namespace")]
491 pub async fn namespace_wasm(&self, namespace: &str) -> Result<wasm_bindgen::JsValue, Error> {
492 let cache = self.cache(namespace).await;
493 let value = cache.get_value().await?;
494 Ok(namespace::get_namespace(namespace, value)?.into())
495 }
496}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
501 #[cfg(not(target_arch = "wasm32"))]
502 use lazy_static::lazy_static;
503
504 cfg_if::cfg_if! {
505 if #[cfg(target_arch = "wasm32")] {
506 use std::sync::Mutex;
507 } else {
508 use async_std::sync::Mutex;
509 use async_std::task::block_on;
510 }
511 }
512
513 #[cfg(not(target_arch = "wasm32"))]
514 lazy_static! {
515 pub(crate) static ref CLIENT_NO_SECRET: Client = {
516 let config = ClientConfig {
517 app_id: String::from("101010101"),
518 cluster: String::from("default"),
519 config_server: String::from("http://81.68.181.139:8080"),
520 label: None,
521 secret: None,
522 cache_dir: Some(String::from("/tmp/apollo")),
523 ip: None,
524 cache_ttl: None,
525 };
526 Client::new(config)
527 };
528 pub(crate) static ref CLIENT_WITH_SECRET: Client = {
529 let config = ClientConfig {
530 app_id: String::from("101010102"),
531 cluster: String::from("default"),
532 config_server: String::from("http://81.68.181.139:8080"),
533 label: None,
534 secret: Some(String::from("53bf47631db540ac9700f0020d2192c8")),
535 cache_dir: Some(String::from("/tmp/apollo")),
536 ip: None,
537 cache_ttl: None,
538 };
539 Client::new(config)
540 };
541 pub(crate) static ref CLIENT_WITH_GRAYSCALE_IP: Client = {
542 let config = ClientConfig {
543 app_id: String::from("101010101"),
544 cluster: String::from("default"),
545 config_server: String::from("http://81.68.181.139:8080"),
546 label: None,
547 secret: None,
548 cache_dir: Some(String::from("/tmp/apollo")),
549 ip: Some(String::from("1.2.3.4")),
550 cache_ttl: None,
551 };
552 Client::new(config)
553 };
554 pub(crate) static ref CLIENT_WITH_GRAYSCALE_LABEL: Client = {
555 let config = ClientConfig {
556 app_id: String::from("101010101"),
557 cluster: String::from("default"),
558 config_server: String::from("http://81.68.181.139:8080"),
559 label: Some(String::from("GrayScale")),
560 secret: None,
561 cache_dir: Some(String::from("/tmp/apollo")),
562 ip: None,
563 cache_ttl: None,
564 };
565 Client::new(config)
566 };
567 }
568
569 pub(crate) fn setup() {
570 cfg_if::cfg_if! {
571 if #[cfg(target_arch = "wasm32")] {
572 let _ = wasm_logger::init(wasm_logger::Config::default());
573 console_error_panic_hook::set_once();
574 } else {
575 let _ = env_logger::builder().is_test(true).try_init();
576 }
577 }
578 }
579
580 #[cfg(not(target_arch = "wasm32"))]
581 #[tokio::test]
582 async fn test_missing_value() {
583 setup();
584 let properties = match CLIENT_NO_SECRET.namespace("application").await.unwrap() {
585 namespace::Namespace::Properties(properties) => properties,
586 _ => panic!("Expected Properties namespace"),
587 };
588
589 assert_eq!(
590 properties.get_property::<String>("missingValue").await,
591 None
592 );
593 }
594
595 #[cfg(target_arch = "wasm32")]
596 #[wasm_bindgen_test::wasm_bindgen_test]
597 #[allow(dead_code)]
598 async fn test_missing_value_wasm() {
599 setup();
600 let client = create_client_no_secret();
601 let namespace = client.namespace("application").await;
602 match namespace {
603 Ok(namespace) => match namespace {
604 namespace::Namespace::Properties(properties) => {
605 assert_eq!(properties.get_string("missingValue").await, None);
606 }
607 _ => panic!("Expected Properties namespace"),
608 },
609 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
610 }
611 }
612
613 #[cfg(not(target_arch = "wasm32"))]
614 #[tokio::test]
615 async fn test_string_value() {
616 setup();
617 let properties = match CLIENT_NO_SECRET.namespace("application").await.unwrap() {
618 namespace::Namespace::Properties(properties) => properties,
619 _ => panic!("Expected Properties namespace"),
620 };
621 assert_eq!(
622 properties.get_property::<String>("stringValue").await,
623 Some("string value".to_string())
624 );
625 }
626
627 #[cfg(target_arch = "wasm32")]
628 #[wasm_bindgen_test::wasm_bindgen_test]
629 #[allow(dead_code)]
630 async fn test_string_value_wasm() {
631 setup();
632 let client = create_client_no_secret();
633 let namespace = client.namespace("application").await;
634 match namespace {
635 Ok(namespace) => match namespace {
636 namespace::Namespace::Properties(properties) => {
637 assert_eq!(
638 properties.get_string("stringValue").await,
639 Some("string value".to_string())
640 );
641 }
642 _ => panic!("Expected Properties namespace"),
643 },
644 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
645 }
646 }
647
648 #[cfg(not(target_arch = "wasm32"))]
649 #[tokio::test]
650 async fn test_string_value_with_secret() {
651 setup();
652 let properties = match CLIENT_WITH_SECRET.namespace("application").await.unwrap() {
653 namespace::Namespace::Properties(properties) => properties,
654 _ => panic!("Expected Properties namespace"),
655 };
656 assert_eq!(
657 properties.get_property::<String>("stringValue").await,
658 Some("string value".to_string())
659 );
660 }
661
662 #[cfg(target_arch = "wasm32")]
663 #[wasm_bindgen_test::wasm_bindgen_test]
664 #[allow(dead_code)]
665 async fn test_string_value_with_secret_wasm() {
666 setup();
667 let client = create_client_with_secret();
668 let namespace = client.namespace("application").await;
669 match namespace {
670 Ok(namespace) => match namespace {
671 namespace::Namespace::Properties(properties) => {
672 assert_eq!(
673 properties.get_string("stringValue").await,
674 Some("string value".to_string())
675 );
676 }
677 _ => panic!("Expected Properties namespace"),
678 },
679 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
680 }
681 }
682
683 #[cfg(not(target_arch = "wasm32"))]
684 #[tokio::test]
685 async fn test_int_value() {
686 setup();
687 let properties = match CLIENT_NO_SECRET.namespace("application").await.unwrap() {
688 namespace::Namespace::Properties(properties) => properties,
689 _ => panic!("Expected Properties namespace"),
690 };
691 assert_eq!(properties.get_property::<i32>("intValue").await, Some(42));
692 }
693
694 #[cfg(target_arch = "wasm32")]
695 #[wasm_bindgen_test::wasm_bindgen_test]
696 #[allow(dead_code)]
697 async fn test_int_value_wasm() {
698 setup();
699 let client = create_client_no_secret();
700 let namespace = client.namespace("application").await;
701 match namespace {
702 Ok(namespace) => match namespace {
703 namespace::Namespace::Properties(properties) => {
704 assert_eq!(properties.get_int("intValue").await, Some(42));
705 }
706 _ => panic!("Expected Properties namespace"),
707 },
708 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
709 }
710 }
711
712 #[cfg(not(target_arch = "wasm32"))]
713 #[tokio::test]
714 async fn test_int_value_with_secret() {
715 setup();
716 let properties = match CLIENT_WITH_SECRET.namespace("application").await.unwrap() {
717 namespace::Namespace::Properties(properties) => properties,
718 _ => panic!("Expected Properties namespace"),
719 };
720 assert_eq!(properties.get_property::<i32>("intValue").await, Some(42));
721 }
722
723 #[cfg(target_arch = "wasm32")]
724 #[wasm_bindgen_test::wasm_bindgen_test]
725 #[allow(dead_code)]
726 async fn test_int_value_with_secret_wasm() {
727 setup();
728 let client = create_client_with_secret();
729 let namespace = client.namespace("application").await;
730 match namespace {
731 Ok(namespace) => match namespace {
732 namespace::Namespace::Properties(properties) => {
733 assert_eq!(properties.get_int("intValue").await, Some(42));
734 }
735 _ => panic!("Expected Properties namespace"),
736 },
737 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
738 }
739 }
740
741 #[cfg(not(target_arch = "wasm32"))]
742 #[tokio::test]
743 async fn test_float_value() {
744 setup();
745 let properties = match CLIENT_NO_SECRET.namespace("application").await.unwrap() {
746 namespace::Namespace::Properties(properties) => properties,
747 _ => panic!("Expected Properties namespace"),
748 };
749 assert_eq!(
750 properties.get_property::<f64>("floatValue").await,
751 Some(4.20)
752 );
753 }
754
755 #[cfg(target_arch = "wasm32")]
756 #[wasm_bindgen_test::wasm_bindgen_test]
757 #[allow(dead_code)]
758 async fn test_float_value_wasm() {
759 setup();
760 let client = create_client_no_secret();
761 let namespace = client.namespace("application").await;
762 match namespace {
763 Ok(namespace) => match namespace {
764 namespace::Namespace::Properties(properties) => {
765 assert_eq!(properties.get_float("floatValue").await, Some(4.20));
766 }
767 _ => panic!("Expected Properties namespace"),
768 },
769 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
770 }
771 }
772
773 #[cfg(not(target_arch = "wasm32"))]
774 #[tokio::test]
775 async fn test_float_value_with_secret() {
776 setup();
777 let properties = match CLIENT_WITH_SECRET.namespace("application").await.unwrap() {
778 namespace::Namespace::Properties(properties) => properties,
779 _ => panic!("Expected Properties namespace"),
780 };
781 assert_eq!(
782 properties.get_property::<f64>("floatValue").await,
783 Some(4.20)
784 );
785 }
786
787 #[cfg(target_arch = "wasm32")]
788 #[wasm_bindgen_test::wasm_bindgen_test]
789 #[allow(dead_code)]
790 async fn test_float_value_with_secret_wasm() {
791 setup();
792 let client = create_client_with_secret();
793 let namespace = client.namespace("application").await;
794 match namespace {
795 Ok(namespace) => match namespace {
796 namespace::Namespace::Properties(properties) => {
797 assert_eq!(properties.get_float("floatValue").await, Some(4.20));
798 }
799 _ => panic!("Expected Properties namespace"),
800 },
801 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
802 }
803 }
804
805 #[cfg(not(target_arch = "wasm32"))]
806 #[tokio::test]
807 async fn test_bool_value() {
808 setup();
809 let properties = match CLIENT_NO_SECRET.namespace("application").await.unwrap() {
810 namespace::Namespace::Properties(properties) => properties,
811 _ => panic!("Expected Properties namespace"),
812 };
813 assert_eq!(
814 properties.get_property::<bool>("boolValue").await,
815 Some(false)
816 );
817 }
818
819 #[cfg(target_arch = "wasm32")]
820 #[wasm_bindgen_test::wasm_bindgen_test]
821 #[allow(dead_code)]
822 async fn test_bool_value_wasm() {
823 setup();
824 let client = create_client_no_secret();
825 let namespace = client.namespace("application").await;
826 match namespace {
827 Ok(namespace) => match namespace {
828 namespace::Namespace::Properties(properties) => {
829 assert_eq!(properties.get_bool("boolValue").await, Some(false));
830 }
831 _ => panic!("Expected Properties namespace"),
832 },
833 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
834 }
835 }
836
837 #[cfg(not(target_arch = "wasm32"))]
838 #[tokio::test]
839 async fn test_bool_value_with_secret() {
840 setup();
841 let properties = match CLIENT_WITH_SECRET.namespace("application").await.unwrap() {
842 namespace::Namespace::Properties(properties) => properties,
843 _ => panic!("Expected Properties namespace"),
844 };
845 assert_eq!(
846 properties.get_property::<bool>("boolValue").await,
847 Some(false)
848 );
849 }
850
851 #[cfg(target_arch = "wasm32")]
852 #[wasm_bindgen_test::wasm_bindgen_test]
853 #[allow(dead_code)]
854 async fn test_bool_value_with_secret_wasm() {
855 setup();
856 let client = create_client_with_secret();
857 let namespace = client.namespace("application").await;
858 match namespace {
859 Ok(namespace) => match namespace {
860 namespace::Namespace::Properties(properties) => {
861 assert_eq!(properties.get_bool("boolValue").await, Some(false));
862 }
863 _ => panic!("Expected Properties namespace"),
864 },
865 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
866 }
867 }
868
869 #[cfg(not(target_arch = "wasm32"))]
870 #[tokio::test]
871 async fn test_bool_value_with_grayscale_ip() {
872 setup();
873 let properties = match CLIENT_WITH_GRAYSCALE_IP
874 .namespace("application")
875 .await
876 .unwrap()
877 {
878 namespace::Namespace::Properties(properties) => properties,
879 _ => panic!("Expected Properties namespace"),
880 };
881 assert_eq!(
882 properties.get_property::<bool>("grayScaleValue").await,
883 Some(true)
884 );
885 let properties = match CLIENT_NO_SECRET.namespace("application").await.unwrap() {
886 namespace::Namespace::Properties(properties) => properties,
887 _ => panic!("Expected Properties namespace"),
888 };
889 assert_eq!(
890 properties.get_property::<bool>("grayScaleValue").await,
891 Some(false)
892 );
893 }
894
895 #[cfg(target_arch = "wasm32")]
896 #[wasm_bindgen_test::wasm_bindgen_test]
897 #[allow(dead_code)]
898 async fn test_bool_value_with_grayscale_ip_wasm() {
899 setup();
900 let client1 = create_client_with_grayscale_ip();
901 let namespace = client1.namespace("application").await;
902 match namespace {
903 Ok(namespace) => match namespace {
904 namespace::Namespace::Properties(properties) => {
905 assert_eq!(properties.get_bool("grayScaleValue").await, Some(true));
906 }
907 _ => panic!("Expected Properties namespace"),
908 },
909 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
910 }
911
912 let client2 = create_client_no_secret();
913 let namespace = client2.namespace("application").await;
914 match namespace {
915 Ok(namespace) => match namespace {
916 namespace::Namespace::Properties(properties) => {
917 assert_eq!(properties.get_bool("grayScaleValue").await, Some(false));
918 }
919 _ => panic!("Expected Properties namespace"),
920 },
921 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
922 }
923 }
924
925 #[cfg(not(target_arch = "wasm32"))]
926 #[tokio::test]
927 async fn test_bool_value_with_grayscale_label() {
928 setup();
929 let properties = match CLIENT_WITH_GRAYSCALE_LABEL
930 .namespace("application")
931 .await
932 .unwrap()
933 {
934 namespace::Namespace::Properties(properties) => properties,
935 _ => panic!("Expected Properties namespace"),
936 };
937 assert_eq!(
938 properties.get_property::<bool>("grayScaleValue").await,
939 Some(true)
940 );
941 let properties = match CLIENT_NO_SECRET.namespace("application").await.unwrap() {
942 namespace::Namespace::Properties(properties) => properties,
943 _ => panic!("Expected Properties namespace"),
944 };
945 assert_eq!(
946 properties.get_property::<bool>("grayScaleValue").await,
947 Some(false)
948 );
949 }
950
951 #[cfg(target_arch = "wasm32")]
952 #[wasm_bindgen_test::wasm_bindgen_test]
953 #[allow(dead_code)]
954 async fn test_bool_value_with_grayscale_label_wasm() {
955 setup();
956 let client1 = create_client_with_grayscale_label();
957 let namespace = client1.namespace("application").await;
958 match namespace {
959 Ok(namespace) => match namespace {
960 namespace::Namespace::Properties(properties) => {
961 assert_eq!(properties.get_bool("grayScaleValue").await, Some(true));
962 }
963 _ => panic!("Expected Properties namespace"),
964 },
965 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
966 }
967
968 let client2 = create_client_no_secret();
969 let namespace = client2.namespace("application").await;
970 match namespace {
971 Ok(namespace) => match namespace {
972 namespace::Namespace::Properties(properties) => {
973 assert_eq!(properties.get_bool("grayScaleValue").await, Some(false));
974 }
975 _ => panic!("Expected Properties namespace"),
976 },
977 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
978 }
979 }
980
981 #[cfg(target_arch = "wasm32")]
982 fn create_client_no_secret() -> Client {
983 let config = ClientConfig {
984 app_id: String::from("101010101"),
985 cluster: String::from("default"),
986 config_server: String::from("http://81.68.181.139:8080"),
987 label: None,
988 secret: None,
989 cache_dir: None,
990 ip: None,
991 cache_ttl: None,
992 };
993 Client::new(config)
994 }
995
996 #[cfg(target_arch = "wasm32")]
997 fn create_client_with_secret() -> Client {
998 let config = ClientConfig {
999 app_id: String::from("101010102"),
1000 cluster: String::from("default"),
1001 config_server: String::from("http://81.68.181.139:8080"),
1002 label: None,
1003 secret: Some(String::from("53bf47631db540ac9700f0020d2192c8")),
1004 cache_dir: None,
1005 ip: None,
1006 cache_ttl: None,
1007 };
1008 Client::new(config)
1009 }
1010
1011 #[cfg(target_arch = "wasm32")]
1012 fn create_client_with_grayscale_ip() -> Client {
1013 let config = ClientConfig {
1014 app_id: String::from("101010101"),
1015 cluster: String::from("default"),
1016 config_server: String::from("http://81.68.181.139:8080"),
1017 label: None,
1018 secret: None,
1019 cache_dir: None,
1020 ip: Some(String::from("1.2.3.4")),
1021 cache_ttl: None,
1022 };
1023 Client::new(config)
1024 }
1025
1026 #[cfg(target_arch = "wasm32")]
1027 fn create_client_with_grayscale_label() -> Client {
1028 let config = ClientConfig {
1029 app_id: String::from("101010101"),
1030 cluster: String::from("default"),
1031 config_server: String::from("http://81.68.181.139:8080"),
1032 label: Some(String::from("GrayScale")),
1033 secret: None,
1034 cache_dir: None,
1035 ip: None,
1036 cache_ttl: None,
1037 };
1038 Client::new(config)
1039 }
1040
1041 #[cfg(not(target_arch = "wasm32"))]
1042 #[tokio::test] async fn test_add_listener_and_notify_on_refresh() {
1044 setup();
1045
1046 let listener_called_flag = Arc::new(Mutex::new(false));
1048 let received_config_data = Arc::new(Mutex::new(None::<Namespace>));
1049
1050 let config = ClientConfig {
1053 config_server: "http://81.68.181.139:8080".to_string(), app_id: "101010101".to_string(), cluster: "default".to_string(),
1056 cache_dir: Some(String::from("/tmp/apollo")), secret: None,
1058 label: None,
1059 ip: None,
1060 cache_ttl: None,
1061 };
1063
1064 let client = Client::new(config);
1065
1066 let flag_clone = listener_called_flag.clone();
1067 let data_clone = received_config_data.clone();
1068
1069 let listener: EventListener = Arc::new(move |result| {
1070 let mut called_guard = block_on(flag_clone.lock());
1071 *called_guard = true;
1072 if let Ok(config_value) = result {
1073 match config_value {
1074 Namespace::Properties(_) => {
1075 let mut data_guard = block_on(data_clone.lock());
1076 *data_guard = Some(config_value.clone());
1077 }
1078 _ => {
1079 panic!("Expected Properties namespace, got {config_value:?}");
1080 }
1081 }
1082 }
1083 });
1086
1087 client.add_listener("application", listener).await;
1088
1089 let cache = client.cache("application").await;
1090
1091 match cache.refresh().await {
1094 Ok(_) => log::debug!("Refresh successful for test_add_listener_and_notify_on_refresh"),
1095 Err(e) => panic!("Cache refresh failed during test: {e:?}"),
1096 }
1097
1098 let called = *listener_called_flag.lock().await;
1100 assert!(called, "Listener was not called.");
1101
1102 let config_data_guard = received_config_data.lock().await;
1104 assert!(
1105 config_data_guard.is_some(),
1106 "Listener did not receive config data."
1107 );
1108
1109 if let Some(value) = config_data_guard.as_ref() {
1113 match value {
1114 Namespace::Properties(properties) => {
1115 assert_eq!(
1116 properties.get_string("stringValue").await,
1117 Some(String::from("string value")),
1118 "Received config data does not match expected content for stringValue."
1119 );
1120 }
1121 _ => {
1122 panic!("Expected Properties namespace, got {value:?}");
1123 }
1124 }
1125 }
1126 }
1127
1128 #[cfg(target_arch = "wasm32")]
1129 #[wasm_bindgen_test::wasm_bindgen_test]
1130 async fn test_add_listener_wasm_and_notify() {
1131 setup(); let listener_called_flag = Arc::new(Mutex::new(false));
1135 let received_config_data = Arc::new(Mutex::new(None::<Namespace>));
1136
1137 let flag_clone = listener_called_flag.clone();
1138 let data_clone = received_config_data.clone();
1139
1140 let js_listener_func_body = format!(
1142 r#"
1143 (data, error) => {{
1144 // We can't use window in Node.js, so we'll use a different approach
1145 // The Rust closure will handle the verification
1146 console.log('JS Listener called with error:', error);
1147 console.log('JS Listener called with data:', data);
1148 }}
1149 "#
1150 );
1151
1152 let js_listener = js_sys::Function::new_with_args("data, error", &js_listener_func_body);
1153
1154 let client = create_client_no_secret();
1155
1156 let rust_listener: EventListener = Arc::new(move |result| {
1158 let mut called_guard = flag_clone.lock().unwrap();
1159 *called_guard = true;
1160 if let Ok(config_value) = result {
1161 let mut data_guard = data_clone.lock().unwrap();
1162 *data_guard = Some(config_value);
1163 }
1164 });
1165
1166 client.add_listener("application", rust_listener).await;
1167
1168 client.add_listener_wasm("application", js_listener).await;
1170
1171 let cache = client.cache("application").await;
1172
1173 match cache.refresh().await {
1175 Ok(_) => web_sys::console::log_1(&"WASM Test: Refresh successful".into()), Err(e) => panic!("WASM Test: Cache refresh failed: {:?}", e),
1177 }
1178
1179 let called = *listener_called_flag.lock().unwrap();
1181 assert!(called, "Listener was not called.");
1182
1183 let config_data_guard = received_config_data.lock().unwrap();
1185 assert!(
1186 config_data_guard.is_some(),
1187 "Listener did not receive config data."
1188 );
1189
1190 if let Some(value) = config_data_guard.as_ref() {
1192 match value {
1193 namespace::Namespace::Properties(properties) => {
1194 assert_eq!(
1195 properties.get_string("stringValue").await,
1196 Some("string value".to_string())
1197 );
1198 }
1199 _ => panic!("Expected Properties namespace"),
1200 }
1201 }
1202 }
1203}