firebase-admin-sdk 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -21
  3. data/firebase-admin-sdk.gemspec +1 -0
  4. data/lib/firebase-admin-sdk.rb +21 -1
  5. data/lib/firebase/admin/app.rb +0 -6
  6. data/lib/firebase/admin/auth/client.rb +8 -0
  7. data/lib/firebase/admin/auth/token_verifier.rb +1 -1
  8. data/lib/firebase/admin/auth/user_info.rb +6 -6
  9. data/lib/firebase/admin/auth/user_manager.rb +2 -2
  10. data/lib/firebase/admin/auth/user_record.rb +5 -5
  11. data/lib/firebase/admin/config.rb +4 -2
  12. data/lib/firebase/admin/internal/http_client.rb +1 -0
  13. data/lib/firebase/admin/messaging/android_config.rb +77 -0
  14. data/lib/firebase/admin/messaging/android_fcm_options.rb +19 -0
  15. data/lib/firebase/admin/messaging/android_notification.rb +221 -0
  16. data/lib/firebase/admin/messaging/apns_config.rb +38 -0
  17. data/lib/firebase/admin/messaging/apns_fcm_options.rb +27 -0
  18. data/lib/firebase/admin/messaging/apns_payload.rb +28 -0
  19. data/lib/firebase/admin/messaging/aps.rb +82 -0
  20. data/lib/firebase/admin/messaging/aps_alert.rb +110 -0
  21. data/lib/firebase/admin/messaging/client.rb +181 -0
  22. data/lib/firebase/admin/messaging/critical_sound.rb +37 -0
  23. data/lib/firebase/admin/messaging/error.rb +36 -0
  24. data/lib/firebase/admin/messaging/error_info.rb +25 -0
  25. data/lib/firebase/admin/messaging/fcm_options.rb +19 -0
  26. data/lib/firebase/admin/messaging/light_settings.rb +34 -0
  27. data/lib/firebase/admin/messaging/message.rb +83 -0
  28. data/lib/firebase/admin/messaging/message_encoder.rb +355 -0
  29. data/lib/firebase/admin/messaging/multicast_message.rb +67 -0
  30. data/lib/firebase/admin/messaging/notification.rb +34 -0
  31. data/lib/firebase/admin/messaging/topic_management_response.rb +41 -0
  32. data/lib/firebase/admin/messaging/utils.rb +78 -0
  33. data/lib/firebase/admin/version.rb +1 -1
  34. metadata +36 -2
@@ -0,0 +1,38 @@
1
+ module Firebase
2
+ module Admin
3
+ module Messaging
4
+ # APNS-specific options that can be included in a {Message}.
5
+ #
6
+ # Refer to `APNS Documentation` for more information.
7
+ # @see https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html
8
+ class APNSConfig
9
+ # @return [Hash{Symbol,String => String}, nil]
10
+ # A collection of APNs headers. Header values must be strings.
11
+ attr_accessor :headers
12
+
13
+ # @return [APNSPayload, nil]
14
+ # An APNs payload to be included in the message.
15
+ attr_accessor :payload
16
+
17
+ # @return [APNSFCMOptions, nil]
18
+ # Options for features provided by the FCM SDK for iOS.
19
+ attr_accessor :fcm_options
20
+
21
+ # Initializes an {APNSConfig}.
22
+ #
23
+ # @param [Hash{Symbol,String => String}, nil] headers
24
+ # A collection of APNs headers (optional).
25
+ # Header keys and values must be strings.
26
+ # @param [APNSPayload, nil] payload
27
+ # An APNs payload to be included in the message (optional).
28
+ # @param [APNSFCMOptions, nil] fcm_options
29
+ # Options for features provided by the FCM SDK for iOS (optional).
30
+ def initialize(headers: nil, payload: nil, fcm_options: nil)
31
+ self.headers = headers
32
+ self.payload = payload
33
+ self.fcm_options = fcm_options
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,27 @@
1
+ module Firebase
2
+ module Admin
3
+ module Messaging
4
+ # Options for features provided by the FCM SDK for iOS.
5
+ class APNSFCMOptions
6
+ # @return [String, nil]
7
+ # The label associated with the message's analytics data.
8
+ attr_accessor :analytics_label
9
+
10
+ # @return [String, nil]
11
+ # URL of an image to be displayed in the notification.
12
+ attr_accessor :image
13
+
14
+ # Initializes an {APNSFCMOptions}.
15
+ #
16
+ # @param [String, nil] analytics_label
17
+ # The label associated with the message's analytics data (optional).
18
+ # @param [String, nil] image
19
+ # URL of an image to be displayed in the notification (optional).
20
+ def initialize(analytics_label: nil, image: nil)
21
+ self.analytics_label = analytics_label
22
+ self.image = image
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ module Firebase
2
+ module Admin
3
+ module Messaging
4
+ # Represents the payload of an APNs message.
5
+ #
6
+ # Mainly consists of the `aps` dictionary. But may also contain other arbitrary custom keys.
7
+ #
8
+ class APNSPayload
9
+ # @return [APS] The aps instance to be included in the payload.
10
+ attr_accessor :aps
11
+
12
+ # @return [Hash] Custom fields to include in the payload.
13
+ attr_accessor :data
14
+
15
+ # Initializes a payload.
16
+ #
17
+ # @param [APS] aps
18
+ # An {APS} instance to be included in the payload.
19
+ # @param [Hash, nil] data
20
+ # Arbitrary keyword arguments to be included as custom fields in the payload.
21
+ def initialize(aps:, data: nil)
22
+ @aps = aps
23
+ @data = data
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,82 @@
1
+ module Firebase
2
+ module Admin
3
+ module Messaging
4
+ # Aps dictionary to be included in an APNS payload.
5
+ class APS
6
+ # @return [APSAlert, String, nil]
7
+ # Alert to be included in the message.
8
+ attr_accessor :alert
9
+
10
+ # @return [Integer, nil]
11
+ # Badge to be displayed with the message. Set to 0 to remove the badge. When not specified, the badge will
12
+ # remain unchanged.
13
+ attr_accessor :badge
14
+
15
+ # @return [String, CriticalSound, nil]
16
+ # Sound to be played with the message.
17
+ attr_accessor :sound
18
+
19
+ # @return [Boolean, nil]
20
+ # Specifies whether to configure a background update notification.
21
+ attr_accessor :content_available
22
+
23
+ # @return [Boolean, nil]
24
+ # Specifies whether to set the `mutable-content` property on the message so the clients can modify the
25
+ # notification via app extensions.
26
+ attr_accessor :mutable_content
27
+
28
+ # @return [String, nil]
29
+ # Type of the notification.
30
+ attr_accessor :category
31
+
32
+ # @return [String, nil]
33
+ # An app-specific identifier for grouping notifications.
34
+ attr_accessor :thread_id
35
+
36
+ # @return [Hash]
37
+ # App-specific custom fields.
38
+ attr_accessor :custom_data
39
+
40
+ # Initializes an {APS}.
41
+ #
42
+ # @param [APSAlert, String, nil] alert
43
+ # Alert to be included in the message (optional).
44
+ # @param [Integer, nil] badge
45
+ # Badge to be displayed with the message (optional).
46
+ # Set to 0 to remove the badge. When not specified, the badge will remain unchanged.
47
+ # @param [String, CriticalSound, nil] sound
48
+ # Sound to be played with the message (optional).
49
+ # @param [Boolean, nil] content_available
50
+ # Specifies whether to configure a background update notification (optional).
51
+ # @param [Boolean, nil] mutable_content
52
+ # Specifies whether to set the `mutable-content` property on the message so the clients can modify the
53
+ # notification via app extensions (optional).
54
+ # @param [String, nil] category
55
+ # Type of the notification (optional).
56
+ # @param [String, nil] thread_id
57
+ # An app-specific identifier for grouping notifications (optional).
58
+ # @param [Hash, nil] custom_data
59
+ # App-specific custom fields (optional).
60
+ def initialize(
61
+ alert: nil,
62
+ badge: nil,
63
+ sound: nil,
64
+ content_available: nil,
65
+ mutable_content: nil,
66
+ category: nil,
67
+ thread_id: nil,
68
+ custom_data: nil
69
+ )
70
+ self.alert = alert
71
+ self.badge = badge
72
+ self.sound = sound
73
+ self.content_available = content_available
74
+ self.mutable_content = mutable_content
75
+ self.category = category
76
+ self.thread_id = thread_id
77
+ self.custom_data = custom_data
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,110 @@
1
+ module Firebase
2
+ module Admin
3
+ module Messaging
4
+ # An alert that can be included in an {APS}.
5
+ class APSAlert
6
+ # @return [String, nil]
7
+ # Title of the alert. If specified, overrides the title set via {Notification}.
8
+ attr_accessor :title
9
+
10
+ # @return [String, nil]
11
+ # Subtitle of the alert.
12
+ attr_accessor :subtitle
13
+
14
+ # @return [String, nil]
15
+ # Body of the alert. If specified, overrides the body set via {Notification}.
16
+ attr_accessor :body
17
+
18
+ # @return [String, nil]
19
+ # Key of the body string in the app's string resources to use to localize the body text.
20
+ attr_accessor :loc_key
21
+
22
+ # @return [Array<String>, nil]
23
+ # A list of resource keys that will be used in place of the format specifiers in {loc_key}.
24
+ attr_accessor :loc_args
25
+
26
+ # @return [String, nil]
27
+ # Key of the title string in the app's string resources to use to localize the title text.
28
+ attr_accessor :title_loc_key
29
+
30
+ # @return [Array<String>, nil]
31
+ # A list of resource keys that will be used in place of the format specifiers in {title_loc_key}.
32
+ attr_accessor :title_loc_args
33
+
34
+ # @return [String, nil]
35
+ # Key of the subtitle string in the app's string resources to use to localize the subtitle text.
36
+ attr_accessor :subtitle_loc_key
37
+
38
+ # @return [Array<String>, nil]
39
+ # A list of resource keys that will be used in place of the format specifiers in {subtitle_loc_key}.
40
+ attr_accessor :subtitle_loc_args
41
+
42
+ # @return [String, nil]
43
+ # Key of the text in the app's string resources to use to localize the action button text.
44
+ attr_accessor :action_loc_key
45
+
46
+ # @return [String, nil]
47
+ # Image for the notification action.
48
+ attr_accessor :launch_image
49
+
50
+ # @return [Hash, nil]
51
+ # A Hash of custom key-value pairs to be included in the {APSAlert}
52
+ attr_accessor :custom_data
53
+
54
+ # Initializes an {APSAlert}.
55
+ #
56
+ # @param [String, nil] title
57
+ # Title of the alert (optional). If specified, overrides the title set via {Notification}.
58
+ # @param [String, nil] subtitle
59
+ # Subtitle of the alert (optional).
60
+ # @param [String, nil] body
61
+ # Body of the alert (optional). If specified, overrides the body set via {Notification}.
62
+ # @param [String, nil] loc_key
63
+ # Key of the body string in the app's string resources to use to localize the body text (optional).
64
+ # @param [Array<String>, nil] loc_args
65
+ # List of resource keys that will be used in place of the format specifiers in {loc_key} (optional).
66
+ # @param [String, nil] title_loc_key
67
+ # Key of the title string in the app's string resources to use to localize the title text (optional).
68
+ # @param [Array<String>, nil] title_loc_args
69
+ # List of resource keys that will be used in place of the format specifiers in {title_loc_key} (optional).
70
+ # @param [String, nil] subtitle_loc_key
71
+ # Key of the subtitle string in the app's string resources to use to localize the subtitle text (optional).
72
+ # @param [Array<String>, nil] subtitle_loc_args
73
+ # List of resource keys that will be used in place of the format specifiers in {subtitle_loc_key} (optional).
74
+ # @param [String, nil] action_loc_key
75
+ # Key of the text in the app's string resources to use to localize the action button text (optional).
76
+ # @param [String, nil] launch_image
77
+ # Image for the notification action (optional).
78
+ # @param [Hash, nil] custom_data
79
+ # A Hash of custom key-value pairs to be included in the {APSAlert} (optional).
80
+ def initialize(
81
+ title: nil,
82
+ subtitle: nil,
83
+ body: nil,
84
+ loc_key: nil,
85
+ loc_args: nil,
86
+ title_loc_key: nil,
87
+ title_loc_args: nil,
88
+ subtitle_loc_key: nil,
89
+ subtitle_loc_args: nil,
90
+ action_loc_key: nil,
91
+ launch_image: nil,
92
+ custom_data: nil
93
+ )
94
+ self.title = title
95
+ self.subtitle = subtitle
96
+ self.body = body
97
+ self.loc_key = loc_key
98
+ self.loc_args = loc_args
99
+ self.title_loc_key = title_loc_key
100
+ self.title_loc_args = title_loc_args
101
+ self.subtitle_loc_key = subtitle_loc_key
102
+ self.subtitle_loc_args = subtitle_loc_args
103
+ self.action_loc_key = action_loc_key
104
+ self.launch_image = launch_image
105
+ self.custom_data = custom_data
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,181 @@
1
+ module Firebase
2
+ module Admin
3
+ module Messaging
4
+ # A client for communicating with the Firebase Cloud Messaging service.
5
+ class Client
6
+ def initialize(app)
7
+ @project_id = app.project_id
8
+ @http_client = Firebase::Admin::Internal::HTTPClient.new(credentials: app.credentials)
9
+ @message_encoder = MessageEncoder.new
10
+ end
11
+
12
+ # Sends a message via Firebase Cloud Messaging (FCM).
13
+ #
14
+ # If the `dry_run` flag is set, the message will not be actually delivered to the recipients.
15
+ # Instead FCM performs all the usual validations, and emulates the send operation.
16
+ #
17
+ # @param [Message] message A message to send.
18
+ # @param [Boolean] dry_run A flag indicating whether to run the operation in dry run mode.
19
+ #
20
+ # @return [String] A message id that uniquely identifies the message.
21
+ def send_one(message, dry_run: false)
22
+ body = {
23
+ validate_only: dry_run,
24
+ message: @message_encoder.encode(message)
25
+ }
26
+ res = @http_client.post(send_url, body, FCM_HEADERS)
27
+ res.body["name"]
28
+ rescue Faraday::Error => e
29
+ raise parse_fcm_error(e)
30
+ end
31
+
32
+ # Sends the given list of messages via Firebase Cloud Messaging (FCM) as a single batch.
33
+ #
34
+ # If the `dry_run` flag is set, the messages will not be actually delivered to the recipients.
35
+ # Instead FCM performs all the usual validations, and emulates the send operation.
36
+ #
37
+ # @param [Array<Message>] messages An array of messages to send.
38
+ # @param [Boolean] dry_run A flag indicating whether to run the operation in dry run mode.
39
+ #
40
+ # @return [BatchResponse] A batch response.
41
+ def send_all(messages, dry_run: false)
42
+ raise NotImplementedError
43
+ end
44
+
45
+ # Sends the given multicast message to all tokens via Firebase Cloud Messaging (FCM).
46
+ #
47
+ # If the `dry_run` flag is set, the message will not be actually delivered to the recipients.
48
+ # Instead FCM performs all the usual validations, and emulates the send operation.
49
+ #
50
+ # @param [MulticastMessage] multicast_message A multicast message to send.
51
+ # @param [Boolean] dry_run A flag indicating whether to run the operation in dry run mode.
52
+ #
53
+ # @return [BatchResponse] A batch response.
54
+ def send_multicast(multicast_message, dry_run: false)
55
+ messages = multicast_message.tokens.map do |token|
56
+ Message.new(
57
+ token: token,
58
+ data: multicast_message.data,
59
+ notification: multicast_message.notification,
60
+ android: multicast_message.android,
61
+ apns: multicast_message.apns,
62
+ fcm_options: multicast_message.fcm_options
63
+ )
64
+ end
65
+ send_all(messages, dry_run: dry_run)
66
+ end
67
+
68
+ # Subscribes a list of registration tokens to an FCM topic.
69
+ #
70
+ # @param [Array<String>, String] tokens An array of device registration tokens (max 1000).
71
+ # @param [String] topic Name of the topic to subscribe to. May contain the `/topics` prefix.
72
+ #
73
+ # @return [TopicManagementResponse] A topic management response.
74
+ def subscribe_to_topic(tokens, topic)
75
+ make_topic_mgmt_request(tokens, topic, "batchAdd")
76
+ end
77
+
78
+ # Unsubscribes a list of registration tokens from an FCM topic.
79
+ #
80
+ # @param [Array<String>, String] tokens An array of device registration tokens (max 1000).
81
+ # @param [String] topic Name of the topic to unsubscribe from. May contain the `/topics` prefix.
82
+ #
83
+ # @return [TopicManagementResponse] A topic management response.
84
+ def unsubscribe_from_topic(tokens, topic)
85
+ make_topic_mgmt_request(tokens, topic, "batchRemove")
86
+ end
87
+
88
+ private
89
+
90
+ # @return [String] The firebase cloud messaging send endpoint url.
91
+ def send_url
92
+ "#{FCM_HOST}/v1/projects/#{@project_id}/messages:send"
93
+ end
94
+
95
+ # @return [String] The topic management endpoint url.
96
+ def topic_mgmt_url(operation)
97
+ "#{IID_HOST}/iid/v1:#{operation}"
98
+ end
99
+
100
+ def make_topic_mgmt_request(tokens, topic, operation)
101
+ tokens = [tokens] if tokens.is_a?(String)
102
+
103
+ unless tokens.is_a?(Array) && !tokens.empty?
104
+ raise ArgumentError, "tokens must be a string or non-empty array of strings."
105
+ end
106
+
107
+ unless tokens.all?(String)
108
+ raise ArgumentError, "tokens must be a non-empty array of strings."
109
+ end
110
+
111
+ unless topic.is_a?(String) && !topic.empty?
112
+ raise ArgumentError, "topic must be a non-empty string."
113
+ end
114
+
115
+ unless %w[batchAdd batchRemove].include?(operation)
116
+ raise ArgumentError, "operation is invalid"
117
+ end
118
+
119
+ uri = topic_mgmt_url(operation)
120
+ res = @http_client.post(uri, {
121
+ to: @message_encoder.sanitize_topic_name(topic, strip_prefix: false),
122
+ registration_tokens: tokens
123
+ }, IID_HEADERS)
124
+ TopicManagementResponse.new(res)
125
+ end
126
+
127
+ # @param [Faraday::Error] err
128
+ def parse_fcm_error(err)
129
+ msg, info = parse_platform_error(err.response_status, err.response_body)
130
+ return err if info.empty?
131
+
132
+ details = info["details"] || []
133
+ detail = details.find { |detail| detail["@type"] == "type.googleapis.com/google.firebase.fcm.v1.FcmError" }
134
+ return err unless detail.is_a?(Hash)
135
+
136
+ cls = FCM_ERROR_TYPES[detail["errorCode"] || ""] || Error
137
+ cls.new(msg, info)
138
+ rescue JSON::ParserError
139
+ Error.new("HTTP response is not json.", err.response)
140
+ end
141
+
142
+ # Parses an HTTP error response from a Google Cloud Platform API and extracts the error code
143
+ # and message fields.
144
+ #
145
+ # @param [Integer] status_code
146
+ # @param [String] body
147
+ # @return Array<String,Hash>
148
+ def parse_platform_error(status_code, body)
149
+ parsed = JSON.parse(body)
150
+ data = parsed.is_a?(Hash) ? parsed : {}
151
+ details = data.fetch("error", {})
152
+ msg = details.fetch("message", "Unexpected HTTP response with status #{status_code}; body: #{body}")
153
+ [msg, details]
154
+ end
155
+
156
+ FCM_HOST = "https://fcm.googleapis.com"
157
+ FCM_HEADERS = {"X-GOOG-API-FORMAT-VERSION": "2"}
158
+ IID_HOST = "https://iid.googleapis.com"
159
+ IID_HEADERS = {"access_token_auth" => "true"}
160
+
161
+ FCM_ERROR_TYPES = {
162
+ "APNS_AUTH_ERROR" => ThirdPartyAuthError,
163
+ "INVALID_ARGUMENT" => InvalidArgumentError,
164
+ "QUOTA_EXCEEDED" => QuotaExceededError,
165
+ "SENDER_ID_MISMATCH" => SenderIdMismatchError,
166
+ "THIRD_PARTY_AUTH_ERROR" => ThirdPartyAuthError,
167
+ "UNREGISTERED" => UnregisteredError,
168
+ "UNSPECIFIED_ERROR" => UnspecifiedError
169
+ }
170
+ end
171
+ end
172
+
173
+ class App
174
+ # Gets the Firebase Cloud Messaging client for this App.
175
+ # @return [Firebase::Admin::Messaging::Client]
176
+ def messaging
177
+ @messaging_client ||= Messaging::Client.new(self)
178
+ end
179
+ end
180
+ end
181
+ end