11namespace Firebase . Sample . Messaging {
22 using Firebase . Extensions ;
3+ using Firebase . Functions ;
34 using Firebase . Messaging ;
45 using System ;
56 using System . Collections ;
7+ using System . Collections . Generic ;
68 using System . Text . RegularExpressions ;
79 using System . Threading . Tasks ;
810 using UnityEngine ;
@@ -12,8 +14,6 @@ public class UIHandlerAutomated : UIHandler {
1214 private Firebase . Sample . AutomatedTestRunner testRunner ;
1315
1416 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" ;
1717
1818 private const string MessageFoo = "This is a test message" ;
1919 private const string MessageBar = "It contains some data" ;
@@ -22,14 +22,15 @@ public class UIHandlerAutomated : UIHandler {
2222
2323 private const string MessageNotificationTitle = "JSON message!" ;
2424 private const string MessageNotificationBody = "This notification has a body!" ;
25- private const string JsonMessageNotification = "\" notification\" :{\" title\" :\" " +
26- MessageNotificationTitle + "\" ,\" body\" :\" " + MessageNotificationBody + "\" }" ;
2725
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+ } ;
3334
3435 private string registrationToken ;
3536 private FirebaseMessage lastReceivedMessage ;
@@ -66,10 +67,6 @@ protected override void Start() {
6667 // Disable these tests on desktop, as desktop uses a stub implementation.
6768#if ( UNITY_IOS || UNITY_TVOS || UNITY_ANDROID )
6869 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)
7370 MakeTest ( TestSendJsonMessageToDevice ) ,
7471 MakeTest ( TestSendJsonMessageToSubscribedTopic ) ,
7572#else // (UNITY_IOS || UNITY_TVOS || UNITY_ANDROID)
@@ -85,10 +82,6 @@ protected override void Start() {
8582 // Disable these tests on desktop, as desktop uses a stub implementation.
8683#if ( UNITY_IOS || UNITY_TVOS || UNITY_ANDROID )
8784 "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)
9285 "TestSendJsonMessageToDevice" ,
9386 "TestSendJsonMessageToSubscribedTopic" ,
9487#else // #if (UNITY_IOS || UNITY_TVOS || UNITY_ANDROID)
@@ -165,51 +158,54 @@ void ThrowIfMissingRegistrationToken() {
165158 }
166159 }
167160
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-
181161 // Sends a JSON message to the server, setting this device as the addressee, waits until the app
182162 // receives the message and verifies the contents are the same as were sent.
183163 IEnumerator TestSendJsonMessageToDevice ( TaskCompletionSource < string > tcs ) {
184164 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+ } ) ;
186172 // TODO(b/65218400): check message id.
187- while ( lastReceivedMessage == null ) {
173+ while ( lastReceivedMessage == null && ! failedToSend ) {
188174 yield return new WaitForSeconds ( 0.5f ) ;
189175 }
190- ValidateJsonMessageA ( tcs , lastReceivedMessage ) ;
191- lastReceivedMessage = null ;
176+ if ( lastReceivedMessage != null ) {
177+ ValidateJsonMessageA ( tcs , lastReceivedMessage ) ;
178+ lastReceivedMessage = null ;
179+ }
192180 }
193181
194182 // Sends a JSON message to the server, specifying a topic to which this device is subscribed,
195183 // waits until the app receives the message and verifies the contents are the same as were sent.
196184 IEnumerator TestSendJsonMessageToSubscribedTopic ( TaskCompletionSource < string > tcs ) {
197185 ThrowIfMissingRegistrationToken ( ) ;
186+ bool failedToSend = false ;
198187 // Note: Ideally this would use a more unique topic, but topic creation and subscription
199188 // takes additional time, so instead this only subscribes during this one test, and doesn't
200189 // fully test unsubscribing.
201190 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+ } ) ;
203197 } ) ;
204198 // TODO(b/65218400): check message id.
205- while ( lastReceivedMessage == null ) {
199+ while ( lastReceivedMessage == null && ! failedToSend ) {
206200 yield return new WaitForSeconds ( 0.5f ) ;
207201 }
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+ }
213209 }
214210
215211 // Fake test (always passes immediately). Can be used on platforms with no other tests.
@@ -234,114 +230,32 @@ IEnumerator TestDeleteTokenAsync(TaskCompletionSource<string> tcs) {
234230 yield break ;
235231 }
236232
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 ) ;
270246 }
271247
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 ) ;
345259 }
346260
347261 void ValidateJsonMessageA ( TaskCompletionSource < string > tcs , FirebaseMessage message ) {
0 commit comments