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)]
540pub(crate) struct TempDir {
541 path: std::path::PathBuf,
542}
543
544#[cfg(test)]
545impl TempDir {
546 pub(crate) fn new(name: &str) -> Self {
547 let path = std::env::temp_dir().join(name);
548 let _ = std::fs::create_dir_all(&path);
550 Self { path }
551 }
552
553 pub(crate) fn path(&self) -> &std::path::Path {
554 &self.path
555 }
556}
557
558#[cfg(test)]
559impl Drop for TempDir {
560 fn drop(&mut self) {
561 let _ = std::fs::remove_dir_all(&self.path);
563 }
564}
565
566#[cfg(test)]
567pub(crate) fn setup() {
568 cfg_if::cfg_if! {
569 if #[cfg(target_arch = "wasm32")] {
570 let _ = wasm_logger::init(wasm_logger::Config::default());
571 console_error_panic_hook::set_once();
572 } else {
573 let _ = env_logger::builder().is_test(true).try_init();
574 }
575 }
576}
577
578#[cfg(test)]
579mod tests {
580 use super::*;
581
582 cfg_if::cfg_if! {
583 if #[cfg(target_arch = "wasm32")] {
584 use std::sync::Mutex;
585 } else {
586 use async_std::sync::Mutex;
587 use async_std::task::block_on;
588 }
589 }
590
591 #[cfg(not(target_arch = "wasm32"))]
592 pub(crate) static CLIENT_NO_SECRET: std::sync::LazyLock<Client> =
593 std::sync::LazyLock::new(|| {
594 let config = ClientConfig {
595 app_id: String::from("101010101"),
596 cluster: String::from("default"),
597 config_server: String::from("http://81.68.181.139:8080"),
598 label: None,
599 secret: None,
600 cache_dir: Some(String::from("/tmp/apollo")),
601 ip: None,
602 allow_insecure_https: None,
603 #[cfg(not(target_arch = "wasm32"))]
604 cache_ttl: None,
605 };
606 Client::new(config)
607 });
608
609 #[cfg(not(target_arch = "wasm32"))]
610 pub(crate) static CLIENT_WITH_SECRET: std::sync::LazyLock<Client> =
611 std::sync::LazyLock::new(|| {
612 let config = ClientConfig {
613 app_id: String::from("101010102"),
614 cluster: String::from("default"),
615 config_server: String::from("http://81.68.181.139:8080"),
616 label: None,
617 secret: Some(String::from("53bf47631db540ac9700f0020d2192c8")),
618 cache_dir: Some(String::from("/tmp/apollo")),
619 ip: None,
620 allow_insecure_https: None,
621 #[cfg(not(target_arch = "wasm32"))]
622 cache_ttl: None,
623 };
624 Client::new(config)
625 });
626
627 #[cfg(not(target_arch = "wasm32"))]
628 pub(crate) static CLIENT_WITH_GRAYSCALE_IP: std::sync::LazyLock<Client> =
629 std::sync::LazyLock::new(|| {
630 let config = ClientConfig {
631 app_id: String::from("101010101"),
632 cluster: String::from("default"),
633 config_server: String::from("http://81.68.181.139:8080"),
634 label: None,
635 secret: None,
636 cache_dir: Some(String::from("/tmp/apollo")),
637 ip: Some(String::from("1.2.3.4")),
638 allow_insecure_https: None,
639 #[cfg(not(target_arch = "wasm32"))]
640 cache_ttl: None,
641 };
642 Client::new(config)
643 });
644
645 #[cfg(not(target_arch = "wasm32"))]
646 pub(crate) static CLIENT_WITH_GRAYSCALE_LABEL: std::sync::LazyLock<Client> =
647 std::sync::LazyLock::new(|| {
648 let config = ClientConfig {
649 app_id: String::from("101010101"),
650 cluster: String::from("default"),
651 config_server: String::from("http://81.68.181.139:8080"),
652 label: Some(String::from("GrayScale")),
653 secret: None,
654 cache_dir: Some(String::from("/tmp/apollo")),
655 ip: None,
656 allow_insecure_https: None,
657 #[cfg(not(target_arch = "wasm32"))]
658 cache_ttl: None,
659 };
660 Client::new(config)
661 });
662
663 #[cfg(not(target_arch = "wasm32"))]
664 #[tokio::test]
665 async fn test_missing_value() {
666 setup();
667 let namespace::Namespace::Properties(properties) =
668 CLIENT_NO_SECRET.namespace("application").await.unwrap()
669 else {
670 panic!("Expected Properties namespace");
671 };
672
673 assert_eq!(properties.get_property::<String>("missingValue"), None);
674 }
675
676 #[cfg(target_arch = "wasm32")]
677 #[wasm_bindgen_test::wasm_bindgen_test]
678 #[allow(dead_code)]
679 async fn test_missing_value_wasm() {
680 setup();
681 let client = create_client_no_secret();
682 let namespace = client.namespace("application").await;
683 match namespace {
684 Ok(namespace) => match namespace {
685 namespace::Namespace::Properties(properties) => {
686 assert_eq!(properties.get_string("missingValue"), None);
687 }
688 _ => panic!("Expected Properties namespace"),
689 },
690 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
691 }
692 }
693
694 #[cfg(not(target_arch = "wasm32"))]
695 #[tokio::test]
696 async fn test_string_value() {
697 setup();
698 let namespace::Namespace::Properties(properties) =
699 CLIENT_NO_SECRET.namespace("application").await.unwrap()
700 else {
701 panic!("Expected Properties namespace");
702 };
703
704 assert_eq!(
705 properties.get_property::<String>("stringValue"),
706 Some("string value".to_string())
707 );
708 }
709
710 #[cfg(target_arch = "wasm32")]
711 #[wasm_bindgen_test::wasm_bindgen_test]
712 #[allow(dead_code)]
713 async fn test_string_value_wasm() {
714 setup();
715 let client = create_client_no_secret();
716 let namespace = client.namespace("application").await;
717 match namespace {
718 Ok(namespace) => match namespace {
719 namespace::Namespace::Properties(properties) => {
720 assert_eq!(
721 properties.get_string("stringValue"),
722 Some("string value".to_string())
723 );
724 }
725 _ => panic!("Expected Properties namespace"),
726 },
727 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
728 }
729 }
730
731 #[cfg(not(target_arch = "wasm32"))]
732 #[tokio::test]
733 async fn test_string_value_with_secret() {
734 setup();
735 let namespace::Namespace::Properties(properties) =
736 CLIENT_WITH_SECRET.namespace("application").await.unwrap()
737 else {
738 panic!("Expected Properties namespace");
739 };
740 assert_eq!(
741 properties.get_property::<String>("stringValue"),
742 Some("string value".to_string())
743 );
744 }
745
746 #[cfg(target_arch = "wasm32")]
747 #[wasm_bindgen_test::wasm_bindgen_test]
748 #[allow(dead_code)]
749 async fn test_string_value_with_secret_wasm() {
750 setup();
751 let client = create_client_with_secret();
752 let namespace = client.namespace("application").await;
753 match namespace {
754 Ok(namespace) => match namespace {
755 namespace::Namespace::Properties(properties) => {
756 assert_eq!(
757 properties.get_string("stringValue"),
758 Some("string value".to_string())
759 );
760 }
761 _ => panic!("Expected Properties namespace"),
762 },
763 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
764 }
765 }
766
767 #[cfg(not(target_arch = "wasm32"))]
768 #[tokio::test]
769 async fn test_int_value() {
770 setup();
771 let namespace::Namespace::Properties(properties) =
772 CLIENT_NO_SECRET.namespace("application").await.unwrap()
773 else {
774 panic!("Expected Properties namespace");
775 };
776 assert_eq!(properties.get_property::<i32>("intValue"), Some(42));
777 }
778
779 #[cfg(target_arch = "wasm32")]
780 #[wasm_bindgen_test::wasm_bindgen_test]
781 #[allow(dead_code)]
782 async fn test_int_value_wasm() {
783 setup();
784 let client = create_client_no_secret();
785 let namespace = client.namespace("application").await;
786 match namespace {
787 Ok(namespace) => match namespace {
788 namespace::Namespace::Properties(properties) => {
789 assert_eq!(properties.get_int("intValue"), Some(42));
790 }
791 _ => panic!("Expected Properties namespace"),
792 },
793 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
794 }
795 }
796
797 #[cfg(not(target_arch = "wasm32"))]
798 #[tokio::test]
799 async fn test_int_value_with_secret() {
800 setup();
801 let namespace::Namespace::Properties(properties) =
802 CLIENT_WITH_SECRET.namespace("application").await.unwrap()
803 else {
804 panic!("Expected Properties namespace");
805 };
806 assert_eq!(properties.get_property::<i32>("intValue"), Some(42));
807 }
808
809 #[cfg(target_arch = "wasm32")]
810 #[wasm_bindgen_test::wasm_bindgen_test]
811 #[allow(dead_code)]
812 async fn test_int_value_with_secret_wasm() {
813 setup();
814 let client = create_client_with_secret();
815 let namespace = client.namespace("application").await;
816 match namespace {
817 Ok(namespace) => match namespace {
818 namespace::Namespace::Properties(properties) => {
819 assert_eq!(properties.get_int("intValue"), Some(42));
820 }
821 _ => panic!("Expected Properties namespace"),
822 },
823 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
824 }
825 }
826
827 #[cfg(not(target_arch = "wasm32"))]
828 #[tokio::test]
829 async fn test_float_value() {
830 setup();
831 let namespace::Namespace::Properties(properties) =
832 CLIENT_NO_SECRET.namespace("application").await.unwrap()
833 else {
834 panic!("Expected Properties namespace");
835 };
836 assert_eq!(properties.get_property::<f64>("floatValue"), Some(4.20));
837 }
838
839 #[cfg(target_arch = "wasm32")]
840 #[wasm_bindgen_test::wasm_bindgen_test]
841 #[allow(dead_code)]
842 async fn test_float_value_wasm() {
843 setup();
844 let client = create_client_no_secret();
845 let namespace = client.namespace("application").await;
846 match namespace {
847 Ok(namespace) => match namespace {
848 namespace::Namespace::Properties(properties) => {
849 assert_eq!(properties.get_float("floatValue"), Some(4.20));
850 }
851 _ => panic!("Expected Properties namespace"),
852 },
853 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
854 }
855 }
856
857 #[cfg(not(target_arch = "wasm32"))]
858 #[tokio::test]
859 async fn test_float_value_with_secret() {
860 setup();
861 let namespace::Namespace::Properties(properties) =
862 CLIENT_WITH_SECRET.namespace("application").await.unwrap()
863 else {
864 panic!("Expected Properties namespace");
865 };
866 assert_eq!(properties.get_property::<f64>("floatValue"), Some(4.20));
867 }
868
869 #[cfg(target_arch = "wasm32")]
870 #[wasm_bindgen_test::wasm_bindgen_test]
871 #[allow(dead_code)]
872 async fn test_float_value_with_secret_wasm() {
873 setup();
874 let client = create_client_with_secret();
875 let namespace = client.namespace("application").await;
876 match namespace {
877 Ok(namespace) => match namespace {
878 namespace::Namespace::Properties(properties) => {
879 assert_eq!(properties.get_float("floatValue"), Some(4.20));
880 }
881 _ => panic!("Expected Properties namespace"),
882 },
883 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
884 }
885 }
886
887 #[cfg(not(target_arch = "wasm32"))]
888 #[tokio::test]
889 async fn test_bool_value() {
890 setup();
891 let namespace::Namespace::Properties(properties) =
892 CLIENT_NO_SECRET.namespace("application").await.unwrap()
893 else {
894 panic!("Expected Properties namespace");
895 };
896 assert_eq!(properties.get_property::<bool>("boolValue"), Some(false));
897 }
898
899 #[cfg(target_arch = "wasm32")]
900 #[wasm_bindgen_test::wasm_bindgen_test]
901 #[allow(dead_code)]
902 async fn test_bool_value_wasm() {
903 setup();
904 let client = create_client_no_secret();
905 let namespace = client.namespace("application").await;
906 match namespace {
907 Ok(namespace) => match namespace {
908 namespace::Namespace::Properties(properties) => {
909 assert_eq!(properties.get_bool("boolValue"), Some(false));
910 }
911 _ => panic!("Expected Properties namespace"),
912 },
913 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
914 }
915 }
916
917 #[cfg(not(target_arch = "wasm32"))]
918 #[tokio::test]
919 async fn test_bool_value_with_secret() {
920 setup();
921 let namespace::Namespace::Properties(properties) =
922 CLIENT_WITH_SECRET.namespace("application").await.unwrap()
923 else {
924 panic!("Expected Properties namespace");
925 };
926 assert_eq!(properties.get_property::<bool>("boolValue"), Some(false));
927 }
928
929 #[cfg(target_arch = "wasm32")]
930 #[wasm_bindgen_test::wasm_bindgen_test]
931 #[allow(dead_code)]
932 async fn test_bool_value_with_secret_wasm() {
933 setup();
934 let client = create_client_with_secret();
935 let namespace = client.namespace("application").await;
936 match namespace {
937 Ok(namespace) => match namespace {
938 namespace::Namespace::Properties(properties) => {
939 assert_eq!(properties.get_bool("boolValue"), Some(false));
940 }
941 _ => panic!("Expected Properties namespace"),
942 },
943 Err(e) => panic!("Expected Properties namespace, got error: {e:?}"),
944 }
945 }
946
947 #[cfg(not(target_arch = "wasm32"))]
948 #[tokio::test]
949 async fn test_bool_value_with_grayscale_ip() {
950 setup();
951 let namespace::Namespace::Properties(properties) = CLIENT_WITH_GRAYSCALE_IP
952 .namespace("application")
953 .await
954 .unwrap()
955 else {
956 panic!("Expected Properties namespace");
957 };
958 assert_eq!(
959 properties.get_property::<bool>("grayScaleValue"),
960 Some(true)
961 );
962 let namespace::Namespace::Properties(properties) =
963 CLIENT_NO_SECRET.namespace("application").await.unwrap()
964 else {
965 panic!("Expected Properties namespace");
966 };
967 assert_eq!(
968 properties.get_property::<bool>("grayScaleValue"),
969 Some(false)
970 );
971 }
972
973 #[cfg(target_arch = "wasm32")]
974 #[wasm_bindgen_test::wasm_bindgen_test]
975 #[allow(dead_code)]
976 async fn test_bool_value_with_grayscale_ip_wasm() {
977 setup();
978 let client1 = create_client_with_grayscale_ip();
979 let namespace = client1.namespace("application").await;
980 match namespace {
981 Ok(namespace) => match namespace {
982 namespace::Namespace::Properties(properties) => {
983 assert_eq!(properties.get_bool("grayScaleValue"), Some(true));
984 }
985 _ => panic!("Expected Properties namespace"),
986 },
987 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
988 }
989
990 let client2 = create_client_no_secret();
991 let namespace = client2.namespace("application").await;
992 match namespace {
993 Ok(namespace) => match namespace {
994 namespace::Namespace::Properties(properties) => {
995 assert_eq!(properties.get_bool("grayScaleValue"), Some(false));
996 }
997 _ => panic!("Expected Properties namespace"),
998 },
999 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
1000 }
1001 }
1002
1003 #[cfg(not(target_arch = "wasm32"))]
1004 #[tokio::test]
1005 async fn test_bool_value_with_grayscale_label() {
1006 setup();
1007 let namespace::Namespace::Properties(properties) = CLIENT_WITH_GRAYSCALE_LABEL
1008 .namespace("application")
1009 .await
1010 .unwrap()
1011 else {
1012 panic!("Expected Properties namespace");
1013 };
1014 assert_eq!(
1015 properties.get_property::<bool>("grayScaleValue"),
1016 Some(true)
1017 );
1018 let namespace::Namespace::Properties(properties) =
1019 CLIENT_NO_SECRET.namespace("application").await.unwrap()
1020 else {
1021 panic!("Expected Properties namespace");
1022 };
1023 assert_eq!(
1024 properties.get_property::<bool>("grayScaleValue"),
1025 Some(false)
1026 );
1027 }
1028
1029 #[cfg(target_arch = "wasm32")]
1030 #[wasm_bindgen_test::wasm_bindgen_test]
1031 #[allow(dead_code)]
1032 async fn test_bool_value_with_grayscale_label_wasm() {
1033 setup();
1034 let client1 = create_client_with_grayscale_label();
1035 let namespace = client1.namespace("application").await;
1036 match namespace {
1037 Ok(namespace) => match namespace {
1038 namespace::Namespace::Properties(properties) => {
1039 assert_eq!(properties.get_bool("grayScaleValue"), Some(true));
1040 }
1041 _ => panic!("Expected Properties namespace"),
1042 },
1043 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
1044 }
1045
1046 let client2 = create_client_no_secret();
1047 let namespace = client2.namespace("application").await;
1048 match namespace {
1049 Ok(namespace) => match namespace {
1050 namespace::Namespace::Properties(properties) => {
1051 assert_eq!(properties.get_bool("grayScaleValue"), Some(false));
1052 }
1053 _ => panic!("Expected Properties namespace"),
1054 },
1055 Err(e) => panic!("Expected Properties namespace, got error: {:?}", e),
1056 }
1057 }
1058
1059 #[cfg(target_arch = "wasm32")]
1060 fn create_client_no_secret() -> Client {
1061 let config = ClientConfig {
1062 app_id: String::from("101010101"),
1063 cluster: String::from("default"),
1064 config_server: String::from("http://81.68.181.139:8080"),
1065 label: None,
1066 secret: None,
1067 cache_dir: None,
1068 ip: None,
1069 allow_insecure_https: None,
1070 };
1071 Client::new(config)
1072 }
1073
1074 #[cfg(target_arch = "wasm32")]
1075 fn create_client_with_secret() -> Client {
1076 let config = ClientConfig {
1077 app_id: String::from("101010102"),
1078 cluster: String::from("default"),
1079 config_server: String::from("http://81.68.181.139:8080"),
1080 label: None,
1081 secret: Some(String::from("53bf47631db540ac9700f0020d2192c8")),
1082 cache_dir: None,
1083 ip: None,
1084 allow_insecure_https: None,
1085 };
1086 Client::new(config)
1087 }
1088
1089 #[cfg(target_arch = "wasm32")]
1090 fn create_client_with_grayscale_ip() -> Client {
1091 let config = ClientConfig {
1092 app_id: String::from("101010101"),
1093 cluster: String::from("default"),
1094 config_server: String::from("http://81.68.181.139:8080"),
1095 label: None,
1096 secret: None,
1097 cache_dir: None,
1098 ip: Some(String::from("1.2.3.4")),
1099 allow_insecure_https: None,
1100 };
1101 Client::new(config)
1102 }
1103
1104 #[cfg(target_arch = "wasm32")]
1105 fn create_client_with_grayscale_label() -> Client {
1106 let config = ClientConfig {
1107 app_id: String::from("101010101"),
1108 cluster: String::from("default"),
1109 config_server: String::from("http://81.68.181.139:8080"),
1110 label: Some(String::from("GrayScale")),
1111 secret: None,
1112 cache_dir: None,
1113 ip: None,
1114 allow_insecure_https: None,
1115 };
1116 Client::new(config)
1117 }
1118
1119 #[cfg(not(target_arch = "wasm32"))]
1120 #[tokio::test] async fn test_add_listener_and_notify_on_refresh() {
1122 setup();
1123
1124 let listener_called_flag = Arc::new(Mutex::new(false));
1126 let received_config_data = Arc::new(Mutex::new(None::<Namespace>));
1127
1128 let config = ClientConfig {
1131 config_server: "http://81.68.181.139:8080".to_string(), app_id: "101010101".to_string(), cluster: "default".to_string(),
1134 cache_dir: Some(String::from("/tmp/apollo")), secret: None,
1136 label: None,
1137 ip: None,
1138 allow_insecure_https: None,
1139 #[cfg(not(target_arch = "wasm32"))]
1140 cache_ttl: None,
1141 };
1143
1144 let client = Client::new(config);
1145
1146 let flag_clone = listener_called_flag.clone();
1147 let data_clone = received_config_data.clone();
1148
1149 let listener: EventListener = Arc::new(move |result| {
1150 let mut called_guard = block_on(flag_clone.lock());
1151 *called_guard = true;
1152 if let Ok(config_value) = result {
1153 match config_value {
1154 Namespace::Properties(_) => {
1155 let mut data_guard = block_on(data_clone.lock());
1156 *data_guard = Some(config_value.clone());
1157 }
1158 _ => {
1159 panic!("Expected Properties namespace, got {config_value:?}");
1160 }
1161 }
1162 }
1163 });
1166
1167 client.add_listener("application", listener).await;
1168
1169 let cache = client.cache("application").await;
1170
1171 match cache.refresh().await {
1174 Ok(()) => log::debug!("Refresh successful for test_add_listener_and_notify_on_refresh"),
1175 Err(e) => panic!("Cache refresh failed during test: {e:?}"),
1176 }
1177
1178 let called = *listener_called_flag.lock().await;
1180 assert!(called, "Listener was not called.");
1181
1182 let config_data_guard = received_config_data.lock().await;
1184 assert!(
1185 config_data_guard.is_some(),
1186 "Listener did not receive config data."
1187 );
1188
1189 if let Some(value) = config_data_guard.as_ref() {
1193 match value {
1194 Namespace::Properties(properties) => {
1195 assert_eq!(
1196 properties.get_string("stringValue"),
1197 Some(String::from("string value")),
1198 "Received config data does not match expected content for stringValue."
1199 );
1200 }
1201 _ => {
1202 panic!("Expected Properties namespace, got {value:?}");
1203 }
1204 }
1205 }
1206 }
1207
1208 #[cfg(target_arch = "wasm32")]
1209 #[wasm_bindgen_test::wasm_bindgen_test]
1210 async fn test_add_listener_wasm_and_notify() {
1211 setup(); let listener_called_flag = Arc::new(Mutex::new(false));
1215 let received_config_data = Arc::new(Mutex::new(None::<Namespace>));
1216
1217 let flag_clone = listener_called_flag.clone();
1218 let data_clone = received_config_data.clone();
1219
1220 let js_listener_func_body = format!(
1222 r#"
1223 (data, error) => {{
1224 // We can't use window in Node.js, so we'll use a different approach
1225 // The Rust closure will handle the verification
1226 console.log('JS Listener called with error:', error);
1227 console.log('JS Listener called with data:', data);
1228 }}
1229 "#
1230 );
1231
1232 let js_listener = js_sys::Function::new_with_args("data, error", &js_listener_func_body);
1233
1234 let client = create_client_no_secret();
1235
1236 let rust_listener: EventListener = Arc::new(move |result| {
1238 let mut called_guard = flag_clone.lock().unwrap();
1239 *called_guard = true;
1240 if let Ok(config_value) = result {
1241 let mut data_guard = data_clone.lock().unwrap();
1242 *data_guard = Some(config_value);
1243 }
1244 });
1245
1246 client.add_listener("application", rust_listener).await;
1247
1248 client.add_listener_wasm("application", js_listener).await;
1250
1251 let cache = client.cache("application").await;
1252
1253 match cache.refresh().await {
1255 Ok(_) => web_sys::console::log_1(&"WASM Test: Refresh successful".into()), Err(e) => panic!("WASM Test: Cache refresh failed: {:?}", e),
1257 }
1258
1259 let called = *listener_called_flag.lock().unwrap();
1261 assert!(called, "Listener was not called.");
1262
1263 let config_data_guard = received_config_data.lock().unwrap();
1265 assert!(
1266 config_data_guard.is_some(),
1267 "Listener did not receive config data."
1268 );
1269
1270 if let Some(value) = config_data_guard.as_ref() {
1272 match value {
1273 namespace::Namespace::Properties(properties) => {
1274 assert_eq!(
1275 properties.get_string("stringValue"),
1276 Some("string value".to_string())
1277 );
1278 }
1279 _ => panic!("Expected Properties namespace"),
1280 }
1281 }
1282 }
1283
1284 #[cfg(not(target_arch = "wasm32"))]
1285 #[tokio::test]
1286 async fn test_concurrent_namespace_hang_repro() {
1287 use async_std::task;
1288 setup();
1289
1290 let temp_dir = TempDir::new("apollo_hang_test");
1291
1292 let config = ClientConfig {
1293 app_id: String::from("101010101"),
1294 cluster: String::from("default"),
1295 config_server: String::from("http://81.68.181.139:8080"),
1296 secret: None,
1297 cache_dir: Some(temp_dir.path().to_str().unwrap().to_string()),
1298 label: None,
1299 ip: None,
1300 allow_insecure_https: None,
1301 cache_ttl: None,
1302 };
1303
1304 let client = Arc::new(Client::new(config));
1305 let client_in_listener = client.clone();
1306
1307 let listener_triggered = Arc::new(Mutex::new(false));
1308 let listener_triggered_in_listener = listener_triggered.clone();
1309
1310 let listener: EventListener = Arc::new(move |_| {
1311 let client_in_listener = client_in_listener.clone();
1312 let listener_triggered_in_listener = listener_triggered_in_listener.clone();
1313 task::spawn(async move {
1314 let mut triggered = listener_triggered_in_listener.lock().await;
1315 if *triggered {
1316 return;
1318 }
1319 *triggered = true;
1320 drop(triggered);
1321
1322 let _ = client_in_listener.namespace("application").await;
1324 });
1325 });
1326
1327 client.add_listener("application", listener).await;
1328
1329 let test_body = async {
1330 let _ = client.namespace("application").await;
1331 };
1332
1333 let res = tokio::time::timeout(std::time::Duration::from_secs(10), test_body).await;
1335 assert!(res.is_ok(), "Test timed out, which indicates a deadlock.");
1336 }
1337}