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