/// <summary>
        /// When marking requests where you want to define VAPID details, call this method
        /// before sendNotifications() or pass in the details and options to
        /// sendNotification.
        /// </summary>
        /// <param name="vapidDetails"></param>
        public void SetVapidDetails(VapidDetails vapidDetails)
        {
            VapidHelper.ValidateSubject(vapidDetails.Subject);
            VapidHelper.ValidatePublicKey(vapidDetails.PublicKey);
            VapidHelper.ValidatePrivateKey(vapidDetails.PrivateKey);

            _vapidDetails = vapidDetails;
        }
        /// <summary>
        /// To get a request without sending a push notification call this method.
        ///
        /// This method will throw an ArgumentException if there is an issue with the input.
        /// </summary>
        /// <param name="subscription">The PushSubscription you wish to send the notification to.</param>
        /// <param name="payload">The payload you wish to send to the user</param>
        /// <param name="options">Options for the GCM API key and vapid keys can be passed in if they are unique for each notification.</param>
        /// <returns>A HttpRequestMessage object that can be sent.</returns>
        public HttpRequestMessage GenerateRequestDetails(PushSubscription subscription, string payload, Dictionary <string, object> options = null)
        {
            if (!Uri.IsWellFormedUriString(subscription.Endpoint, UriKind.Absolute))
            {
                throw new ArgumentException(@"You must pass in a subscription with at least a valid endpoint");
            }

            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, subscription.Endpoint);

            if (!String.IsNullOrEmpty(payload) && (String.IsNullOrEmpty(subscription.Auth) || String.IsNullOrEmpty(subscription.P256DH)))
            {
                throw new ArgumentException(@"To send a message with a payload, the subscription must have 'auth' and 'p256dh' keys.");
            }

            string       currentGCMAPiKey            = _gcmAPIKey;
            VapidDetails currentVapidDetails         = _vapidDetails;
            int          timeToLive                  = DefaultTtl;
            Dictionary <string, object> extraHeaders = new Dictionary <string, object>();

            if (options != null)
            {
                List <string> validOptionsKeys = new List <string> {
                    "headers", "gcmAPIKey", "vapidDetails", "TTL"
                };
                foreach (string key in options.Keys)
                {
                    if (!validOptionsKeys.Contains(key))
                    {
                        throw new ArgumentException(key + " is an invalid options. The valid options are" + String.Join(",", validOptionsKeys));
                    }
                }


                if (options.ContainsKey("headers"))
                {
                    Dictionary <string, object> headers = options["headers"] as Dictionary <string, object>;
                    if (headers == null)
                    {
                        throw new ArgumentException("options.headers must be of type Dictionary<string,object>");
                    }

                    extraHeaders = headers;
                }

                if (options.ContainsKey("gcmAPIKey"))
                {
                    string gcmAPIKey = options["gcmAPIKey"] as string;
                    if (gcmAPIKey == null)
                    {
                        throw new ArgumentException("options.gcmAPIKey must be of type string");
                    }

                    currentGCMAPiKey = gcmAPIKey;
                }

                if (options.ContainsKey("vapidDetails"))
                {
                    VapidDetails vapidDetails = options["vapidDetails"] as VapidDetails;
                    if (vapidDetails == null)
                    {
                        throw new ArgumentException("options.vapidDetails must be of type VapidDetails");
                    }

                    currentVapidDetails = vapidDetails;
                }

                if (options.ContainsKey("TTL"))
                {
                    int?ttl = options["TTL"] as int?;
                    if (ttl == null)
                    {
                        throw new ArgumentException("options.TTL must be of type int");
                    }

                    //at this stage ttl cannot be null.
                    timeToLive = (int)ttl;
                }
            }

            string cryptoKeyHeader = null;

            request.Headers.Add("TTL", timeToLive.ToString());

            foreach (KeyValuePair <string, object> header in extraHeaders)
            {
                request.Headers.Add(header.Key, header.Value.ToString());
            }

            if (!String.IsNullOrEmpty(payload))
            {
                if (String.IsNullOrEmpty(subscription.P256DH) || String.IsNullOrEmpty(subscription.Auth))
                {
                    throw new ArgumentException(@"Unable to send a message with payload to this subscription since it doesn't have the required encryption key");
                }

                EncryptionResult encryptedPayload = Encryptor.Encrypt(subscription.P256DH, subscription.Auth, payload);

                request.Content = new ByteArrayContent(encryptedPayload.Payload);
                request.Content.Headers.ContentType   = new MediaTypeHeaderValue("application/octet-stream");
                request.Content.Headers.ContentLength = encryptedPayload.Payload.Length;
                request.Content.Headers.ContentEncoding.Add("aesgcm");
                request.Headers.Add("Encryption", "salt=" + encryptedPayload.Base64EncodeSalt());
                cryptoKeyHeader = @"dh=" + encryptedPayload.Base64EncodePublicKey();
            }
            else
            {
                request.Content = new ByteArrayContent(new byte[0]);
                request.Content.Headers.ContentLength = 0;
            }

            bool isGCM = subscription.Endpoint.StartsWith(@"https://android.googleapis.com/gcm/send");

            if (isGCM)
            {
                if (!String.IsNullOrEmpty(currentGCMAPiKey))
                {
                    request.Headers.TryAddWithoutValidation("Authorization", "key=" + currentGCMAPiKey);
                }
            }
            else if (currentVapidDetails != null)
            {
                Uri    uri      = new Uri(subscription.Endpoint);
                string audience = uri.Scheme + "://" + uri.Host;
                Dictionary <string, string> vapidHeaders = VapidHelper.GetVapidHeaders(audience, currentVapidDetails.Subject, currentVapidDetails.PublicKey, currentVapidDetails.PrivateKey);
                //request.Headers.Add(@"Authorization", vapidHeaders["Authorization"]);
                request.Headers.Authorization = new AuthenticationHeaderValue(vapidHeaders["Authorization"].Split(" ")[0], vapidHeaders["Authorization"].Split(" ")[1]);
                if (String.IsNullOrEmpty(cryptoKeyHeader))
                {
                    cryptoKeyHeader = vapidHeaders["Crypto-Key"];
                }
                else
                {
                    cryptoKeyHeader += @";" + vapidHeaders["Crypto-Key"];
                }
            }

            request.Headers.Add("Crypto-Key", cryptoKeyHeader);
            return(request);
        }
Example #3
0
        /// <summary>
        ///     To get a request without sending a push notification call this method.
        ///     This method will throw an ArgumentException if there is an issue with the input.
        /// </summary>
        /// <param name="subscription">The PushSubscription you wish to send the notification to.</param>
        /// <param name="payload">The payload you wish to send to the user</param>
        /// <param name="options">
        ///     Options for the GCM API key and vapid keys can be passed in if they are unique for each
        ///     notification.
        /// </param>
        /// <returns>A HttpRequestMessage object that can be sent.</returns>
        public HttpRequestMessage GenerateRequestDetails(
            PushSubscription subscription,
            string payload,
            Dictionary <string, object> options = null,
            string googleFirebaseAppId          = null,
            string googleFirebaseSenderId       = null)
        {
            if (!Uri.IsWellFormedUriString(subscription.Endpoint, UriKind.Absolute))
            {
                throw new ArgumentException(@"You must pass in a subscription with at least a valid endpoint");
            }

            var request = new HttpRequestMessage(HttpMethod.Post, subscription.Endpoint);

            if (!string.IsNullOrEmpty(payload) && (string.IsNullOrEmpty(subscription.Auth) ||
                                                   string.IsNullOrEmpty(subscription.P256DH)))
            {
                throw new ArgumentException(
                          @"To send a message with a payload, the subscription must have 'auth' and 'p256dh' keys.");
            }

            var currentGcmApiKey    = _gcmApiKey;
            var currentVapidDetails = _vapidDetails;
            var timeToLive          = DefaultTtl;
            var extraHeaders        = new Dictionary <string, object>();

            if (options != null)
            {
                var validOptionsKeys = new List <string> {
                    "headers", "gcmAPIKey", "vapidDetails", "TTL"
                };
                foreach (var key in options.Keys)
                {
                    if (!validOptionsKeys.Contains(key))
                    {
                        throw new ArgumentException(key + " is an invalid options. The valid options are" +
                                                    string.Join(",", validOptionsKeys));
                    }
                }

                if (options.ContainsKey("headers"))
                {
                    var headers = options["headers"] as Dictionary <string, object>;
                    if (headers == null)
                    {
                        throw new ArgumentException("options.headers must be of type Dictionary<string,object>");
                    }

                    extraHeaders = headers;
                }

                if (options.ContainsKey("gcmAPIKey"))
                {
                    var gcmApiKey = options["gcmAPIKey"] as string;
                    if (gcmApiKey == null)
                    {
                        throw new ArgumentException("options.gcmAPIKey must be of type string");
                    }

                    currentGcmApiKey = gcmApiKey;
                }

                if (options.ContainsKey("vapidDetails"))
                {
                    var vapidDetails = options["vapidDetails"] as VapidDetails;
                    if (vapidDetails == null)
                    {
                        throw new ArgumentException("options.vapidDetails must be of type VapidDetails");
                    }

                    currentVapidDetails = vapidDetails;
                }

                if (options.ContainsKey("TTL"))
                {
                    var ttl = options["TTL"] as int?;
                    if (ttl == null)
                    {
                        throw new ArgumentException("options.TTL must be of type int");
                    }

                    //at this stage ttl cannot be null.
                    timeToLive = (int)ttl;
                }
            }

            string cryptoKeyHeader = null;

            request.Headers.Add("TTL", timeToLive.ToString());

            foreach (var header in extraHeaders)
            {
                request.Headers.Add(header.Key, header.Value.ToString());
            }

            if (!string.IsNullOrEmpty(payload))
            {
                if (string.IsNullOrEmpty(subscription.P256DH) || string.IsNullOrEmpty(subscription.Auth))
                {
                    throw new ArgumentException(
                              @"Unable to send a message with payload to this subscription since it doesn't have the required encryption key");
                }

                var encryptedPayload = Encryptor.Encrypt(subscription.P256DH, subscription.Auth, payload);

                request.Content = new ByteArrayContent(encryptedPayload.Payload);
                request.Content.Headers.ContentType   = new MediaTypeHeaderValue("application/octet-stream");
                request.Content.Headers.ContentLength = encryptedPayload.Payload.Length;
                request.Content.Headers.ContentEncoding.Add("aesgcm");
                request.Headers.Add("Encryption", "salt=" + encryptedPayload.Base64EncodeSalt());
                cryptoKeyHeader = @"dh=" + encryptedPayload.Base64EncodePublicKey();
            }
            else
            {
                request.Content = new ByteArrayContent(new byte[0]);
                request.Content.Headers.ContentLength = 0;
            }

            var uri      = new Uri(subscription.Endpoint);
            var audience = uri.Scheme + @"://" + uri.Host;


            var vapidHeaders = VapidHelper.GetVapidHeaders(audience, currentVapidDetails.Subject, currentVapidDetails.PublicKey, currentVapidDetails.PrivateKey);
            var serverKey    = string.Format("key={0}", googleFirebaseAppId);
            var senderId     = string.Format("id={0}", googleFirebaseSenderId);

            if (audience.StartsWith(@"https://updates.push.services.mozilla.com"))
            {
                request.Headers.Add(@"Authorization", vapidHeaders["Authorization"]);
            }
            else
            {
                request.Headers.TryAddWithoutValidation("Authorization", serverKey);
                request.Headers.TryAddWithoutValidation("Sender", senderId);
            }
            var isGcm = subscription.Endpoint.StartsWith(@"https://fcm.googleapis.com/fcm/send");

            if (!isGcm && currentVapidDetails != null)
            {
                if (string.IsNullOrEmpty(cryptoKeyHeader))
                {
                    cryptoKeyHeader = vapidHeaders["Crypto-Key"];
                }
                else
                {
                    cryptoKeyHeader += @";" + vapidHeaders["Crypto-Key"];
                }
            }

            request.Headers.Add("Crypto-Key", cryptoKeyHeader);
            return(request);
        }