From 069246e247c64a61422c4b03287333913d0d7923 Mon Sep 17 00:00:00 2001 From: adnanjpg Date: Mon, 22 Jan 2024 16:10:52 +0300 Subject: [PATCH] feat: build new api --- a.sh | 1 + examples/simple_sender.rs | 37 ++-- src/android/android_config.rs | 73 ++++++++ src/android/android_fcm_options.rs | 21 +++ src/android/android_message_priority.rs | 10 + src/android/android_notification.rs | 234 ++++++++++++++++++++++++ src/android/color.rs | 43 +++++ src/android/light_settings.rs | 43 +++++ src/android/mod.rs | 229 +---------------------- src/android/notification_priority.rs | 14 ++ src/android/visibility.rs | 12 ++ src/apns/apns_config.rs | 37 ++++ src/apns/apns_fcm_options.rs | 29 +++ src/apns/mod.rs | 30 +-- src/client/mod.rs | 24 ++- src/lib.rs | 1 + src/message/fcm_options.rs | 21 +++ src/message/mod.rs | 128 ++++--------- src/message/target.rs | 9 + src/message/tests.rs | 136 ++++++++++---- src/notification/mod.rs | 45 ++--- src/notification/tests.rs | 50 +---- src/web/mod.rs | 35 +--- src/web/webpush_config.rs | 44 +++++ src/web/webpush_fcm_options.rs | 30 +++ 25 files changed, 838 insertions(+), 498 deletions(-) create mode 100644 a.sh create mode 100644 src/android/android_config.rs create mode 100644 src/android/android_fcm_options.rs create mode 100644 src/android/android_message_priority.rs create mode 100644 src/android/android_notification.rs create mode 100644 src/android/color.rs create mode 100644 src/android/light_settings.rs create mode 100644 src/android/notification_priority.rs create mode 100644 src/android/visibility.rs create mode 100644 src/apns/apns_config.rs create mode 100644 src/apns/apns_fcm_options.rs create mode 100644 src/message/fcm_options.rs create mode 100644 src/message/target.rs create mode 100644 src/web/webpush_config.rs create mode 100644 src/web/webpush_fcm_options.rs diff --git a/a.sh b/a.sh new file mode 100644 index 000000000..5d89ab970 --- /dev/null +++ b/a.sh @@ -0,0 +1 @@ +cargo run --example simple_sender -- -t fP0EXs_HQ4Gj1sE5xBP6LP:APA91bGxDOk4GzvIvvGFPa6kDWOOHm2zYA3tUN1jYwErSBq_s27NioYjYWfUpgUW3_LIsreSw1pbtTjEpCK_cKR6AHxo1PGZ1Yd3S8lyX148I_LgKaTNYorG2QtkcwfB9Nm5yKGJwLaG \ No newline at end of file diff --git a/examples/simple_sender.rs b/examples/simple_sender.rs index 6a634b801..fb7cedc28 100644 --- a/examples/simple_sender.rs +++ b/examples/simple_sender.rs @@ -1,11 +1,8 @@ -use argparse::{ArgumentParser, Store}; -use fcm::{Client, MessageBuilder, Target}; -use serde::Serialize; +// cargo run --example simple_sender -- -t -#[derive(Serialize)] -struct CustomData { - message: &'static str, -} +use argparse::{ArgumentParser, Store}; +use fcm::{fcm_options::FcmOptions, target::Target, Client, Message, Notification}; +use serde_json::json; #[tokio::main] async fn main() -> Result<(), Box> { @@ -22,12 +19,28 @@ async fn main() -> Result<(), Box> { } let client = Client::new(); - let data = CustomData { message: "howdy" }; - - let mut builder = MessageBuilder::new(Target::Token(device_token)); - builder.data(&data)?; - let response = client.send(builder.finalize()).await?; + let data = json!({ + "key": "value", + }); + + let builder = Message { + data: Some(data), + notification: Some(Notification { + title: Some("Hello".to_string()), + body: Some(format!("it's {}", chrono::Utc::now())), + image: None, + }), + target: Target::Token(device_token), + android: None, + webpush: None, + apns: None, + fcm_options: Some(FcmOptions { + analytics_label: "analytics_label".to_string(), + }), + }; + + let response = client.send(builder).await?; println!("Sent: {:?}", response); Ok(()) diff --git a/src/android/android_config.rs b/src/android/android_config.rs new file mode 100644 index 000000000..800a840e5 --- /dev/null +++ b/src/android/android_config.rs @@ -0,0 +1,73 @@ +use serde::Serialize; +use serde_json::Value; + +use super::{ + android_fcm_options::{AndroidFcmOptions, AndroidFcmOptionsInternal}, + android_message_priority::AndroidMessagePriority, + android_notification::{AndroidNotification, AndroidNotificationInternal}, +}; + +#[derive(Serialize, Debug)] +//https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#androidconfig +pub struct AndroidConfigInternal { + // An identifier of a group of messages that can be collapsed, so that only the last message gets + // sent when delivery can be resumed. + #[serde(skip_serializing_if = "Option::is_none")] + collapse_key: Option, + + // Message priority. + #[serde(skip_serializing_if = "Option::is_none")] + priority: Option, + + // How long (in seconds) the message should be kept in FCM storage if the device is offline. + // Duration format: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?authuser=0#google.protobuf.Duration + #[serde(skip_serializing_if = "Option::is_none")] + ttl: Option, + + // Package name of the application where the registration token must match in order to receive the message. + #[serde(skip_serializing_if = "Option::is_none")] + restricted_package_name: Option, + + // Arbitrary key/value payload. + #[serde(skip_serializing_if = "Option::is_none")] + data: Option, + + // Notification to send to android devices. + #[serde(skip_serializing_if = "Option::is_none")] + notification: Option, + + // Options for features provided by the FCM SDK for Android. + #[serde(skip_serializing_if = "Option::is_none")] + fcm_options: Option, + + // If set to true, messages will be allowed to be delivered to the app while the device is in direct boot mode. + #[serde(skip_serializing_if = "Option::is_none")] + direct_boot_ok: Option, +} + +#[derive(Debug)] +pub struct AndroidConfig { + pub collapse_key: Option, + pub priority: Option, + pub ttl: Option, + pub restricted_package_name: Option, + pub data: Option, + pub notification: Option, + pub fcm_options: Option, + pub direct_boot_ok: Option, +} + +impl AndroidConfig { + pub fn finalize(self) -> AndroidConfigInternal { + AndroidConfigInternal { + collapse_key: self.collapse_key, + priority: self.priority, + ttl: self.ttl, + restricted_package_name: self.restricted_package_name, + data: self.data, + notification: self.notification.map(|n| n.finalize()), + fcm_options: self.fcm_options.map(|f| f.finalize()), + direct_boot_ok: self.direct_boot_ok, + } + } +} diff --git a/src/android/android_fcm_options.rs b/src/android/android_fcm_options.rs new file mode 100644 index 000000000..81a32de7a --- /dev/null +++ b/src/android/android_fcm_options.rs @@ -0,0 +1,21 @@ +use serde::Serialize; + +#[derive(Serialize, Debug)] +//https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#androidconfig +pub struct AndroidFcmOptionsInternal { + // Label associated with the message's analytics data. + analytics_label: String, +} + +#[derive(Debug)] +pub struct AndroidFcmOptions { + pub analytics_label: String, +} + +impl AndroidFcmOptions { + pub fn finalize(self) -> AndroidFcmOptionsInternal { + AndroidFcmOptionsInternal { + analytics_label: self.analytics_label, + } + } +} diff --git a/src/android/android_message_priority.rs b/src/android/android_message_priority.rs new file mode 100644 index 000000000..046466294 --- /dev/null +++ b/src/android/android_message_priority.rs @@ -0,0 +1,10 @@ +use serde::Serialize; + +#[allow(dead_code)] +#[derive(Serialize, Debug)] +#[serde(rename_all = "UPPERCASE")] +// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#androidmessagepriority +pub enum AndroidMessagePriority { + Normal, + High, +} diff --git a/src/android/android_notification.rs b/src/android/android_notification.rs new file mode 100644 index 000000000..0656c13fe --- /dev/null +++ b/src/android/android_notification.rs @@ -0,0 +1,234 @@ +use serde::Serialize; + +use super::{ + light_settings::{LightSettings, LightSettingsInternal}, + notification_priority::NotificationPriority, + visibility::Visibility, +}; + +#[derive(Serialize, Debug)] +// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#androidnotification +pub struct AndroidNotificationInternal { + // The notification's title. + #[serde(skip_serializing_if = "Option::is_none")] + title: Option, + + // The notification's body text. + #[serde(skip_serializing_if = "Option::is_none")] + body: Option, + + // The notification's icon. + #[serde(skip_serializing_if = "Option::is_none")] + icon: Option, + + // The notification's icon color, expressed in #rrggbb format. + #[serde(skip_serializing_if = "Option::is_none")] + color: Option, + + // The sound to play when the device receives the notification. + #[serde(skip_serializing_if = "Option::is_none")] + sound: Option, + + // Identifier used to replace existing notifications in the notification drawer. + #[serde(skip_serializing_if = "Option::is_none")] + tag: Option, + + // The action associated with a user click on the notification. + #[serde(skip_serializing_if = "Option::is_none")] + click_action: Option, + + // The key to the body string in the app's string resources to use to localize the body text to the user's + // current localization. + #[serde(skip_serializing_if = "Option::is_none")] + body_loc_key: Option, + + // Variable string values to be used in place of the format specifiers in body_loc_key to use to localize the + // body text to the user's current localization. + #[serde(skip_serializing_if = "Option::is_none")] + body_loc_args: Option>, + + // The key to the title string in the app's string resources to use to localize the title text to the user's + // current localization. + #[serde(skip_serializing_if = "Option::is_none")] + title_loc_key: Option, + + // Variable string values to be used in place of the format specifiers in title_loc_key to use to localize the + // title text to the user's current localization. + #[serde(skip_serializing_if = "Option::is_none")] + title_loc_args: Option>, + + // The notification's channel id (new in Android O). + #[serde(skip_serializing_if = "Option::is_none")] + channel_id: Option, + + // Sets the "ticker" text, which is sent to accessibility services. + #[serde(skip_serializing_if = "Option::is_none")] + ticker: Option, + + // When set to false or unset, the notification is automatically dismissed when the user clicks it in the panel. + #[serde(skip_serializing_if = "Option::is_none")] + sticky: Option, + + // Set the time that the event in the notification occurred. Notifications in the panel are sorted by this time. + // Timestamp format: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?authuser=0#google.protobuf.Timestamp + #[serde(skip_serializing_if = "Option::is_none")] + event_time: Option, + + // Set whether or not this notification is relevant only to the current device. + #[serde(skip_serializing_if = "Option::is_none")] + local_only: Option, + + // Set the relative priority for this notification. + #[serde(skip_serializing_if = "Option::is_none")] + notification_priority: Option, + + // If set to true, use the Android framework's default sound for the notification. + #[serde(skip_serializing_if = "Option::is_none")] + default_sound: Option, + + // If set to true, use the Android framework's default vibrate pattern for the notification. + #[serde(skip_serializing_if = "Option::is_none")] + default_vibrate_timings: Option, + + // If set to true, use the Android framework's default LED light settings for the notification. + #[serde(skip_serializing_if = "Option::is_none")] + default_light_settings: Option, + + // Set the vibration pattern to use + // Duration format: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?authuser=0#google.protobuf.Duration + #[serde(skip_serializing_if = "Option::is_none")] + vibrate_timings: Option>, + + // Set the Notification.visibility of the notification. + #[serde(skip_serializing_if = "Option::is_none")] + visibility: Option, + + // Sets the number of items this notification represents. + #[serde(skip_serializing_if = "Option::is_none")] + notification_count: Option, + + // Settings to control the notification's LED blinking rate and color if LED is available on the device. + #[serde(skip_serializing_if = "Option::is_none")] + light_settings: Option, + + // Contains the URL of an image that is going to be displayed in a notification. + #[serde(skip_serializing_if = "Option::is_none")] + image: Option, +} + +#[derive(Debug)] +// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#androidnotification +pub struct AndroidNotification { + // The notification's title. + pub title: Option, + + // The notification's body text. + pub body: Option, + + // The notification's icon. + pub icon: Option, + + // The notification's icon color, expressed in #rrggbb format. + pub color: Option, + + // The sound to play when the device receives the notification. + pub sound: Option, + + // Identifier used to replace existing notifications in the notification drawer. + pub tag: Option, + + // The action associated with a user click on the notification. + pub click_action: Option, + + // The key to the body string in the app's string resources to use to localize the body text to the user's + // current localization. + pub body_loc_key: Option, + + // Variable string values to be used in place of the format specifiers in body_loc_key to use to localize the + // body text to the user's current localization. + pub body_loc_args: Option>, + + // The key to the title string in the app's string resources to use to localize the title text to the user's + // current localization. + pub title_loc_key: Option, + + // Variable string values to be used in place of the format specifiers in title_loc_key to use to localize the + // title text to the user's current localization. + pub title_loc_args: Option>, + + // The notification's channel id (new in Android O). + pub channel_id: Option, + + // Sets the "ticker" text, which is sent to accessibility services. + pub ticker: Option, + + // When set to false or unset, the notification is automatically dismissed when the user clicks it in the panel. + pub sticky: Option, + + // Set the time that the event in the notification occurred. Notifications in the panel are sorted by this time. + // Timestamp format: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?authuser=0#google.protobuf.Timestamp + pub event_time: Option, + + // Set whether or not this notification is relevant only to the current device. + pub local_only: Option, + + // Set the relative priority for this notification. + pub notification_priority: Option, + + // If set to true, use the Android framework's default sound for the notification. + pub default_sound: Option, + + // If set to true, use the Android framework's default vibrate pattern for the notification. + pub default_vibrate_timings: Option, + + // If set to true, use the Android framework's default LED light settings for the notification. + pub default_light_settings: Option, + + // Set the vibration pattern to use + // Duration format: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?authuser=0#google.protobuf.Duration + pub vibrate_timings: Option>, + + // Set the Notification.visibility of the notification. + pub visibility: Option, + + // Sets the number of items this notification represents. + pub notification_count: Option, + + // Settings to control the notification's LED blinking rate and color if LED is available on the device. + pub light_settings: Option, + + // Contains the URL of an image that is going to be displayed in a notification. + pub image: Option, +} + +impl AndroidNotification { + pub fn finalize(self) -> AndroidNotificationInternal { + AndroidNotificationInternal { + title: self.title, + body: self.body, + icon: self.icon, + color: self.color, + sound: self.sound, + tag: self.tag, + click_action: self.click_action, + body_loc_key: self.body_loc_key, + body_loc_args: self.body_loc_args, + title_loc_key: self.title_loc_key, + title_loc_args: self.title_loc_args, + channel_id: self.channel_id, + ticker: self.ticker, + sticky: self.sticky, + event_time: self.event_time, + local_only: self.local_only, + notification_priority: self.notification_priority, + default_sound: self.default_sound, + default_vibrate_timings: self.default_vibrate_timings, + default_light_settings: self.default_light_settings, + vibrate_timings: self.vibrate_timings, + visibility: self.visibility, + notification_count: self.notification_count, + light_settings: self.light_settings.map(|x| x.finalize()), + image: self.image, + } + } +} diff --git a/src/android/color.rs b/src/android/color.rs new file mode 100644 index 000000000..9520607f3 --- /dev/null +++ b/src/android/color.rs @@ -0,0 +1,43 @@ +use serde::Serialize; + +#[derive(Serialize, Debug)] +// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#Color +pub struct ColorInternal { + // The amount of red in the color as a value in the interval [0, 1]. + red: f32, + + // The amount of green in the color as a value in the interval [0, 1]. + green: f32, + + // The amount of blue in the color as a value in the interval [0, 1]. + blue: f32, + + // The fraction of this color that should be applied to the pixel. + alpha: f32, +} + +#[derive(Debug)] +pub struct Color { + // The amount of red in the color as a value in the interval [0, 1]. + pub red: f32, + + // The amount of green in the color as a value in the interval [0, 1]. + pub green: f32, + + // The amount of blue in the color as a value in the interval [0, 1]. + pub blue: f32, + + // The fraction of this color that should be applied to the pixel. + pub alpha: f32, +} + +impl Color { + pub fn finalize(self) -> ColorInternal { + ColorInternal { + red: self.red, + green: self.green, + blue: self.blue, + alpha: self.alpha, + } + } +} diff --git a/src/android/light_settings.rs b/src/android/light_settings.rs new file mode 100644 index 000000000..43cd7b01b --- /dev/null +++ b/src/android/light_settings.rs @@ -0,0 +1,43 @@ +use serde::Serialize; + +use super::color::{Color, ColorInternal}; + +#[derive(Serialize, Debug)] +// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#LightSettings +pub struct LightSettingsInternal { + // Set color of the LED with google.type.Color. + color: ColorInternal, + + // Along with light_off_duration, define the blink rate of LED flashes + // Duration format: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?authuser=0#google.protobuf.Duration + light_on_duration: String, + + // Along with light_on_duration, define the blink rate of LED flashes. + // Duration format: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?authuser=0#google.protobuf.Duration + light_off_duration: String, +} + +#[derive(Debug)] +// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#LightSettings +pub struct LightSettings { + // Set color of the LED with google.type.Color. + pub color: Color, + + // Along with light_off_duration, define the blink rate of LED flashes + // Duration format: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?authuser=0#google.protobuf.Duration + pub light_on_duration: String, + + // Along with light_on_duration, define the blink rate of LED flashes. + // Duration format: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?authuser=0#google.protobuf.Duration + pub light_off_duration: String, +} + +impl LightSettings { + pub fn finalize(self) -> LightSettingsInternal { + LightSettingsInternal { + color: self.color.finalize(), + light_on_duration: self.light_on_duration, + light_off_duration: self.light_off_duration, + } + } +} diff --git a/src/android/mod.rs b/src/android/mod.rs index a0f1778a0..b4ba2e81b 100644 --- a/src/android/mod.rs +++ b/src/android/mod.rs @@ -1,221 +1,8 @@ -use serde::Serialize; -use serde_json::Value; - -#[derive(Serialize, Debug)] -//https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#androidconfig -pub struct AndroidConfig { - // An identifier of a group of messages that can be collapsed, so that only the last message gets - // sent when delivery can be resumed. - #[serde(skip_serializing_if = "Option::is_none")] - collapse_key: Option, - - // Message priority. - #[serde(skip_serializing_if = "Option::is_none")] - priority: Option, - - // How long (in seconds) the message should be kept in FCM storage if the device is offline. - // Duration format: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?authuser=0#google.protobuf.Duration - #[serde(skip_serializing_if = "Option::is_none")] - ttl: Option, - - // Package name of the application where the registration token must match in order to receive the message. - #[serde(skip_serializing_if = "Option::is_none")] - restricted_package_name: Option, - - // Arbitrary key/value payload. - #[serde(skip_serializing_if = "Option::is_none")] - data: Option, - - // Notification to send to android devices. - #[serde(skip_serializing_if = "Option::is_none")] - notification: Option, - - // Options for features provided by the FCM SDK for Android. - #[serde(skip_serializing_if = "Option::is_none")] - fcm_options: Option, - - // If set to true, messages will be allowed to be delivered to the app while the device is in direct boot mode. - #[serde(skip_serializing_if = "Option::is_none")] - direct_boot_ok: Option, -} - -#[derive(Serialize, Debug)] -// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#Color -pub struct Color { - // The amount of red in the color as a value in the interval [0, 1]. - red: f32, - - // The amount of green in the color as a value in the interval [0, 1]. - green: f32, - - // The amount of blue in the color as a value in the interval [0, 1]. - blue: f32, - - // The fraction of this color that should be applied to the pixel. - alpha: f32, -} - -#[derive(Serialize, Debug)] -// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#LightSettings -pub struct LightSettings { - // Set color of the LED with google.type.Color. - color: Color, - - // Along with light_off_duration, define the blink rate of LED flashes - // Duration format: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?authuser=0#google.protobuf.Duration - light_on_duration: String, - - // Along with light_on_duration, define the blink rate of LED flashes. - // Duration format: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?authuser=0#google.protobuf.Duration - light_off_duration: String, -} - -#[derive(Serialize, Debug)] -// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#androidnotification -pub struct AndroidNotification { - // The notification's title. - #[serde(skip_serializing_if = "Option::is_none")] - title: Option, - - // The notification's body text. - #[serde(skip_serializing_if = "Option::is_none")] - body: Option, - - // The notification's icon. - #[serde(skip_serializing_if = "Option::is_none")] - icon: Option, - - // The notification's icon color, expressed in #rrggbb format. - #[serde(skip_serializing_if = "Option::is_none")] - color: Option, - - // The sound to play when the device receives the notification. - #[serde(skip_serializing_if = "Option::is_none")] - sound: Option, - - // Identifier used to replace existing notifications in the notification drawer. - #[serde(skip_serializing_if = "Option::is_none")] - tag: Option, - - // The action associated with a user click on the notification. - #[serde(skip_serializing_if = "Option::is_none")] - click_action: Option, - - // The key to the body string in the app's string resources to use to localize the body text to the user's - // current localization. - #[serde(skip_serializing_if = "Option::is_none")] - body_loc_key: Option, - - // Variable string values to be used in place of the format specifiers in body_loc_key to use to localize the - // body text to the user's current localization. - #[serde(skip_serializing_if = "Option::is_none")] - body_loc_args: Option>, - - // The key to the title string in the app's string resources to use to localize the title text to the user's - // current localization. - #[serde(skip_serializing_if = "Option::is_none")] - title_loc_key: Option, - - // Variable string values to be used in place of the format specifiers in title_loc_key to use to localize the - // title text to the user's current localization. - #[serde(skip_serializing_if = "Option::is_none")] - title_loc_args: Option>, - - // The notification's channel id (new in Android O). - #[serde(skip_serializing_if = "Option::is_none")] - channel_id: Option, - - // Sets the "ticker" text, which is sent to accessibility services. - #[serde(skip_serializing_if = "Option::is_none")] - ticker: Option, - - // When set to false or unset, the notification is automatically dismissed when the user clicks it in the panel. - #[serde(skip_serializing_if = "Option::is_none")] - sticky: Option, - - // Set the time that the event in the notification occurred. Notifications in the panel are sorted by this time. - // Timestamp format: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?authuser=0#google.protobuf.Timestamp - #[serde(skip_serializing_if = "Option::is_none")] - event_time: Option, - - // Set whether or not this notification is relevant only to the current device. - #[serde(skip_serializing_if = "Option::is_none")] - local_only: Option, - - // Set the relative priority for this notification. - #[serde(skip_serializing_if = "Option::is_none")] - notification_priority: Option, - - // If set to true, use the Android framework's default sound for the notification. - #[serde(skip_serializing_if = "Option::is_none")] - default_sound: Option, - - // If set to true, use the Android framework's default vibrate pattern for the notification. - #[serde(skip_serializing_if = "Option::is_none")] - default_vibrate_timings: Option, - - // If set to true, use the Android framework's default LED light settings for the notification. - #[serde(skip_serializing_if = "Option::is_none")] - default_light_settings: Option, - - // Set the vibration pattern to use - // Duration format: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?authuser=0#google.protobuf.Duration - #[serde(skip_serializing_if = "Option::is_none")] - vibrate_timings: Option>, - - // Set the Notification.visibility of the notification. - #[serde(skip_serializing_if = "Option::is_none")] - visibility: Option, - - // Sets the number of items this notification represents. - #[serde(skip_serializing_if = "Option::is_none")] - notification_count: Option, - - // Settings to control the notification's LED blinking rate and color if LED is available on the device. - #[serde(skip_serializing_if = "Option::is_none")] - light_settings: Option, - - // Contains the URL of an image that is going to be displayed in a notification. - #[serde(skip_serializing_if = "Option::is_none")] - image: Option, -} - -#[derive(Serialize, Debug)] -//https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#androidconfig -pub struct AndroidFcmOptions { - // Label associated with the message's analytics data. - analytics_label: String, -} - -#[allow(dead_code)] -#[derive(Serialize, Debug)] -#[serde(rename_all = "UPPERCASE")] -// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#androidmessagepriority -pub enum AndroidMessagePriority { - Normal, - High, -} - -#[allow(dead_code)] -#[derive(Serialize, Debug)] -#[serde(rename_all = "UPPERCASE")] -// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#notificationpriority -pub enum NotificationPriority { - PriorityUnspecified, - PriorityMin, - PriorityLow, - PriorityDefault, - PriorityHigh, - PriorityMax, -} - -#[allow(dead_code)] -#[derive(Serialize, Debug)] -#[serde(rename_all = "UPPERCASE")] -// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#visibility -pub enum Visibility { - VisibilityUnspecified, - Private, - Public, - Secret, -} +pub mod android_config; +pub mod android_fcm_options; +pub mod android_message_priority; +pub mod android_notification; +pub mod color; +pub mod light_settings; +pub mod notification_priority; +pub mod visibility; diff --git a/src/android/notification_priority.rs b/src/android/notification_priority.rs new file mode 100644 index 000000000..bb1bdbf17 --- /dev/null +++ b/src/android/notification_priority.rs @@ -0,0 +1,14 @@ +use serde::Serialize; + +#[allow(dead_code)] +#[derive(Serialize, Debug)] +#[serde(rename_all = "UPPERCASE")] +// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#notificationpriority +pub enum NotificationPriority { + PriorityUnspecified, + PriorityMin, + PriorityLow, + PriorityDefault, + PriorityHigh, + PriorityMax, +} diff --git a/src/android/visibility.rs b/src/android/visibility.rs new file mode 100644 index 000000000..cd8b56dd4 --- /dev/null +++ b/src/android/visibility.rs @@ -0,0 +1,12 @@ +use serde::Serialize; + +#[allow(dead_code)] +#[derive(Serialize, Debug)] +#[serde(rename_all = "UPPERCASE")] +// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#visibility +pub enum Visibility { + VisibilityUnspecified, + Private, + Public, + Secret, +} diff --git a/src/apns/apns_config.rs b/src/apns/apns_config.rs new file mode 100644 index 000000000..6d2b6b8e8 --- /dev/null +++ b/src/apns/apns_config.rs @@ -0,0 +1,37 @@ +use serde::Serialize; +use serde_json::Value; + +use super::apns_fcm_options::{ApnsFcmOptions, ApnsFcmOptionsInternal}; + +#[derive(Serialize, Debug)] +// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#apnsconfig +pub struct ApnsConfigInternal { + // HTTP request headers defined in Apple Push Notification Service. + #[serde(skip_serializing_if = "Option::is_none")] + headers: Option, + + // APNs payload as a JSON object, including both aps dictionary and custom payload. + #[serde(skip_serializing_if = "Option::is_none")] + payload: Option, + + // Options for features provided by the FCM SDK for iOS. + #[serde(skip_serializing_if = "Option::is_none")] + fcm_options: Option, +} + +#[derive(Debug)] +pub struct ApnsConfig { + pub headers: Option, + pub payload: Option, + pub fcm_options: Option, +} + +impl ApnsConfig { + pub fn finalize(self) -> ApnsConfigInternal { + ApnsConfigInternal { + headers: self.headers, + payload: self.payload, + fcm_options: self.fcm_options.map(|fcm_options| fcm_options.finalize()), + } + } +} diff --git a/src/apns/apns_fcm_options.rs b/src/apns/apns_fcm_options.rs new file mode 100644 index 000000000..841fa6412 --- /dev/null +++ b/src/apns/apns_fcm_options.rs @@ -0,0 +1,29 @@ +use serde::Serialize; + +#[derive(Serialize, Debug)] +// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#apnsfcmoptions +pub struct ApnsFcmOptionsInternal { + // Label associated with the message's analytics data. + analytics_label: Option, + + // Contains the URL of an image that is going to be displayed in a notification. + image: Option, +} + +#[derive(Debug)] +pub struct ApnsFcmOptions { + // Label associated with the message's analytics data. + pub analytics_label: Option, + + // Contains the URL of an image that is going to be displayed in a notification. + pub image: Option, +} + +impl ApnsFcmOptions { + pub fn finalize(self) -> ApnsFcmOptionsInternal { + ApnsFcmOptionsInternal { + analytics_label: self.analytics_label, + image: self.image, + } + } +} diff --git a/src/apns/mod.rs b/src/apns/mod.rs index bfd5a2f71..8cd464e28 100644 --- a/src/apns/mod.rs +++ b/src/apns/mod.rs @@ -1,28 +1,2 @@ -use serde::Serialize; -use serde_json::Value; - -#[derive(Serialize, Debug)] -// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#apnsconfig -pub struct ApnsConfig { - // HTTP request headers defined in Apple Push Notification Service. - #[serde(skip_serializing_if = "Option::is_none")] - headers: Option, - - // APNs payload as a JSON object, including both aps dictionary and custom payload. - #[serde(skip_serializing_if = "Option::is_none")] - payload: Option, - - // Options for features provided by the FCM SDK for iOS. - #[serde(skip_serializing_if = "Option::is_none")] - fcm_options: Option, -} - -#[derive(Serialize, Debug)] -// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#apnsfcmoptions -pub struct ApnsFcmOptions { - // Label associated with the message's analytics data. - analytics_label: String, - - // Contains the URL of an image that is going to be displayed in a notification. - image: String, -} +pub mod apns_config; +pub mod apns_fcm_options; diff --git a/src/client/mod.rs b/src/client/mod.rs index 788fda83b..b82ca8821 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,7 +1,7 @@ pub(crate) mod response; use crate::client::response::{ErrorReason, FcmError, FcmResponse, RetryAfter}; -use crate::Message; +use crate::{Message, MessageInternal}; use gauth::serv_account::ServiceAccount; use reqwest::header::RETRY_AFTER; use reqwest::{Body, StatusCode}; @@ -18,9 +18,17 @@ impl Default for Client { } } -#[derive(Serialize, Debug)] -pub struct MessageWrapper { - pub message: Message, +// will be used to wrap the message in a "message" field +#[derive(Serialize)] +struct MessageWrapper<'a> { + #[serde(rename = "message")] + message: &'a MessageInternal, +} + +impl MessageWrapper<'_> { + fn new(message: &MessageInternal) -> MessageWrapper { + MessageWrapper { message } + } } impl Client { @@ -107,7 +115,8 @@ impl Client { } pub async fn send(&self, message: Message) -> Result { - let wrapper = MessageWrapper { message }; + let fin = message.finalize(); + let wrapper = MessageWrapper::new(&fin); let payload = serde_json::to_vec(&wrapper).unwrap(); let project_id = match self.get_project_id() { @@ -131,6 +140,11 @@ impl Client { .body(Body::from(payload)) .build()?; + // print the body + let body = request.body().unwrap(); + let body = std::str::from_utf8(body.as_bytes().unwrap()).unwrap(); + println!("body: {}", body); + let response = self.http_client.execute(request).await?; let response_status = response.status(); diff --git a/src/lib.rs b/src/lib.rs index c44d0a4ba..58461c143 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,6 +62,7 @@ mod message; pub use crate::message::*; + mod notification; pub use crate::notification::*; mod android; diff --git a/src/message/fcm_options.rs b/src/message/fcm_options.rs new file mode 100644 index 000000000..8aa0cc7dc --- /dev/null +++ b/src/message/fcm_options.rs @@ -0,0 +1,21 @@ +use serde::Serialize; + +#[derive(Serialize, Debug)] +// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#fcmoptions +pub struct FcmOptionsInternal { + // Label associated with the message's analytics data. + analytics_label: String, +} + +#[derive(Debug)] +pub struct FcmOptions { + pub analytics_label: String, +} + +impl FcmOptions { + pub fn finalize(self) -> FcmOptionsInternal { + FcmOptionsInternal { + analytics_label: self.analytics_label, + } + } +} diff --git a/src/message/mod.rs b/src/message/mod.rs index 988073154..9abb1bcaf 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -1,21 +1,25 @@ -use crate::android::AndroidConfig; -use crate::apns::ApnsConfig; -use crate::web::WebpushConfig; -use crate::Notification; +pub mod fcm_options; +pub mod target; +#[cfg(test)] +mod tests; + use serde::ser::SerializeMap; -use serde::{Serialize, Serializer}; +use serde::Serialize; +use serde::Serializer; use serde_json::Value; -#[cfg(test)] -mod tests; +use crate::android::android_config::AndroidConfig; +use crate::android::android_config::AndroidConfigInternal; +use crate::apns::apns_config::ApnsConfig; +use crate::apns::apns_config::ApnsConfigInternal; +use crate::fcm_options::FcmOptions; +use crate::fcm_options::FcmOptionsInternal; +use crate::notification::Notification; +use crate::notification::NotificationInternal; +use crate::web::webpush_config::WebpushConfig; +use crate::web::webpush_config::WebpushConfigInternal; -#[derive(Clone, Serialize, Debug, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum Target { - Token(String), - Topic(String), - Condition(String), -} +use self::target::Target; fn output_target(target: &Target, s: S) -> Result where @@ -32,43 +36,36 @@ where #[derive(Serialize, Debug)] // https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#resource:-message -pub struct Message { +pub struct MessageInternal { // Arbitrary key/value payload, which must be UTF-8 encoded. #[serde(skip_serializing_if = "Option::is_none")] data: Option, // Basic notification template to use across all platforms. #[serde(skip_serializing_if = "Option::is_none")] - notification: Option, + notification: Option, // Android specific options for messages sent through FCM connection server. #[serde(skip_serializing_if = "Option::is_none")] - android: Option, + android: Option, // Webpush protocol options. #[serde(skip_serializing_if = "Option::is_none")] - webpush: Option, + webpush: Option, // Apple Push Notification Service specific options. #[serde(skip_serializing_if = "Option::is_none")] - apns: Option, + apns: Option, // Template for FCM SDK feature options to use across all platforms. #[serde(skip_serializing_if = "Option::is_none")] - fcm_options: Option, + fcm_options: Option, // Target to send a message to. #[serde(flatten, serialize_with = "output_target")] target: Target, } -#[derive(Serialize, Debug)] -// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#fcmoptions -pub struct FcmOptions { - // Label associated with the message's analytics data. - analytics_label: String, -} - /// /// A builder to get a `Message` instance. /// @@ -82,71 +79,26 @@ pub struct FcmOptions { /// let message = builder.finalize(); /// ``` #[derive(Debug)] -pub struct MessageBuilder { - data: Option, - notification: Option, - target: Target, +pub struct Message { + pub data: Option, + pub notification: Option, + pub target: Target, + pub android: Option, + pub webpush: Option, + pub apns: Option, + pub fcm_options: Option, } -impl MessageBuilder { - /// Get a new instance of Message. You need to supply to. - pub fn new(target: Target) -> Self { - MessageBuilder { - data: None, - notification: None, - target, - } - } - - /// Use this to add custom key-value pairs to the message. This data - /// must be handled appropriately on the client end. The data can be - /// anything that Serde can serialize to JSON. - /// - /// # Examples: - /// ```rust - /// use fcm::{MessageBuilder, Target}; - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert("message", "Howdy!"); - /// - /// let mut builder = MessageBuilder::new(Target::Token("token".to_string())); - /// builder.data(&map).expect("Should have been able to add data"); - /// let message = builder.finalize(); - /// ``` - pub fn data(&mut self, data: &dyn erased_serde::Serialize) -> Result<&mut Self, serde_json::Error> { - self.data = Some(serde_json::to_value(data)?); - Ok(self) - } - - /// Use this to set a `Notification` for the message. - /// # Examples: - /// ```rust - /// use fcm::{MessageBuilder, NotificationBuilder, Target}; - /// - /// let mut builder = NotificationBuilder::new(); - /// builder.title("Hey!".to_string()); - /// builder.body("Do you want to catch up later?".to_string()); - /// let notification = builder.finalize(); - /// - /// let mut builder = MessageBuilder::new(Target::Token("token".to_string())); - /// builder.notification(notification); - /// let message = builder.finalize(); - /// ``` - pub fn notification(&mut self, notification: Notification) -> &mut Self { - self.notification = Some(notification); - self - } - +impl Message { /// Complete the build and get a `Message` instance - pub fn finalize(self) -> Message { - Message { + pub fn finalize(self) -> MessageInternal { + MessageInternal { data: self.data, - notification: self.notification, - android: None, - webpush: None, - apns: None, - fcm_options: None, + notification: self.notification.map(|n| n.finalize()), + android: self.android.map(|a| a.finalize()), + webpush: self.webpush.map(|w| w.finalize()), + apns: self.apns.map(|a| a.finalize()), + fcm_options: self.fcm_options.map(|f| f.finalize()), target: self.target, } } diff --git a/src/message/target.rs b/src/message/target.rs new file mode 100644 index 000000000..b90db8584 --- /dev/null +++ b/src/message/target.rs @@ -0,0 +1,9 @@ +use serde::Serialize; + +#[derive(Clone, Serialize, Debug, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum Target { + Token(String), + Topic(String), + Condition(String), +} diff --git a/src/message/tests.rs b/src/message/tests.rs index c23e94e45..0773b3433 100644 --- a/src/message/tests.rs +++ b/src/message/tests.rs @@ -1,18 +1,19 @@ -use crate::notification::NotificationBuilder; -use crate::{MessageBuilder, Target}; -use serde::Serialize; +use crate::{notification::Notification, target::Target, Message}; use serde_json::json; -#[derive(Serialize)] -struct CustomData { - foo: &'static str, - bar: bool, -} - #[test] fn should_create_new_message() { let target = Target::Token("token".to_string()); - let msg = MessageBuilder::new(target.clone()).finalize(); + let msg = Message { + target: target.clone(), + data: None, + notification: None, + android: None, + webpush: None, + apns: None, + fcm_options: None, + } + .finalize(); assert_eq!(msg.target, target); } @@ -20,7 +21,16 @@ fn should_create_new_message() { #[test] fn should_leave_nones_out_of_the_json() { let target = Target::Token("token".to_string()); - let msg = MessageBuilder::new(target).finalize(); + let msg = Message { + target: target.clone(), + data: None, + notification: None, + android: None, + webpush: None, + apns: None, + fcm_options: None, + } + .finalize(); let payload = serde_json::to_string(&msg).unwrap(); let expected_payload = json!({ @@ -34,11 +44,17 @@ fn should_leave_nones_out_of_the_json() { #[test] fn should_add_custom_data_to_the_payload() { let target = Target::Token("token".to_string()); - let mut builder = MessageBuilder::new(target); - - let data = CustomData { foo: "bar", bar: false }; - - builder.data(&data).unwrap(); + let data = json!({ "foo": "bar", "bar": false }); + + let builder = Message { + target: target, + data: Some(data), + notification: None, + android: None, + webpush: None, + apns: None, + fcm_options: None, + }; let msg = builder.finalize(); let payload = serde_json::to_string(&msg).unwrap(); @@ -58,9 +74,20 @@ fn should_add_custom_data_to_the_payload() { #[test] fn should_be_able_to_render_a_full_token_message_to_json() { let target = Target::Token("token".to_string()); - let mut builder = MessageBuilder::new(target); - - builder.notification(NotificationBuilder::new().finalize()); + let notification = Notification { + title: None, + body: None, + image: None, + }; + let builder = Message { + target: target.clone(), + data: None, + notification: Some(notification), + android: None, + webpush: None, + apns: None, + fcm_options: None, + }; let payload = serde_json::to_string(&builder.finalize()).unwrap(); @@ -76,9 +103,20 @@ fn should_be_able_to_render_a_full_token_message_to_json() { #[test] fn should_be_able_to_render_a_full_topic_message_to_json() { let target = Target::Topic("my_topic".to_string()); - let mut builder = MessageBuilder::new(target); - - builder.notification(NotificationBuilder::new().finalize()); + let notification = Notification { + title: None, + body: None, + image: None, + }; + let builder = Message { + target: target.clone(), + data: None, + notification: Some(notification), + android: None, + webpush: None, + apns: None, + fcm_options: None, + }; let payload = serde_json::to_string(&builder.finalize()).unwrap(); @@ -94,9 +132,20 @@ fn should_be_able_to_render_a_full_topic_message_to_json() { #[test] fn should_be_able_to_render_a_full_condition_message_to_json() { let target = Target::Condition("my_condition".to_string()); - let mut builder = MessageBuilder::new(target); - - builder.notification(NotificationBuilder::new().finalize()); + let notification = Notification { + title: None, + body: None, + image: None, + }; + let builder = Message { + target: target.clone(), + data: None, + notification: Some(notification), + android: None, + webpush: None, + apns: None, + fcm_options: None, + }; let payload = serde_json::to_string(&builder.finalize()).unwrap(); @@ -112,15 +161,34 @@ fn should_be_able_to_render_a_full_condition_message_to_json() { #[test] fn should_set_notifications() { let target = Target::Token("token".to_string()); - let msg = MessageBuilder::new(target.clone()).finalize(); - - assert_eq!(msg.notification, None); - - let nm = NotificationBuilder::new().finalize(); - - let mut builder = MessageBuilder::new(target); - builder.notification(nm); + let msg = Message { + target: target.clone(), + data: None, + notification: None, + android: None, + webpush: None, + apns: None, + fcm_options: None, + }; + + assert_eq!(msg.notification.is_none(), true); + + let nm = Notification { + title: None, + body: None, + image: None, + }; + + let builder = Message { + target: target.clone(), + data: None, + notification: Some(nm), + android: None, + webpush: None, + apns: None, + fcm_options: None, + }; let msg = builder.finalize(); - assert_ne!(msg.notification, None); + assert_eq!(msg.notification.is_none(), false); } diff --git a/src/notification/mod.rs b/src/notification/mod.rs index 0ca5606a0..7be580c22 100644 --- a/src/notification/mod.rs +++ b/src/notification/mod.rs @@ -1,14 +1,14 @@ -use serde::Serialize; - #[cfg(test)] mod tests; +use serde::Serialize; + /// This struct represents a FCM notification. Use the /// corresponding `NotificationBuilder` to get an instance. You can then use /// this notification instance when sending a FCM message. #[derive(Serialize, Debug, PartialEq)] // https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#notification -pub struct Notification { +pub struct NotificationInternal { // The notification's title. #[serde(skip_serializing_if = "Option::is_none")] title: Option, @@ -34,40 +34,17 @@ pub struct Notification { /// builder.body("3 runs to win in 1 ball".to_string()); /// let notification = builder.finalize(); /// ``` -#[derive(Default)] -pub struct NotificationBuilder { - title: Option, - body: Option, - image: Option, +#[derive(Debug)] +pub struct Notification { + pub title: Option, + pub body: Option, + pub image: Option, } -impl NotificationBuilder { - /// Get a new `NotificationBuilder` instance, with a title. - pub fn new() -> NotificationBuilder { - Self::default() - } - - // Set the title of the notification - pub fn title(&mut self, title: String) -> &mut Self { - self.title = Some(title); - self - } - - /// Set the body of the notification - pub fn body(&mut self, body: String) -> &mut Self { - self.body = Some(body); - self - } - - /// Set the image - pub fn image(&mut self, image: String) -> &mut Self { - self.image = Some(image); - self - } - +impl Notification { /// Complete the build and get a `Notification` instance - pub fn finalize(self) -> Notification { - Notification { + pub fn finalize(self) -> NotificationInternal { + NotificationInternal { title: self.title, body: self.body, image: self.image, diff --git a/src/notification/tests.rs b/src/notification/tests.rs index 36315ec1a..3bf593d87 100644 --- a/src/notification/tests.rs +++ b/src/notification/tests.rs @@ -1,16 +1,15 @@ -use crate::NotificationBuilder; +use crate::Notification; use serde_json::json; #[test] fn should_be_able_to_render_a_full_notification_to_json() { - let mut builder = NotificationBuilder::new(); + let not = Notification { + title: Some("foo".to_string()), + body: Some("bar".to_string()), + image: Some("https://my.image.com/test.jpg".to_string()), + }; - builder - .title("foo".to_string()) - .body("bar".to_string()) - .image("https://my.image.com/test.jpg".to_string()); - - let payload = serde_json::to_string(&builder.finalize()).unwrap(); + let payload = serde_json::to_string(¬.finalize()).unwrap(); let expected_payload = json!({ "title": "foo", @@ -21,38 +20,3 @@ fn should_be_able_to_render_a_full_notification_to_json() { assert_eq!(expected_payload, payload); } - -#[test] -fn should_set_notification_title() { - let nm = NotificationBuilder::new().finalize(); - - assert_eq!(nm.title, None); - - let mut builder = NotificationBuilder::new(); - builder.title("title".to_string()); - let nm = builder.finalize(); - - assert_eq!(nm.title, Some("title".to_string())); -} - -#[test] -fn should_set_notification_body() { - let nm = NotificationBuilder::new().finalize(); - - assert_eq!(nm.body, None); - - let mut builder = NotificationBuilder::new(); - builder.body("body".to_string()); - let nm = builder.finalize(); - - assert_eq!(nm.body, Some("body".to_string())); -} - -#[test] -fn should_set_notification_image() { - let mut builder = NotificationBuilder::new(); - builder.image("https://my.image.com/test.jpg".to_string()); - let nm = builder.finalize(); - - assert_eq!(nm.image, Some("https://my.image.com/test.jpg".to_string())); -} diff --git a/src/web/mod.rs b/src/web/mod.rs index 60d42990d..52e1762c8 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -1,33 +1,2 @@ -use serde::Serialize; -use serde_json::Value; - -#[derive(Serialize, Debug)] -// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#webpushconfig -pub struct WebpushConfig { - // HTTP headers defined in webpush protocol. - #[serde(skip_serializing_if = "Option::is_none")] - headers: Option, - - // Arbitrary key/value payload. - #[serde(skip_serializing_if = "Option::is_none")] - data: Option, - - // Web Notification options as a JSON object. - // Struct format: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?authuser=0#google.protobuf.Struct - #[serde(skip_serializing_if = "Option::is_none")] - notification: Option, - - // Options for features provided by the FCM SDK for Web. - #[serde(skip_serializing_if = "Option::is_none")] - fcm_options: Option, -} - -#[derive(Serialize, Debug)] -// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#webpushfcmoptions -pub struct WebpushFcmOptions { - // The link to open when the user clicks on the notification. - link: String, - - // Label associated with the message's analytics data. - analytics_label: String, -} +pub mod webpush_config; +pub mod webpush_fcm_options; diff --git a/src/web/webpush_config.rs b/src/web/webpush_config.rs new file mode 100644 index 000000000..e78bfd345 --- /dev/null +++ b/src/web/webpush_config.rs @@ -0,0 +1,44 @@ +use serde::Serialize; +use serde_json::Value; + +use super::webpush_fcm_options::{WebpushFcmOptions, WebpushFcmOptionsInternal}; + +#[derive(Serialize, Debug)] +// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#webpushconfig +pub struct WebpushConfigInternal { + // HTTP headers defined in webpush protocol. + #[serde(skip_serializing_if = "Option::is_none")] + headers: Option, + + // Arbitrary key/value payload. + #[serde(skip_serializing_if = "Option::is_none")] + data: Option, + + // Web Notification options as a JSON object. + // Struct format: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf?authuser=0#google.protobuf.Struct + #[serde(skip_serializing_if = "Option::is_none")] + notification: Option, + + // Options for features provided by the FCM SDK for Web. + #[serde(skip_serializing_if = "Option::is_none")] + fcm_options: Option, +} + +#[derive(Debug)] +pub struct WebpushConfig { + pub headers: Option, + pub data: Option, + pub notification: Option, + pub fcm_options: Option, +} + +impl WebpushConfig { + pub fn finalize(self) -> WebpushConfigInternal { + WebpushConfigInternal { + headers: self.headers, + data: self.data, + notification: self.notification, + fcm_options: self.fcm_options.map(|fcm_options| fcm_options.finalize()), + } + } +} diff --git a/src/web/webpush_fcm_options.rs b/src/web/webpush_fcm_options.rs new file mode 100644 index 000000000..45b78c673 --- /dev/null +++ b/src/web/webpush_fcm_options.rs @@ -0,0 +1,30 @@ +use serde::Serialize; + +#[derive(Serialize, Debug)] +// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#webpushfcmoptions +pub struct WebpushFcmOptionsInternal { + // The link to open when the user clicks on the notification. + link: String, + + // Label associated with the message's analytics data. + analytics_label: String, +} + +#[derive(Debug)] +// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?authuser=0#webpushfcmoptions +pub struct WebpushFcmOptions { + // The link to open when the user clicks on the notification. + pub link: String, + + // Label associated with the message's analytics data. + pub analytics_label: String, +} + +impl WebpushFcmOptions { + pub fn finalize(self) -> WebpushFcmOptionsInternal { + WebpushFcmOptionsInternal { + link: self.link, + analytics_label: self.analytics_label, + } + } +}