firebase-admin-sdk 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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