简体   繁体   中英

C# Firebase Cloud Message send multiple Web JS notifications using Topic messaging

I'm trying to send multiple web notifications using topic instead of looping send notification to each token.

But I stuck at sending notification to topic using Credential with 401 unauthorized

Here is my code

Firstly, I request web browser permission for notification token via this Firebase sample code: https://github.com/firebase/quickstart-js/blob/master/messaging

On index.html I change code like this to send token to server for subscribing to topic

    function resetUI() {
        clearMessages();

        // [START get_token]
        // Get Instance ID token. Initially this makes a network call, once retrieved
        // subsequent calls to getToken will return from cache.
        messaging.getToken().then(function (currentToken) {
            if (currentToken) {
                console.log("Get Token OK: " + currentToken);
                sendTokenToServer(currentToken);
            } else {
                // Show permission request.
                console.log('No Instance ID token available. Request permission to generate one.');
                // Reset Token on Server to False
                setTokenSentToServer(false);

                // Request permission for Token
                requestPermission();
            }
        }).catch(function (err) {
            console.log('An error occurred while retrieving token. ', err);

            setTokenSentToServer(false);
        });
        // [END get_token]
    }


    // Send the Instance ID token your application server, so that it can:
    // - send messages back to this app
    // - subscribe/unsubscribe the token from topics
    function sendTokenToServer(currentToken) {
        if (!(window.localStorage.getItem('EDXNotificationIsSentToServer') === '1')) {
            console.log('Sending token to server...');

            var tokenObject = {
                token: currentToken
            };

            $.ajax({
                type: "POST",
                url: '@Url.Action("SaveToken", "Api")',
                data: JSON.stringify(tokenObject),
                contentType: "application/json; charset=utf-8",
                success: function (data) {
                    if (data) {
                        // TODO(developer): Send the current token to your server.
                        setTokenSentToServer(true);
                    }
                },
                complete: function (status) {
                },
                error: function (error) { }
            });

        } else {
            console.log('Token already sent to server so won\'t send it again unless it changes');
        }

    }

On server, I stored token to database and subscribe it to test topic

    public class ApiController : Controller
    {
        private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// Lưu token
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public JsonResult SaveToken(TBL_Customer_Tokens model)
        {
            try
            {
                if (!string.IsNullOrEmpty(model.token))
                {
                    bool ret = true;

                    if(GCMUtils.SubcribeTokenToTopic(model.token, ConfigurationManager.AppSettings["GCMTopic"]))
                        ret = DataServiceFactory.GetCustomerService().SaveToken(model.token); // Save token to database, don't care about this function. It is ok

                    if (ret)
                        Json(true, JsonRequestBehavior.AllowGet);
                }
            }
            catch (Exception ex)
            {
                log.Debug(ex.Message, ex);
            }

            return Json(false, JsonRequestBehavior.AllowGet);
        }
    }

Here is GCMUtils

    public class GCMUtils
    {
        private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);


        public static bool SubcribeTokenToTopic(string token, string topic)
        {
            try
            {
                string url = String.Format("https://iid.googleapis.com/iid/v1/{0}/rel/topics/{1}", token, topic);

                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);

                req.ContentType = "application/json";
                req.Headers.Add("Authorization", "key=" + ConfigurationManager.AppSettings["GCMServerKey"]);


                req.Method = "POST";
                req.ContentLength = 0;

                Stream reqStream = req.GetRequestStream();
                reqStream.Close();

                HttpWebResponse response = (HttpWebResponse)req.GetResponse();

                if (response.StatusCode == HttpStatusCode.OK)
                    return true;
            }
            catch (Exception ex)
            {
                log.Debug(ex.Message, ex);
            }

            return false;
        }
    }

Token is retrieved, saved to database and successfully subscribed to topic. So you may ignore above code , analyze only problem belows

Secondly, I'm trying to send web notification to Topic instead of sending single notification to each token https://firebase.google.com/docs/cloud-messaging/js/topic-messaging

    public static bool SendFirebaseTopicNotificationAsync(string topicSubscription, NotificationPayload dat)
    {
        try
        {
            List<string> scopes = new List<string>();
            scopes.Add("https://www.googleapis.com/auth/firebase");
            scopes.Add("https://www.googleapis.com/auth/firebase.messaging");

            string accesstoken = GoogleCredential.FromFile(ConfigurationManager.AppSettings["GoogleCredentialFilePath"])
                                                        .CreateScoped(scopes)
                                                        .UnderlyingCredential
                                                        .GetAccessTokenForRequestAsync().Result;

            WebRequest tRequest = WebRequest.Create("https://fcm.googleapis.com/v1/projects/test-project/messages:send");
            tRequest.Method = "post";


            tRequest.Headers.Add("Authorization", "bearer " + accesstoken);
            tRequest.ContentType = "application/json";

            var payload = new
            {
                message = new
                {
                    topic = topicSubscription,
                    notification = new {
                        title = dat.title,
                        body = dat.body
                    },
                    webpush = new
                    {
                        fcm_options = new {
                            link = dat.click_action
                        }
                    }
                }
            };

            string postbody = JsonConvert.SerializeObject(payload).ToString();
            Byte[] byteArray = Encoding.UTF8.GetBytes(postbody);
            tRequest.ContentLength = byteArray.Length;
            using (Stream dataStream = tRequest.GetRequestStream())
            {
                dataStream.Write(byteArray, 0, byteArray.Length);
                using (WebResponse tResponse = tRequest.GetResponse())
                {
                    using (Stream dataStreamResponse = tResponse.GetResponseStream())
                    {
                        if (dataStreamResponse != null) using (StreamReader tReader = new StreamReader(dataStreamResponse))
                            {
                                String sResponseFromServer = tReader.ReadToEnd();
                                Console.WriteLine(sResponseFromServer);

                                return true;
                            }
                    }
                }
            }
        }
        catch (Exception ex)
        {
            log.Debug(ex.Message, ex);
        }

        return false;
    }

Note that test-project in SendFirebaseTopicNotificationAsync is project name. I'm sure it is true

ConfigurationManager.AppSettings["GoogleCredentialFilePath"] is absolute file path to .json key file which I'm following this guideline to retrieve https://www.c-sharpcorner.com/article/retrieve-access-token-for-google-service-account-form-json-or-p12-key-in-c-sharp/

But I get 401 Unauthorized error. It seems access token from GoogleCredential is wrong

Any helps will be appreciated

Thank you

Stupid mistake here is this line

    tRequest.Headers.Add("Authorization", "bearer " + accesstoken);

bearer is wrong, use Bearer instead

    tRequest.Headers.Add("Authorization", "Bearer " + accesstoken);

I also found this article https://github.com/googleapis/google-api-dotnet-client/issues/1140

This guys also had 401 Unauthorized too. But his problem differs from me. He didn't enable FCM API in Google Cloud Platform https://console.cloud.google.com/apis/dashboard

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM