Skip to content

Commit e1cdd50

Browse files
authored
Update the Messaging testapp to use Functions (#1037)
* Update the Messaging testapp to use Functions * Update build_testapps.json
1 parent 1b1faae commit e1cdd50

File tree

5 files changed

+62
-159
lines changed

5 files changed

+62
-159
lines changed

messaging/testapp/Assets/Firebase/Sample/Messaging/UIHandlerAutomated.cs

Lines changed: 61 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
namespace 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) {

scripts/gha-encrypted/messaging/server_key.txt.gpg

Lines changed: 0 additions & 2 deletions
This file was deleted.

scripts/gha-encrypted/messaging/uri.txt.gpg

Lines changed: 0 additions & 1 deletion
This file was deleted.

scripts/gha/integration_testing/build_testapps.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@
169169
"testapp_path": "messaging/testapp",
170170
"platforms": ["Android", "Playmode", "iOS", "tvOS", "Windows", "macOS", "Linux"],
171171
"plugins": [
172+
"FirebaseFunctions.unitypackage",
172173
"FirebaseMessaging.unitypackage"
173174
],
174175
"provision": "Firebase_Unity_Messaging_Test_App_Dev.mobileprovision",

scripts/gha/restore_secrets.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,6 @@ def main(argv):
124124
file_path = os.path.join(repo_dir, "dynamic_links", "testapp", "Assets", "Firebase", "Sample", CAPITALIZATIONS["dynamic_links"], "UIHandler.cs")
125125
_patch_file(file_path, "REPLACE_WITH_YOUR_URI_PREFIX", uri_prefix)
126126

127-
print("Attempting to patch Messaging server key and uri.")
128-
server_key_path = os.path.join(secrets_dir, "messaging", "server_key.txt.gpg")
129-
server_key = _decrypt(server_key_path, passphrase)
130-
uri_path = os.path.join(secrets_dir, "messaging", "uri.txt.gpg")
131-
uri = _decrypt(uri_path, passphrase)
132-
file_path = os.path.join(repo_dir, "messaging", "testapp", "Assets", "Firebase", "Sample", CAPITALIZATIONS["messaging"], "UIHandlerAutomated.cs")
133-
_patch_file(file_path, "REPLACE_WITH_YOUR_SERVER_KEY", server_key)
134-
_patch_file(file_path, "REPLACE_WITH_YOUR_BACKEND_URL", uri)
135-
136127
print("Attempting to patch Storage Bucket.")
137128
bucket_path = os.path.join(secrets_dir, "storage", "bucket.txt.gpg")
138129
bucket = _decrypt(bucket_path, passphrase)

0 commit comments

Comments
 (0)