1use crate::namespace::Namespace;
57use async_std::sync::RwLock;
58use cache::Cache;
59use client_config::ClientConfig;
60use log::{error, trace};
61use std::{collections::HashMap, sync::Arc};
62use wasm_bindgen::prelude::wasm_bindgen;
63
64cfg_if::cfg_if! {
65 if #[cfg(not(target_arch = "wasm32"))] {
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)]
125pub enum Error {
126 #[error("Client is already running")]
132 AlreadyRunning,
133
134 #[error("Namespace error: {0}")]
139 Namespace(#[from] namespace::Error),
140
141 #[error("Cache error: {0}")]
146 Cache(#[from] cache::Error),
147}
148
149impl From<Error> for wasm_bindgen::JsValue {
150 fn from(error: Error) -> Self {
151 error.to_string().into()
152 }
153}
154
155cfg_if::cfg_if! {
161 if #[cfg(target_arch = "wasm32")] {
162 pub type EventListener = Arc<dyn Fn(Result<Namespace, Error>)>;
166 } else {
167 pub type EventListener = Arc<dyn Fn(Result<Namespace, Error>) + Send + Sync>;
175 }
176}
177
178#[wasm_bindgen]
226pub struct Client {
227 config: ClientConfig,
232
233 namespaces: Arc<RwLock<HashMap<String, Arc<Cache>>>>,
239
240 handle: Option<async_std::task::JoinHandle<()>>,
246
247 running: Arc<RwLock<bool>>,
252}
253
254impl Client {
255 pub(crate) async fn cache(&self, namespace: &str) -> Arc<Cache> {
265 let mut namespaces = self.namespaces.write().await;
266 let cache = namespaces.entry(namespace.to_string()).or_insert_with(|| {
267 trace!("Cache miss, creating cache for namespace {namespace}");
268 Arc::new(Cache::new(self.config.clone(), namespace))
269 });
270 cache.clone()
271 }
272
273 pub async fn add_listener(&self, namespace: &str, listener: EventListener) {
274 let mut namespaces = self.namespaces.write().await;
275 let cache = namespaces.entry(namespace.to_string()).or_insert_with(|| {
276 trace!("Cache miss, creating cache for namespace {namespace}");
277 Arc::new(Cache::new(self.config.clone(), namespace))
278 });
279 cache.add_listener(listener).await;
280 }
281
282 pub async fn namespace(&self, namespace: &str) -> Result<namespace::Namespace, Error> {
347 let cache = self.cache(namespace).await;
348 let value = cache.get_value().await?;
349 Ok(namespace::get_namespace(namespace, value)?)
350 }
351
352 pub async fn start(&mut self) -> Result<(), Error> {
375 let mut running = self.running.write().await;
376 if *running {
377 return Err(Error::AlreadyRunning);
378 }
379
380 *running = true;
381
382 cfg_if::cfg_if! {
383 if #[cfg(target_arch = "wasm32")] {
384 self.handle = None;
385 } else {
386 let running = self.running.clone();
387 let namespaces = self.namespaces.clone();
388 let handle = spawn(async move {
390 loop {
391 let running = running.read().await;
392 if !*running {
393 break;
394 }
395
396 let namespaces = namespaces.read().await;
397 for (namespace, cache) in namespaces.iter() {
399 if let Err(err) = cache.refresh().await {
400 error!("Failed to refresh cache for namespace {namespace}: {err:?}");
401 } else {
402 log::debug!("Successfully refreshed cache for namespace {namespace}");
403 }
404 }
405
406 async_std::task::sleep(std::time::Duration::from_secs(30)).await;
408 }
409 });
410 self.handle = Some(handle);
411 }
412 }
413
414 Ok(())
415 }
416
417 pub async fn stop(&mut self) {
427 let mut running = self.running.write().await;
428 *running = false;
429
430 cfg_if::cfg_if! {
431 if #[cfg(not(target_arch = "wasm32"))] {
432 if let Some(handle) = self.handle.take() {
433 handle.cancel().await;
434 }
435 }
436 }
437 }
438}
439
440#[wasm_bindgen]
441impl Client {
442 #[wasm_bindgen(constructor)]
452 #[must_use]
453 pub fn new(config: ClientConfig) -> Self {
454 Self {
455 config,
456 namespaces: Arc::new(RwLock::new(HashMap::new())),
457 handle: None,
458 running: Arc::new(RwLock::new(false)),
459 }
460 }
461
462 #[cfg(target_arch = "wasm32")]
492 #[wasm_bindgen(js_name = "add_listener")]
493 pub async fn add_listener_wasm(&self, namespace: &str, js_listener: js_sys::Function) {
494 let js_listener_clone = js_listener.clone();
495
496 let event_listener: EventListener = Arc::new(move |result: Result<Namespace, Error>| {
497 let err_js_val: wasm_bindgen::JsValue;
498 let data_js_val: wasm_bindgen::JsValue;
499
500 match result {
501 Ok(value) => {
502 data_js_val = value.into();
503 err_js_val = wasm_bindgen::JsValue::UNDEFINED;
504 }
505 Err(cache_error) => {
506 err_js_val = cache_error.into();
507 data_js_val = wasm_bindgen::JsValue::UNDEFINED;
508 }
509 };
510
511 match js_listener_clone.call2(
513 &wasm_bindgen::JsValue::UNDEFINED,
514 &data_js_val,
515 &err_js_val,
516 ) {
517 Ok(_) => {
518 }
520 Err(e) => {
521 log::error!("JavaScript listener threw an error: {:?}", e);
523 }
524 }
525 });
526
527 self.add_listener(namespace, event_listener).await; }
529
530 #[cfg(target_arch = "wasm32")]
531 #[wasm_bindgen(js_name = "namespace")]
532 pub async fn namespace_wasm(&self, namespace: &str) -> Result<wasm_bindgen::JsValue, Error> {
533 let cache = self.cache(namespace).await;
534 let value = cache.get_value().await?;
535 Ok(namespace::get_namespace(namespace, value)?.into())
536 }
537}
538
539#[cfg(test)]
540mod tests {
541 use super::*;
542
543 cfg_if::cfg_if! {
544 if #[cfg(target_arch = "wasm32")] {
545 use std::sync::Mutex;
546 } else {
547 use async_std::sync::Mutex;
548 use async_std::task::block_on;
549 }
550 }
551
552 #[cfg(not(target_arch = "wasm32"))]
553 pub(crate) static CLIENT_NO_SECRET: std::sync::LazyLock<Client> =
554 std::sync::LazyLock::new(|| {
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: None,
560 secret: None,
561 cache_dir: Some(String::from("/tmp/apollo")),
562 ip: None,
563 allow_insecure_https: None,
564 #[cfg(not(target_arch = "wasm32"))]
565 cache_ttl: None,
566 };
567 Client::new(config)
568 });
569
570 #[cfg(not(target_arch = "wasm32"))]
571 pub(crate) static CLIENT_WITH_SECRET: std::sync::LazyLock<Client> =
572 std::sync::LazyLock::new(|| {
573 let config = ClientConfig {
574 app_id: String::from("101010102"),
575 cluster: String::from("default"),
576 config_server: String::from("http://81.68.181.139:8080"),
577 label: None,
578 secret: Some(String::from("53bf47631db540ac9700f0020d2192c8")),
579 cache_dir: Some(String::from("/tmp/apollo")),
580 ip: None,
581 allow_insecure_https: None,
582 #[cfg(not(target_arch = "wasm32"))]
583 cache_ttl: None,
584 };
585 Client::new(config)
586 });
587
588 #[cfg(not(target_arch = "wasm32"))]
589 pub(crate) static CLIENT_WITH_GRAYSCALE_IP: std::sync::LazyLock<Client> =
590 std::sync::LazyLock::new(|| {
591 let config = ClientConfig {
592 app_id: String::from("101010101"),
593 cluster: String::from("default"),
594 config_server: String::from("http://81.68.181.139:8080"),
595 label: None,
596 secret: None,
597 cache_dir: Some(String::from("/tmp/apollo")),
598 ip: Some(String::from("1.2.3.4")),
599 allow_insecure_https: None,
600 #[cfg(not(target_arch = "wasm32"))]
601 cache_ttl: None,
602 };
603 Client::new(config)
604 });
605
606 #[cfg(not(target_arch = "wasm32"))]
607 pub(crate) static CLIENT_WITH_GRAYSCALE_LABEL: std::sync::LazyLock<Client> =
608 std::sync::LazyLock::new(|| {
609 let config = ClientConfig {
610 app_id: String::from("101010101"),
611 cluster: String::from("default"),
612 config_server: String::from("http://81.68.181.139:8080"),
613 label: Some(String::from("GrayScale")),
614 secret: None,
615 cache_dir: Some(String::from("/tmp/apollo")),
616 ip: None,
617 allow_insecure_https: None,
618 #[cfg(not(target_arch = "wasm32"))]
619 cache_ttl: None,
620 };
621 Client::new(config)
622 });
623
624 pub(crate) fn setup() {
625 cfg_if::cfg_if! {
626 if #[cfg(target_arch = "wasm32")] {
627 let _ = wasm_logger::init(wasm_logger::Config::default());
628 console_error_panic_hook::set_once();
629 } else {
630 let _ = env_logger::builder().is_test(true).try_init();
631 }
632 }
633 }
634
635 #[cfg(not(target_arch = "wasm32"))]
636 #[tokio::test]
637 async fn test_missing_value() {
638 setup();
639 let namespace::Namespace::Properties(properties) =
640 CLIENT_NO_SECRET.namespace("application").await.unwrap()
641 else {
642 panic!("Expected Properties namespace");
643 };
644
645 assert_eq!(properties.get_property::<String>("missingValue"), None);
646 }
647
648 #[cfg(target_arch = "wasm32")]
649 #[wasm_bindgen_test::wasm_bindgen_test]
650 #[allow(dead_code)]
651 async fn test_missing_value_wasm() {
652 setup();
653 let client = create_client_no_secret();
654 let namespace = client.namespace("application").await;
655 match namespace {
656 Ok(namespace) => match namespace {
657 namespace::Namespace::Properties(properties) => {
658 assert_eq!(properties.get_string("missingValue"), None);
659 }
660 _ => panic!("Expected Properties namespace"),
661 },
662 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
663 }
664 }
665
666 #[cfg(not(target_arch = "wasm32"))]
667 #[tokio::test]
668 async fn test_string_value() {
669 setup();
670 let namespace::Namespace::Properties(properties) =
671 CLIENT_NO_SECRET.namespace("application").await.unwrap()
672 else {
673 panic!("Expected Properties namespace");
674 };
675
676 assert_eq!(
677 properties.get_property::<String>("stringValue"),
678 Some("string value".to_string())
679 );
680 }
681
682 #[cfg(target_arch = "wasm32")]
683 #[wasm_bindgen_test::wasm_bindgen_test]
684 #[allow(dead_code)]
685 async fn test_string_value_wasm() {
686 setup();
687 let client = create_client_no_secret();
688 let namespace = client.namespace("application").await;
689 match namespace {
690 Ok(namespace) => match namespace {
691 namespace::Namespace::Properties(properties) => {
692 assert_eq!(
693 properties.get_string("stringValue"),
694 Some("string value".to_string())
695 );
696 }
697 _ => panic!("Expected Properties namespace"),
698 },
699 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
700 }
701 }
702
703 #[cfg(not(target_arch = "wasm32"))]
704 #[tokio::test]
705 async fn test_string_value_with_secret() {
706 setup();
707 let namespace::Namespace::Properties(properties) =
708 CLIENT_WITH_SECRET.namespace("application").await.unwrap()
709 else {
710 panic!("Expected Properties namespace");
711 };
712 assert_eq!(
713 properties.get_property::<String>("stringValue"),
714 Some("string value".to_string())
715 );
716 }
717
718 #[cfg(target_arch = "wasm32")]
719 #[wasm_bindgen_test::wasm_bindgen_test]
720 #[allow(dead_code)]
721 async fn test_string_value_with_secret_wasm() {
722 setup();
723 let client = create_client_with_secret();
724 let namespace = client.namespace("application").await;
725 match namespace {
726 Ok(namespace) => match namespace {
727 namespace::Namespace::Properties(properties) => {
728 assert_eq!(
729 properties.get_string("stringValue"),
730 Some("string value".to_string())
731 );
732 }
733 _ => panic!("Expected Properties namespace"),
734 },
735 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
736 }
737 }
738
739 #[cfg(not(target_arch = "wasm32"))]
740 #[tokio::test]
741 async fn test_int_value() {
742 setup();
743 let namespace::Namespace::Properties(properties) =
744 CLIENT_NO_SECRET.namespace("application").await.unwrap()
745 else {
746 panic!("Expected Properties namespace");
747 };
748 assert_eq!(properties.get_property::<i32>("intValue"), Some(42));
749 }
750
751 #[cfg(target_arch = "wasm32")]
752 #[wasm_bindgen_test::wasm_bindgen_test]
753 #[allow(dead_code)]
754 async fn test_int_value_wasm() {
755 setup();
756 let client = create_client_no_secret();
757 let namespace = client.namespace("application").await;
758 match namespace {
759 Ok(namespace) => match namespace {
760 namespace::Namespace::Properties(properties) => {
761 assert_eq!(properties.get_int("intValue"), Some(42));
762 }
763 _ => panic!("Expected Properties namespace"),
764 },
765 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
766 }
767 }
768
769 #[cfg(not(target_arch = "wasm32"))]
770 #[tokio::test]
771 async fn test_int_value_with_secret() {
772 setup();
773 let namespace::Namespace::Properties(properties) =
774 CLIENT_WITH_SECRET.namespace("application").await.unwrap()
775 else {
776 panic!("Expected Properties namespace");
777 };
778 assert_eq!(properties.get_property::<i32>("intValue"), Some(42));
779 }
780
781 #[cfg(target_arch = "wasm32")]
782 #[wasm_bindgen_test::wasm_bindgen_test]
783 #[allow(dead_code)]
784 async fn test_int_value_with_secret_wasm() {
785 setup();
786 let client = create_client_with_secret();
787 let namespace = client.namespace("application").await;
788 match namespace {
789 Ok(namespace) => match namespace {
790 namespace::Namespace::Properties(properties) => {
791 assert_eq!(properties.get_int("intValue"), Some(42));
792 }
793 _ => panic!("Expected Properties namespace"),
794 },
795 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
796 }
797 }
798
799 #[cfg(not(target_arch = "wasm32"))]
800 #[tokio::test]
801 async fn test_float_value() {
802 setup();
803 let namespace::Namespace::Properties(properties) =
804 CLIENT_NO_SECRET.namespace("application").await.unwrap()
805 else {
806 panic!("Expected Properties namespace");
807 };
808 assert_eq!(properties.get_property::<f64>("floatValue"), Some(4.20));
809 }
810
811 #[cfg(target_arch = "wasm32")]
812 #[wasm_bindgen_test::wasm_bindgen_test]
813 #[allow(dead_code)]
814 async fn test_float_value_wasm() {
815 setup();
816 let client = create_client_no_secret();
817 let namespace = client.namespace("application").await;
818 match namespace {
819 Ok(namespace) => match namespace {
820 namespace::Namespace::Properties(properties) => {
821 assert_eq!(properties.get_float("floatValue"), Some(4.20));
822 }
823 _ => panic!("Expected Properties namespace"),
824 },
825 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
826 }
827 }
828
829 #[cfg(not(target_arch = "wasm32"))]
830 #[tokio::test]
831 async fn test_float_value_with_secret() {
832 setup();
833 let namespace::Namespace::Properties(properties) =
834 CLIENT_WITH_SECRET.namespace("application").await.unwrap()
835 else {
836 panic!("Expected Properties namespace");
837 };
838 assert_eq!(properties.get_property::<f64>("floatValue"), Some(4.20));
839 }
840
841 #[cfg(target_arch = "wasm32")]
842 #[wasm_bindgen_test::wasm_bindgen_test]
843 #[allow(dead_code)]
844 async fn test_float_value_with_secret_wasm() {
845 setup();
846 let client = create_client_with_secret();
847 let namespace = client.namespace("application").await;
848 match namespace {
849 Ok(namespace) => match namespace {
850 namespace::Namespace::Properties(properties) => {
851 assert_eq!(properties.get_float("floatValue"), Some(4.20));
852 }
853 _ => panic!("Expected Properties namespace"),
854 },
855 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
856 }
857 }
858
859 #[cfg(not(target_arch = "wasm32"))]
860 #[tokio::test]
861 async fn test_bool_value() {
862 setup();
863 let namespace::Namespace::Properties(properties) =
864 CLIENT_NO_SECRET.namespace("application").await.unwrap()
865 else {
866 panic!("Expected Properties namespace");
867 };
868 assert_eq!(properties.get_property::<bool>("boolValue"), Some(false));
869 }
870
871 #[cfg(target_arch = "wasm32")]
872 #[wasm_bindgen_test::wasm_bindgen_test]
873 #[allow(dead_code)]
874 async fn test_bool_value_wasm() {
875 setup();
876 let client = create_client_no_secret();
877 let namespace = client.namespace("application").await;
878 match namespace {
879 Ok(namespace) => match namespace {
880 namespace::Namespace::Properties(properties) => {
881 assert_eq!(properties.get_bool("boolValue"), Some(false));
882 }
883 _ => panic!("Expected Properties namespace"),
884 },
885 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
886 }
887 }
888
889 #[cfg(not(target_arch = "wasm32"))]
890 #[tokio::test]
891 async fn test_bool_value_with_secret() {
892 setup();
893 let namespace::Namespace::Properties(properties) =
894 CLIENT_WITH_SECRET.namespace("application").await.unwrap()
895 else {
896 panic!("Expected Properties namespace");
897 };
898 assert_eq!(properties.get_property::<bool>("boolValue"), Some(false));
899 }
900
901 #[cfg(target_arch = "wasm32")]
902 #[wasm_bindgen_test::wasm_bindgen_test]
903 #[allow(dead_code)]
904 async fn test_bool_value_with_secret_wasm() {
905 setup();
906 let client = create_client_with_secret();
907 let namespace = client.namespace("application").await;
908 match namespace {
909 Ok(namespace) => match namespace {
910 namespace::Namespace::Properties(properties) => {
911 assert_eq!(properties.get_bool("boolValue"), Some(false));
912 }
913 _ => panic!("Expected Properties namespace"),
914 },
915 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
916 }
917 }
918
919 #[cfg(not(target_arch = "wasm32"))]
920 #[tokio::test]
921 async fn test_bool_value_with_grayscale_ip() {
922 setup();
923 let namespace::Namespace::Properties(properties) = CLIENT_WITH_GRAYSCALE_IP
924 .namespace("application")
925 .await
926 .unwrap()
927 else {
928 panic!("Expected Properties namespace");
929 };
930 assert_eq!(
931 properties.get_property::<bool>("grayScaleValue"),
932 Some(true)
933 );
934 let namespace::Namespace::Properties(properties) =
935 CLIENT_NO_SECRET.namespace("application").await.unwrap()
936 else {
937 panic!("Expected Properties namespace");
938 };
939 assert_eq!(
940 properties.get_property::<bool>("grayScaleValue"),
941 Some(false)
942 );
943 }
944
945 #[cfg(target_arch = "wasm32")]
946 #[wasm_bindgen_test::wasm_bindgen_test]
947 #[allow(dead_code)]
948 async fn test_bool_value_with_grayscale_ip_wasm() {
949 setup();
950 let client1 = create_client_with_grayscale_ip();
951 let namespace = client1.namespace("application").await;
952 match namespace {
953 Ok(namespace) => match namespace {
954 namespace::Namespace::Properties(properties) => {
955 assert_eq!(properties.get_bool("grayScaleValue"), Some(true));
956 }
957 _ => panic!("Expected Properties namespace"),
958 },
959 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
960 }
961
962 let client2 = create_client_no_secret();
963 let namespace = client2.namespace("application").await;
964 match namespace {
965 Ok(namespace) => match namespace {
966 namespace::Namespace::Properties(properties) => {
967 assert_eq!(properties.get_bool("grayScaleValue"), Some(false));
968 }
969 _ => panic!("Expected Properties namespace"),
970 },
971 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
972 }
973 }
974
975 #[cfg(not(target_arch = "wasm32"))]
976 #[tokio::test]
977 async fn test_bool_value_with_grayscale_label() {
978 setup();
979 let namespace::Namespace::Properties(properties) = CLIENT_WITH_GRAYSCALE_LABEL
980 .namespace("application")
981 .await
982 .unwrap()
983 else {
984 panic!("Expected Properties namespace");
985 };
986 assert_eq!(
987 properties.get_property::<bool>("grayScaleValue"),
988 Some(true)
989 );
990 let namespace::Namespace::Properties(properties) =
991 CLIENT_NO_SECRET.namespace("application").await.unwrap()
992 else {
993 panic!("Expected Properties namespace");
994 };
995 assert_eq!(
996 properties.get_property::<bool>("grayScaleValue"),
997 Some(false)
998 );
999 }
1000
1001 #[cfg(target_arch = "wasm32")]
1002 #[wasm_bindgen_test::wasm_bindgen_test]
1003 #[allow(dead_code)]
1004 async fn test_bool_value_with_grayscale_label_wasm() {
1005 setup();
1006 let client1 = create_client_with_grayscale_label();
1007 let namespace = client1.namespace("application").await;
1008 match namespace {
1009 Ok(namespace) => match namespace {
1010 namespace::Namespace::Properties(properties) => {
1011 assert_eq!(properties.get_bool("grayScaleValue"), Some(true));
1012 }
1013 _ => panic!("Expected Properties namespace"),
1014 },
1015 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
1016 }
1017
1018 let client2 = create_client_no_secret();
1019 let namespace = client2.namespace("application").await;
1020 match namespace {
1021 Ok(namespace) => match namespace {
1022 namespace::Namespace::Properties(properties) => {
1023 assert_eq!(properties.get_bool("grayScaleValue"), Some(false));
1024 }
1025 _ => panic!("Expected Properties namespace"),
1026 },
1027 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
1028 }
1029 }
1030
1031 #[cfg(target_arch = "wasm32")]
1032 fn create_client_no_secret() -> Client {
1033 let config = ClientConfig {
1034 app_id: String::from("101010101"),
1035 cluster: String::from("default"),
1036 config_server: String::from("http://81.68.181.139:8080"),
1037 label: None,
1038 secret: None,
1039 cache_dir: None,
1040 ip: None,
1041 allow_insecure_https: None,
1042 };
1043 Client::new(config)
1044 }
1045
1046 #[cfg(target_arch = "wasm32")]
1047 fn create_client_with_secret() -> Client {
1048 let config = ClientConfig {
1049 app_id: String::from("101010102"),
1050 cluster: String::from("default"),
1051 config_server: String::from("http://81.68.181.139:8080"),
1052 label: None,
1053 secret: Some(String::from("53bf47631db540ac9700f0020d2192c8")),
1054 cache_dir: None,
1055 ip: None,
1056 allow_insecure_https: None,
1057 };
1058 Client::new(config)
1059 }
1060
1061 #[cfg(target_arch = "wasm32")]
1062 fn create_client_with_grayscale_ip() -> Client {
1063 let config = ClientConfig {
1064 app_id: String::from("101010101"),
1065 cluster: String::from("default"),
1066 config_server: String::from("http://81.68.181.139:8080"),
1067 label: None,
1068 secret: None,
1069 cache_dir: None,
1070 ip: Some(String::from("1.2.3.4")),
1071 allow_insecure_https: None,
1072 };
1073 Client::new(config)
1074 }
1075
1076 #[cfg(target_arch = "wasm32")]
1077 fn create_client_with_grayscale_label() -> Client {
1078 let config = ClientConfig {
1079 app_id: String::from("101010101"),
1080 cluster: String::from("default"),
1081 config_server: String::from("http://81.68.181.139:8080"),
1082 label: Some(String::from("GrayScale")),
1083 secret: None,
1084 cache_dir: None,
1085 ip: None,
1086 allow_insecure_https: None,
1087 };
1088 Client::new(config)
1089 }
1090
1091 #[cfg(not(target_arch = "wasm32"))]
1092 #[tokio::test] async fn test_add_listener_and_notify_on_refresh() {
1094 setup();
1095
1096 let listener_called_flag = Arc::new(Mutex::new(false));
1098 let received_config_data = Arc::new(Mutex::new(None::<Namespace>));
1099
1100 let config = ClientConfig {
1103 config_server: "http://81.68.181.139:8080".to_string(), app_id: "101010101".to_string(), cluster: "default".to_string(),
1106 cache_dir: Some(String::from("/tmp/apollo")), secret: None,
1108 label: None,
1109 ip: None,
1110 allow_insecure_https: None,
1111 #[cfg(not(target_arch = "wasm32"))]
1112 cache_ttl: None,
1113 };
1115
1116 let client = Client::new(config);
1117
1118 let flag_clone = listener_called_flag.clone();
1119 let data_clone = received_config_data.clone();
1120
1121 let listener: EventListener = Arc::new(move |result| {
1122 let mut called_guard = block_on(flag_clone.lock());
1123 *called_guard = true;
1124 if let Ok(config_value) = result {
1125 match config_value {
1126 Namespace::Properties(_) => {
1127 let mut data_guard = block_on(data_clone.lock());
1128 *data_guard = Some(config_value.clone());
1129 }
1130 _ => {
1131 panic!("Expected Properties namespace, got {config_value:?}");
1132 }
1133 }
1134 }
1135 });
1138
1139 client.add_listener("application", listener).await;
1140
1141 let cache = client.cache("application").await;
1142
1143 match cache.refresh().await {
1146 Ok(()) => log::debug!("Refresh successful for test_add_listener_and_notify_on_refresh"),
1147 Err(e) => panic!("Cache refresh failed during test: {e:?}"),
1148 }
1149
1150 let called = *listener_called_flag.lock().await;
1152 assert!(called, "Listener was not called.");
1153
1154 let config_data_guard = received_config_data.lock().await;
1156 assert!(
1157 config_data_guard.is_some(),
1158 "Listener did not receive config data."
1159 );
1160
1161 if let Some(value) = config_data_guard.as_ref() {
1165 match value {
1166 Namespace::Properties(properties) => {
1167 assert_eq!(
1168 properties.get_string("stringValue"),
1169 Some(String::from("string value")),
1170 "Received config data does not match expected content for stringValue."
1171 );
1172 }
1173 _ => {
1174 panic!("Expected Properties namespace, got {value:?}");
1175 }
1176 }
1177 }
1178 }
1179
1180 #[cfg(target_arch = "wasm32")]
1181 #[wasm_bindgen_test::wasm_bindgen_test]
1182 async fn test_add_listener_wasm_and_notify() {
1183 setup(); let listener_called_flag = Arc::new(Mutex::new(false));
1187 let received_config_data = Arc::new(Mutex::new(None::<Namespace>));
1188
1189 let flag_clone = listener_called_flag.clone();
1190 let data_clone = received_config_data.clone();
1191
1192 let js_listener_func_body = format!(
1194 r#"
1195 (data, error) => {{
1196 // We can't use window in Node.js, so we'll use a different approach
1197 // The Rust closure will handle the verification
1198 console.log('JS Listener called with error:', error);
1199 console.log('JS Listener called with data:', data);
1200 }}
1201 "#
1202 );
1203
1204 let js_listener = js_sys::Function::new_with_args("data, error", &js_listener_func_body);
1205
1206 let client = create_client_no_secret();
1207
1208 let rust_listener: EventListener = Arc::new(move |result| {
1210 let mut called_guard = flag_clone.lock().unwrap();
1211 *called_guard = true;
1212 if let Ok(config_value) = result {
1213 let mut data_guard = data_clone.lock().unwrap();
1214 *data_guard = Some(config_value);
1215 }
1216 });
1217
1218 client.add_listener("application", rust_listener).await;
1219
1220 client.add_listener_wasm("application", js_listener).await;
1222
1223 let cache = client.cache("application").await;
1224
1225 match cache.refresh().await {
1227 Ok(_) => web_sys::console::log_1(&"WASM Test: Refresh successful".into()), Err(e) => panic!("WASM Test: Cache refresh failed: {:?}", e),
1229 }
1230
1231 let called = *listener_called_flag.lock().unwrap();
1233 assert!(called, "Listener was not called.");
1234
1235 let config_data_guard = received_config_data.lock().unwrap();
1237 assert!(
1238 config_data_guard.is_some(),
1239 "Listener did not receive config data."
1240 );
1241
1242 if let Some(value) = config_data_guard.as_ref() {
1244 match value {
1245 namespace::Namespace::Properties(properties) => {
1246 assert_eq!(
1247 properties.get_string("stringValue"),
1248 Some("string value".to_string())
1249 );
1250 }
1251 _ => panic!("Expected Properties namespace"),
1252 }
1253 }
1254 }
1255}