1
1
namespace Firebase . Sample . Messaging {
2
2
using Firebase . Extensions ;
3
+ using Firebase . Functions ;
3
4
using Firebase . Messaging ;
4
5
using System ;
5
6
using System . Collections ;
7
+ using System . Collections . Generic ;
6
8
using System . Text . RegularExpressions ;
7
9
using System . Threading . Tasks ;
8
10
using UnityEngine ;
@@ -12,8 +14,6 @@ public class UIHandlerAutomated : UIHandler {
12
14
private Firebase . Sample . AutomatedTestRunner testRunner ;
13
15
14
16
private const string TestTopic = "TestTopic" ;
15
- private const string ServerKey = "REPLACE_WITH_YOUR_SERVER_KEY" ;
16
- private const string FirebaseBackendUrl = "https://fcm.googleapis.com/fcm/send" ;
17
17
18
18
private const string MessageFoo = "This is a test message" ;
19
19
private const string MessageBar = "It contains some data" ;
@@ -22,14 +22,15 @@ public class UIHandlerAutomated : UIHandler {
22
22
23
23
private const string MessageNotificationTitle = "JSON message!" ;
24
24
private const string MessageNotificationBody = "This notification has a body!" ;
25
- private const string JsonMessageNotification = "\" notification\" :{\" title\" :\" " +
26
- MessageNotificationTitle + "\" ,\" body\" :\" " + MessageNotificationBody + "\" }" ;
27
25
28
- private const string PlaintextMessage = "data.foo=" + MessageFoo + "&data.bar=" + MessageBar ;
29
- private const string JsonMessageA = "{\" data\" :{\" spam\" :\" " + MessageSpam + "\" , " +
30
- "\" eggs\" :\" " + MessageEggs + "\" }," + JsonMessageNotification + "}" ;
31
- private const string JsonMessageB = "{\" data\" :{\" foo\" :\" " + MessageFoo + "\" , " +
32
- "\" bar\" :\" " + MessageBar + "\" }," + JsonMessageNotification + "}" ;
26
+ private static readonly Dictionary < string , object > TokenMessageFields = new Dictionary < string , object > {
27
+ { "spam" , MessageSpam } ,
28
+ { "eggs" , MessageEggs }
29
+ } ;
30
+ private static readonly Dictionary < string , object > TopicMessageFields = new Dictionary < string , object > {
31
+ { "foo" , MessageFoo } ,
32
+ { "bar" , MessageBar }
33
+ } ;
33
34
34
35
private string registrationToken ;
35
36
private FirebaseMessage lastReceivedMessage ;
@@ -66,10 +67,6 @@ protected override void Start() {
66
67
// Disable these tests on desktop, as desktop uses a stub implementation.
67
68
#if ( UNITY_IOS || UNITY_TVOS || UNITY_ANDROID )
68
69
TestGetRegistrationToken ,
69
- #if ! ( UNITY_IOS || UNITY_TVOS )
70
- // TODO(b/130674454) This test times out on iOS, disabling until fixed.
71
- MakeTest ( TestSendPlaintextMessageToDevice ) ,
72
- #endif // !(UNITY_IOS || UNITY_TVOS)
73
70
MakeTest ( TestSendJsonMessageToDevice ) ,
74
71
MakeTest ( TestSendJsonMessageToSubscribedTopic ) ,
75
72
#else // (UNITY_IOS || UNITY_TVOS || UNITY_ANDROID)
@@ -85,10 +82,6 @@ protected override void Start() {
85
82
// Disable these tests on desktop, as desktop uses a stub implementation.
86
83
#if ( UNITY_IOS || UNITY_TVOS || UNITY_ANDROID )
87
84
"TestGetRegistrationToken" ,
88
- #if ! ( UNITY_IOS || UNITY_TVOS )
89
- // TODO(b/130674454) This test times out on iOS, disabling until fixed.
90
- "TestSendPlaintextMessageToDevice" ,
91
- #endif // !(UNITY_IOS || UNITY_TVOS)
92
85
"TestSendJsonMessageToDevice" ,
93
86
"TestSendJsonMessageToSubscribedTopic" ,
94
87
#else // #if (UNITY_IOS || UNITY_TVOS || UNITY_ANDROID)
@@ -165,51 +158,54 @@ void ThrowIfMissingRegistrationToken() {
165
158
}
166
159
}
167
160
168
- // Sends a plaintext message to the server, setting this device as the addressee, waits until the
169
- // app receives the message and verifies the contents are the same as were sent.
170
- IEnumerator TestSendPlaintextMessageToDevice ( TaskCompletionSource < string > tcs ) {
171
- ThrowIfMissingRegistrationToken ( ) ;
172
- SendPlaintextMessageToDeviceAsync ( PlaintextMessage , registrationToken ) ;
173
- // TODO(b/65218400): check message id.
174
- while ( lastReceivedMessage == null ) {
175
- yield return new WaitForSeconds ( 0.5f ) ;
176
- }
177
- ValidatePlaintextMessage ( tcs , lastReceivedMessage ) ;
178
- lastReceivedMessage = null ;
179
- }
180
-
181
161
// Sends a JSON message to the server, setting this device as the addressee, waits until the app
182
162
// receives the message and verifies the contents are the same as were sent.
183
163
IEnumerator TestSendJsonMessageToDevice ( TaskCompletionSource < string > tcs ) {
184
164
ThrowIfMissingRegistrationToken ( ) ;
185
- SendJsonMessageToDeviceAsync ( JsonMessageA , registrationToken ) ;
165
+ bool failedToSend = false ;
166
+ SendMessageToDeviceAsync ( registrationToken ) . ContinueWithOnMainThread ( t => {
167
+ if ( t . IsFaulted ) {
168
+ tcs . TrySetException ( t . Exception ) ;
169
+ failedToSend = true ;
170
+ }
171
+ } ) ;
186
172
// TODO(b/65218400): check message id.
187
- while ( lastReceivedMessage == null ) {
173
+ while ( lastReceivedMessage == null && ! failedToSend ) {
188
174
yield return new WaitForSeconds ( 0.5f ) ;
189
175
}
190
- ValidateJsonMessageA ( tcs , lastReceivedMessage ) ;
191
- lastReceivedMessage = null ;
176
+ if ( lastReceivedMessage != null ) {
177
+ ValidateJsonMessageA ( tcs , lastReceivedMessage ) ;
178
+ lastReceivedMessage = null ;
179
+ }
192
180
}
193
181
194
182
// Sends a JSON message to the server, specifying a topic to which this device is subscribed,
195
183
// waits until the app receives the message and verifies the contents are the same as were sent.
196
184
IEnumerator TestSendJsonMessageToSubscribedTopic ( TaskCompletionSource < string > tcs ) {
197
185
ThrowIfMissingRegistrationToken ( ) ;
186
+ bool failedToSend = false ;
198
187
// Note: Ideally this would use a more unique topic, but topic creation and subscription
199
188
// takes additional time, so instead this only subscribes during this one test, and doesn't
200
189
// fully test unsubscribing.
201
190
Firebase . Messaging . FirebaseMessaging . SubscribeAsync ( TestTopic ) . ContinueWithOnMainThread ( t => {
202
- SendJsonMessageToTopicAsync ( JsonMessageB , TestTopic ) ;
191
+ SendMessageToTopicAsync ( TestTopic ) . ContinueWithOnMainThread ( t2 => {
192
+ if ( t2 . IsFaulted ) {
193
+ tcs . TrySetException ( t2 . Exception ) ;
194
+ failedToSend = true ;
195
+ }
196
+ } ) ;
203
197
} ) ;
204
198
// TODO(b/65218400): check message id.
205
- while ( lastReceivedMessage == null ) {
199
+ while ( lastReceivedMessage == null && ! failedToSend ) {
206
200
yield return new WaitForSeconds ( 0.5f ) ;
207
201
}
208
- // Unsubscribe from the test topic, to make sure that other messages aren't received.
209
- Firebase . Messaging . FirebaseMessaging . UnsubscribeAsync ( TestTopic ) . ContinueWithOnMainThread ( t => {
210
- ValidateJsonMessageB ( tcs , lastReceivedMessage ) ;
211
- lastReceivedMessage = null ;
212
- } ) ;
202
+ if ( lastReceivedMessage != null ) {
203
+ // Unsubscribe from the test topic, to make sure that other messages aren't received.
204
+ Firebase . Messaging . FirebaseMessaging . UnsubscribeAsync ( TestTopic ) . ContinueWithOnMainThread ( t => {
205
+ ValidateJsonMessageB ( tcs , lastReceivedMessage ) ;
206
+ lastReceivedMessage = null ;
207
+ } ) ;
208
+ }
213
209
}
214
210
215
211
// Fake test (always passes immediately). Can be used on platforms with no other tests.
@@ -234,114 +230,32 @@ IEnumerator TestDeleteTokenAsync(TaskCompletionSource<string> tcs) {
234
230
yield break ;
235
231
}
236
232
237
- // Sends the given message to targetDevice in plaintext format and gives back the message id iff
238
- // the message was sent successfully.
239
- Task < string > SendPlaintextMessageToDeviceAsync ( string message , string targetDevice ) {
240
- var payload = "registration_id=" + targetDevice + "&" + message ;
241
- var request = CreateSendMessageRequest ( payload ) ;
242
- // Though Firebase docs state that if content type is not specified, it defaults to plaintext,
243
- // server actually returns an error without the following line. This likely has something to do
244
- // with the way Unity formats the request.
245
- request . SetRequestHeader ( "Content-Type" , "application/x-www-form-urlencoded;charset=UTF-8" ) ;
246
- return DeliverMessageAsync ( request ) ;
247
- }
248
-
249
- // Sends the given message to targetDevice in JSON format and gives back the message id iff the
250
- // message was sent successfully.
251
- Task < string > SendJsonMessageToDeviceAsync ( string message , string targetDevice ) {
252
- var payload = AddTargetToJsonMessage ( message , targetDevice ) ;
253
- var request = CreateSendMessageRequest ( payload ) ;
254
- request . SetRequestHeader ( "Content-Type" , "application/json" ) ;
255
- return DeliverMessageAsync ( request ) ;
256
- }
257
-
258
- // Sends the given message to the topic in JSON format and gives back the message id iff the
259
- // message was sent successfully.
260
- Task < string > SendJsonMessageToTopicAsync ( string message , string topic ) {
261
- var payload = AddTargetToJsonMessage ( message , "/topics/" + topic ) ;
262
- var request = CreateSendMessageRequest ( payload ) ;
263
- request . SetRequestHeader ( "Content-Type" , "application/json" ) ;
264
- return DeliverMessageAsync ( request ) ;
265
- }
266
-
267
- // Inserts "to" field into the given JSON string.
268
- string AddTargetToJsonMessage ( string message , string target ) {
269
- return message . Insert ( message . IndexOf ( '{' ) + 1 , "\" to\" :\" " + target + "\" , " ) ;
233
+ // Sends a message to the specified target device, using Cloud Functions.
234
+ // This relies on the sendMessage function that is defined in the C++ repo.
235
+ Task < HttpsCallableResult > SendMessageToDeviceAsync ( string targetDevice ) {
236
+ Dictionary < string , object > data = new Dictionary < string , object > ( ) ;
237
+ data [ "sendTo" ] = targetDevice ;
238
+ data [ "isToken" ] = true ;
239
+ data [ "notificationTitle" ] = MessageNotificationTitle ;
240
+ data [ "notificationBody" ] = MessageNotificationBody ;
241
+ data [ "messageFields" ] = TokenMessageFields ;
242
+
243
+ var callable = FirebaseFunctions . DefaultInstance . GetHttpsCallable ( "sendMessage" ) ;
244
+ DebugLog ( "Calling the Cloud Function to send a targeted message" ) ;
245
+ return callable . CallAsync ( data ) ;
270
246
}
271
247
272
- // Creates a POST request to FCM server with proper authentication.
273
- UnityWebRequest CreateSendMessageRequest ( string message ) {
274
- // UnityWebRequest.Post unavoidably applies URL encoding to the payload, which leads to Firebase
275
- // server rejecting the resulting garbled JSON. Unfortunately, there is no way to turn it off.
276
- // The workaround is instead to create a PUT request instead (which is not encoded) and then
277
- // change method to POST.
278
- // See this discussion for reference:
279
- // https://forum.unity3d.com/threads/unitywebrequest-post-url-jsondata-sending-broken-json.414708/#post-2719900
280
- var request = UnityWebRequest . Put ( FirebaseBackendUrl , message ) ;
281
- request . method = "POST" ;
282
-
283
- request . SetRequestHeader ( "Authorization" , String . Format ( "key={0}" , ServerKey ) ) ;
284
-
285
- return request ;
286
- }
287
-
288
- Task < string > DeliverMessageAsync ( UnityWebRequest request ) {
289
- var tcs = new TaskCompletionSource < string > ( ) ;
290
- StartCoroutine ( DeliverMessageCoroutine ( request , tcs ) ) ;
291
- return tcs . Task ;
292
- }
293
-
294
- // Sends the given POST request and gives back the message id iff the message was sent
295
- // successfully.
296
- IEnumerator DeliverMessageCoroutine ( UnityWebRequest request , TaskCompletionSource < string > tcs ) {
297
- yield return request . Send ( ) ;
298
-
299
- #if UNITY_5
300
- if ( request . isError ) {
301
- #else
302
- // After Unity 2017, the UnityWebRequest API changed isError property to isNetworkError for
303
- // system errors, while isHttpError and responseCode is used for server return code such as
304
- // 404/Not Found and 500/Internal Server Error.
305
- if ( request . isNetworkError ) {
306
- #endif
307
- DebugLog ( "The server responded with an error: " + request . error ) ;
308
- tcs . TrySetException ( new Exception ( request . error ) ) ;
309
- }
310
-
311
- DebugLog ( "Server response code: " + request . responseCode . ToString ( ) ) ;
312
- DebugLog ( "Server response contents: " + request . downloadHandler . text ) ;
313
-
314
- // Extract message ID from server response. Unfortunately, there are 3 possible response
315
- // formats.
316
- var messageIdCaptureGroup = "([0-9a-f:%]+)" ;
317
- // JSON format
318
- var messageIdMatch = Regex . Match ( request . downloadHandler . text , "\" message_id\" :\" " +
319
- messageIdCaptureGroup + "\" " ) ;
320
- // When sending to a topic, a different response format is used, try that.
321
- if ( ! messageIdMatch . Success ) {
322
- messageIdMatch = Regex . Match ( request . downloadHandler . text , "\" message_id\" :" +
323
- messageIdCaptureGroup ) ;
324
- }
325
- if ( ! messageIdMatch . Success ) {
326
- // Try plaintext format
327
- messageIdMatch = Regex . Match ( request . downloadHandler . text , "id=" + messageIdCaptureGroup ) ;
328
- }
329
- if ( messageIdMatch . Success ) {
330
- tcs . TrySetResult ( messageIdMatch . Groups [ 1 ] . Value ) ;
331
- } else {
332
- tcs . TrySetException ( new Exception ( "Server response doesn't contain message id: " +
333
- request . downloadHandler . text ) ) ;
334
- }
335
- }
336
-
337
- void ValidatePlaintextMessage ( TaskCompletionSource < string > tcs , FirebaseMessage message ) {
338
- try {
339
- ValidateMessageData ( message , "foo" , MessageFoo ) ;
340
- ValidateMessageData ( message , "bar" , MessageBar ) ;
341
- tcs . SetResult ( message . MessageId ) ;
342
- } catch ( Exception e ) {
343
- tcs . SetException ( e ) ;
344
- }
248
+ Task < HttpsCallableResult > SendMessageToTopicAsync ( string topic ) {
249
+ Dictionary < string , object > data = new Dictionary < string , object > ( ) ;
250
+ data [ "sendTo" ] = topic ;
251
+ data [ "isToken" ] = false ;
252
+ data [ "notificationTitle" ] = MessageNotificationTitle ;
253
+ data [ "notificationBody" ] = MessageNotificationBody ;
254
+ data [ "messageFields" ] = TopicMessageFields ;
255
+
256
+ var callable = FirebaseFunctions . DefaultInstance . GetHttpsCallable ( "sendMessage" ) ;
257
+ DebugLog ( "Calling the Cloud Function to send a topic message" ) ;
258
+ return callable . CallAsync ( data ) ;
345
259
}
346
260
347
261
void ValidateJsonMessageA ( TaskCompletionSource < string > tcs , FirebaseMessage message ) {
0 commit comments