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