fcm-client-0.2.0.0: src/FCMClient/JSON/Types.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
-- | Google Firebase Cloud Messaging model / JSON conversions.
-- https://firebase.google.com/docs/cloud-messaging/http-server-ref
--
-- Data types in this module map field for field in the google docs.
-- See FCMClient.Types module for some wrapper functions on top of raw JSON types.
--
module FCMClient.JSON.Types (
FCMData
, FCMNotification
, fcmTitle
, fcmBody
, fcmAndroidChannelId
, fcmIcon
, fcmSound
, fcmTag
, fcmColor
, fcmBadge
, fcmClickAction
, fcmBodyLocKey
, fcmBodyLocArgs
, fcmTitleLocKey
, fcmTitleLocArgs
, FCMMessage
, fcmTo
, fcmRegistrationIds
, fcmCondition
, fcmCollapseKey
, fcmPriority
, fcmContentAvailable
, fcmDelayWhileIdle
, fcmTimeToLive
, fcmRestrictedPackageName
, fcmDryRun
, fcmData
, fcmNotification
, FCMResult ( FCMResultSuccess
, FCMResultError
)
, _FCMResultSuccess
, _FCMResultError
, FCMClientError ( FCMErrorResponseInvalidJSON
, FCMErrorResponseInvalidAuth
, FCMServerError
, FCMClientJSONError
, FCMClientHTTPError
)
, fcmErrorMessage
, fcmErrorHttpStatus
, _FCMErrorResponseInvalidJSON
, _FCMErrorResponseInvalidAuth
, _FCMServerError
, _FCMClientJSONError
, _FCMClientHTTPError
, FCMResponseBody(..)
, FCMMessageResponse
, _FCMMessageResponse
, _FCMTopicResponse
, fcmCanonicalIds
, fcmFailure
, fcmMulticastId
, fcmResults
, fcmSuccess
, FCMMessageResponseResult(..)
, _FCMMessageResponseResultOk
, _FCMMessageResponseResultError
, FCMMessageResponseResultOk
, fcmMessageId
, fcmRegistrationId
, FCMTopicResponse(..)
, FCMTopicResponseOk
, _FCMTopicResponseOk
, _FCMTopicResponseError
, fcmTopicMessageId
, FCMError(..)
) where
import Control.Applicative
import Control.Lens.TH
import Data.Aeson
import Data.Aeson.Casing
import Data.Aeson.TH
import Data.Aeson.Types
import Data.Default.Class
import Data.List.NonEmpty (NonEmpty)
import Data.Map (Map)
import Data.Text (Text)
import Network.HTTP.Types (Status)
type FCMData = Map Text Text
-- | FCM Notification as defined in https://firebase.google.com/docs/cloud-messaging/http-server-ref#notification-payload-support
-- Abstract type, use lens API to access fields. Record fields are kept private and used for JSON conversion.
data FCMNotification =
FCMNotification {
-- | title Optional, string
-- Indicates notification title. This field is not visible on iOS phones and tablets.
_fcmTitle :: !(Maybe Text)
-- | body Optional, string
-- Indicates notification body text.
, _fcmBody :: !(Maybe Text)
-- | android_channel_id Optional, string
-- The notification's channel id (new in Android O). <https://developer.android.com/preview/features/notification-channels.html>
--
-- The app must create a channel with this channel ID before any notification
-- with this channel ID is received.
--
-- If you don't send this channel ID in the request, or if the channel ID
-- provided has not yet been created by the app, FCM uses the channel ID
-- specified in the app manifest.
, _fcmAndroidChannelId :: !(Maybe Text)
-- | icon Optional, string
-- Android: Indicates notification icon. Sets value to myicon for drawable resource myicon.
, _fcmIcon :: !(Maybe Text)
-- | sound Optional, string
-- IOS: Indicates a sound to play when the device receives a notification. Sound
-- files can be in the main bundle of the client app or in the
-- Library/Sounds folder of the app's data container. See the iOS Developer
-- Library for more information.
-- Android: Indicates a sound to play when the device receives a
-- notification. Supports default or the filename of a sound resource
-- bundled in the app. Sound files must reside in /res/raw/.
, _fcmSound :: !(Maybe Text)
-- | tag Optional, string
-- Android: Indicates whether each notification results in a new entry in the
-- notification drawer on Android. If not set, each request creates a new
-- notification. If set, and a notification with the same tag is already being
-- shown, the new notification replaces the existing one in the notification
-- drawer.
, _fcmTag :: !(Maybe Text)
-- | color Optional, string
-- Android: Indicates color of the icon, expressed in #rrggbb format
, _fcmColor:: !(Maybe Text)
-- | badge Optional, string
-- Indicates the badge on the client app home icon.
, _fcmBadge :: !(Maybe Text)
-- | click_action Optional, string
-- IOS: Indicates the action associated with a user click on the notification.
-- Corresponds to category in the APNs payload.
-- Android: Indicates the action associated with a user click on the
-- notification. When this is set, an activity with a matching intent
-- filter is launched when user clicks the notification.
, _fcmClickAction :: !(Maybe Text)
-- | body_loc_key Optional, string
-- IOS: Indicates the key to the body string for localization. Corresponds to
-- "loc-key" in the APNs payload.
-- Android: Indicates the key to the body string for localization. Use the
-- key in the app's string resources when populating this value.
, _fcmBodyLocKey :: !(Maybe Text)
-- | body_loc_args Optional, JSON array as string
-- IOS: Indicates the string value to replace format specifiers in the body string
-- for localization. Corresponds to "loc-args" in the APNs payload.
-- Android: Indicates the string value to replace format specifiers in the
-- body string for localization. For more information, see Formatting and
-- Styling.
, _fcmBodyLocArgs :: !(Maybe Text)
-- | title_loc_key Optional, string
-- IOS: Indicates the key to the title string for localization. Corresponds to
-- "title-loc-key" in the APNs payload.
-- Android: Indicates the key to the title string for localization. Use the
-- key in the app's string resources when populating this value.
, _fcmTitleLocKey :: !(Maybe Text)
-- | title_loc_args Optional, JSON array as string
-- IOS: Indicates the string value to replace format specifiers in the title
-- string for localization.Corresponds to "title-loc-args" in the APNs
-- payload.
-- Android: Indicates the string value to replace format specifiers in the
-- title string for localization. For more information, see Formatting
-- strings.
, _fcmTitleLocArgs:: !(Maybe Text)
} deriving (Eq, Show)
$(makeLenses ''FCMNotification)
$(deriveJSON (aesonPrefix snakeCase) { omitNothingFields = True } ''FCMNotification)
instance Default FCMNotification where
def = FCMNotification Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
-- | FCM Message as defined in https://firebase.google.com/docs/cloud-messaging/http-server-ref#send-downstream
-- Abstract type, use lens API to access fields. Record fields are kept private and used for JSON conversion.
data FCMMessage =
FCMMessage {
-- | to Optional, string
-- This parameter specifies the recipient of a message.
-- The value must be a registration token, notification key, or topic.
-- Do not set this field when sending to multiple topics. See condition.
_fcmTo :: !(Maybe Text)
-- | registration_ids String array
-- This parameter specifies a list of devices (registration tokens, or IDs)
-- receiving a multicast message. It must contain at least 1 and at most 1000
-- registration tokens.
-- Use this parameter only for multicast messaging, not for single
-- recipients. Multicast messages (sending to more than 1 registration tokens) are
-- allowed using HTTP JSON format only.
, _fcmRegistrationIds :: !(Maybe (NonEmpty Text))
-- | condition Optional, string
-- This parameter specifies a logical expression of conditions that determine
-- the message target. Supported condition: Topic, formatted as "'yourTopic' in
-- topics". This value is case-insensitive. Supported operators: &&, ||. Maximum
-- two operators per topic message supported.
, _fcmCondition :: !(Maybe Text)
-- | collapse_key Optional, string
-- This parameter identifies a group of messages (e.g., with collapse_key:
-- "Updates Available") that can be collapsed, so that only the last message gets
-- sent when delivery can be resumed. This is intended to avoid sending too many
-- of the same messages when the device comes back online or becomes active (see
-- delay_while_idle).
-- Note that there is no guarantee of the order in which messages get sent.
-- Note: A maximum of 4 different collapse keys is allowed at any given time.
-- This means a FCM connection server can simultaneously store 4 different
-- send-to-sync messages per client app. If you exceed this number, there is no
-- guarantee which 4 collapse keys the FCM connection server will keep.
, _fcmCollapseKey :: !(Maybe Text)
-- | priority Optional, string
-- Sets the priority of the message. Valid values are "normal" and "high." On
-- iOS, these correspond to APNs priorities 5 and 10. By default, messages are
-- sent with normal priority. Normal priority optimizes the client app's
-- battery consumption and should be used unless immediate delivery is
-- required. For messages with normal priority, the app may receive the message
-- with unspecified delay. When a message is sent with high priority, it is
-- sent immediately, and the app can wake a sleeping device and open a network
-- connection to your server. For more information, see Setting the priority
-- of a message.
, _fcmPriority :: !(Maybe Text)
-- | content_available Optional, JSON boolean
-- On iOS, use this field to represent content-available in the APNS
-- payload. When a notification or message is sent and this is set to true,
-- an inactive client app is awoken. On Android, data messages wake the app
-- by default. On Chrome, currently not supported.
, _fcmContentAvailable :: !(Maybe Bool)
-- | delay_while_idle Optional, JSON boolean
-- When this parameter is set to true, it indicates that the message should
-- not be sent until the device becomes active. The default value is false.
, _fcmDelayWhileIdle :: !(Maybe Bool)
-- | time_to_live Optional, JSON number
-- This parameter specifies how long (in seconds) the message should be kept in
-- FCM storage if the device is offline. The maximum time to live supported is
-- 4 weeks, and the default value is 4 weeks. For more information, see Setting
-- the lifespan of a message.
, _fcmTimeToLive :: !(Maybe Word)
-- | restricted_package_name Optional, string
-- This parameter specifies the package name of the application where the
-- registration tokens must match in order to receive the message.
, _fcmRestrictedPackageName :: !(Maybe Text)
-- | dry_run Optional, JSON boolean
-- This parameter, when set to true, allows developers to test a request
-- without actually sending a message. The default value is false.
, _fcmDryRun :: !(Maybe Bool)
-- Payload
-- | data Optional, JSON object
-- This parameter specifies the custom key-value pairs of the message's
-- payload. For example, with data:{"score":"3x1"}: On iOS, if the message is
-- sent via APNS, it represents the custom data fields. If it is sent via FCM
-- connection server, it would be represented as key value dictionary in
-- AppDelegate application:didReceiveRemoteNotification:. On Android, this
-- would result in an intent extra named score with the string value 3x1. The
-- key should not be a reserved word ("from" or any word starting with
-- "google" or "gcm"). Do not use any of the words defined in this table (such
-- as collapse_key). Values in string types are recommended. You have to
-- convert values in objects or other non-string data types (e.g., integers or
-- booleans) to string.
, _fcmData :: !(Maybe FCMData)
-- | notification Optional, JSON object
-- This parameter specifies the predefined, user-visible key-value pairs of
-- the notification payload. See Notification payload support for detail.
-- For more information about notification message and data message
-- options, see Payload.
, _fcmNotification :: !(Maybe FCMNotification)
} deriving (Eq, Show)
$(makeLenses ''FCMMessage)
$(deriveJSON (aesonPrefix snakeCase) { omitNothingFields = True } ''FCMMessage)
instance Default FCMMessage where
def = FCMMessage Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
-- | String specifying the error that occurred when processing the message
-- for the recipient. The possible values can be found in table 9.
-- https://firebase.google.com/docs/cloud-messaging/http-server-ref#table9
data FCMError =
FCMErrorDeviceMessageRate
| FCMErrorInternalServerError
| FCMErrorInvalidDataKey
| FCMErrorInvalidPackageName
| FCMErrorInvalidRegistration
| FCMErrorInvalidTtl
| FCMErrorMessageTooBig
| FCMErrorMismatchSenderId
| FCMErrorMissingRegistration
| FCMErrorNotRegistered
| FCMErrorTopicsMessageRate
| FCMErrorUnavailable
| FCMErrorOther Text -- ^ used if we can't parse any of the above
deriving (Eq, Show)
instance ToJSON FCMError where
toJSON FCMErrorDeviceMessageRate = object [ ("error", "DeviceMessageRate") ]
toJSON FCMErrorInternalServerError = object [ ("error", "InternalServerError") ]
toJSON FCMErrorInvalidDataKey = object [ ("error", "InvalidDataKey") ]
toJSON FCMErrorInvalidPackageName = object [ ("error", "InvalidPackageName") ]
toJSON FCMErrorInvalidRegistration = object [ ("error", "InvalidRegistration") ]
toJSON FCMErrorInvalidTtl = object [ ("error", "InvalidTtl") ]
toJSON FCMErrorMessageTooBig = object [ ("error", "MessageTooBig") ]
toJSON FCMErrorMismatchSenderId = object [ ("error", "MismatchSenderId") ]
toJSON FCMErrorMissingRegistration = object [ ("error", "MissingRegistration") ]
toJSON FCMErrorNotRegistered = object [ ("error", "NotRegistered") ]
toJSON FCMErrorTopicsMessageRate = object [ ("error", "TopicsMessageRate") ]
toJSON FCMErrorUnavailable = object [ ("error", "Unavailable") ]
toJSON (FCMErrorOther e) = object [ ("error", toJSON e) ]
instance FromJSON FCMError where
parseJSON (Object v) = fmap ( \(n :: Text) ->
case n
of "DeviceMessageRate" -> FCMErrorDeviceMessageRate
"InternalServerError" -> FCMErrorInternalServerError
"InvalidDataKey" -> FCMErrorInvalidDataKey
"InvalidPackageName" -> FCMErrorInvalidPackageName
"InvalidRegistration" -> FCMErrorInvalidRegistration
"InvalidTtl" -> FCMErrorInvalidTtl
"MessageTooBig" -> FCMErrorMessageTooBig
"MismatchSenderId" -> FCMErrorMismatchSenderId
"MissingRegistration" -> FCMErrorMissingRegistration
"NotRegistered" -> FCMErrorNotRegistered
"TopicsMessageRate" -> FCMErrorTopicsMessageRate
"Unavailable" -> FCMErrorUnavailable
e -> FCMErrorOther e
) (v .: "error")
parseJSON x = typeMismatch "Object" x
data FCMMessageResponseResultOk =
FCMMessageResponseResultOkPayload {
-- | String specifying a unique ID for each successfully processed message.
_fcmMessageId :: !Text
-- | Optional string specifying the canonical registration token for the
-- client app that the message was processed and sent to. Sender should use
-- this value as the registration token for future requests. Otherwise, the
-- messages might be rejected.
, _fcmRegistrationId :: !(Maybe Text)
} deriving (Eq, Show)
$(makeLenses ''FCMMessageResponseResultOk)
$(deriveJSON (aesonPrefix snakeCase) { omitNothingFields = True } ''FCMMessageResponseResultOk)
instance Default FCMMessageResponseResultOk where
def = FCMMessageResponseResultOkPayload "" Nothing
data FCMMessageResponseResult =
FCMMessageResponseResultOk !FCMMessageResponseResultOk
| FCMMessageResponseResultError !FCMError
deriving (Eq, Show)
$(makePrisms ''FCMMessageResponseResult)
instance ToJSON FCMMessageResponseResult where
toJSON (FCMMessageResponseResultOk b) = toJSON b
toJSON (FCMMessageResponseResultError e) = toJSON e
toEncoding (FCMMessageResponseResultOk b) = toEncoding b
toEncoding (FCMMessageResponseResultError e) = toEncoding e
instance FromJSON FCMMessageResponseResult where
parseJSON o = (FCMMessageResponseResultOk <$> parseJSON o)
<|> (FCMMessageResponseResultError <$> parseJSON o)
data FCMMessageResponse =
FCMMessageResponsePayload {
-- | Required, number Unique ID (number) identifying the multicast message.
_fcmMulticastId :: !Integer
-- | Required, number Number of messages that were processed without an error.
, _fcmSuccess :: !Integer
-- | Required, number Number of messages that could not be processed.
, _fcmFailure :: !Integer
-- | Required, number Number of results that contain a canonical
-- registration token. See the registration overview for more discussion of
-- this topic.
, _fcmCanonicalIds :: !Integer
-- | Optional, array object Array of objects representing the status of
-- the messages processed. The objects are listed in the same order as the
-- request (i.e., for each registration ID in the request, its result is
-- listed in the same index in the response).
, _fcmResults :: !(Maybe (NonEmpty FCMMessageResponseResult))
} deriving (Eq, Show)
$(makeLenses ''FCMMessageResponse)
$(deriveJSON (aesonPrefix snakeCase) { omitNothingFields = True } ''FCMMessageResponse)
instance Default FCMMessageResponse where
def = FCMMessageResponsePayload 0 0 0 0 Nothing
newtype FCMTopicResponseOk =
FCMTopicResponseOkPayload {
-- | Optional, number The topic message ID when FCM has successfully
-- received the request and will attempt to deliver to all subscribed
-- devices.
_fcmTopicMessageId :: Integer
} deriving (Eq, Show)
$(makeLenses ''FCMTopicResponseOk )
$(deriveJSON (aesonDrop 9 snakeCase) { omitNothingFields = True } ''FCMTopicResponseOk)
instance Default FCMTopicResponseOk where
def = FCMTopicResponseOkPayload 0
data FCMTopicResponse =
FCMTopicResponseOk !FCMTopicResponseOk
| FCMTopicResponseError !FCMError
deriving (Eq, Show)
$(makePrisms ''FCMTopicResponse)
instance ToJSON FCMTopicResponse where
toJSON (FCMTopicResponseOk o) = toJSON o
toJSON (FCMTopicResponseError e) = toJSON e
toEncoding (FCMTopicResponseOk o) = toEncoding o
toEncoding (FCMTopicResponseError e) = toEncoding e
instance FromJSON FCMTopicResponse where
parseJSON o = (FCMTopicResponseOk <$> parseJSON o)
<|> (FCMTopicResponseError <$> parseJSON o)
data FCMResponseBody = FCMMessageResponse !FCMMessageResponse
| FCMTopicResponse !FCMTopicResponse
deriving (Eq, Show)
$(makePrisms ''FCMResponseBody)
instance ToJSON FCMResponseBody where
toJSON (FCMMessageResponse m) = toJSON m
toJSON (FCMTopicResponse t) = toJSON t
toEncoding (FCMMessageResponse m) = toEncoding m
toEncoding (FCMTopicResponse t) = toEncoding t
instance FromJSON FCMResponseBody where
parseJSON o = (FCMMessageResponse <$> parseJSON o)
<|> (FCMTopicResponse <$> parseJSON o)
-- | Types of FCM errors.
data FCMClientError =
-- | Indicates that the request could not be parsed as JSON, or it
-- contained invalid fields (for instance, passing a string where a number
-- was expected). The exact failure reason is described in the response and
-- the problem should be addressed before the request can be retried.
FCMErrorResponseInvalidJSON { _fcmErrorMessage :: !Text
}
-- | There was an error authenticating the sender account.
| FCMErrorResponseInvalidAuth
-- | Errors in the 500-599 range (such as 500 or 503) indicate that there
-- was an internal error in the FCM connection server while trying to
-- process the request, or that the server is temporarily unavailable (for
-- example, because of timeouts). Sender must retry later, honoring any
-- Retry-After header included in the response. Application servers must
-- implement exponential back-off.
| FCMServerError { _fcmErrorHttpStatus :: !Status
, _fcmErrorMessage :: !Text
}
-- | Client couldn't parse JSON response from server.
| FCMClientJSONError { _fcmErrorMessage :: !Text
}
-- | Unexpected HTTP response or some other HTTP error.
| FCMClientHTTPError { _fcmErrorMessage :: !Text
}
deriving (Show)
$(makeLenses ''FCMClientError)
$(makePrisms ''FCMClientError)
-- | Result of an RPC call.
--
-- Successful response doesn't imply all the messages were delivered,
-- e.g. some may need to be re-sent if a rate limit was exceeded.
--
-- Error cases enumerate all, client and server error conditions.
--
data FCMResult =
-- | Successful response (http 200).
-- Doesn't imply all the messages were delivered,
-- response body may contain error codes.
FCMResultSuccess !FCMResponseBody
-- | Didn't receive JSON response, there were an error of some kind.
| FCMResultError !FCMClientError
deriving (Show)
$(makePrisms ''FCMResult)