diff --git a/NuGet/PushSharp.2.1.0.0-beta.nupkg b/NuGet/PushSharp.2.1.0.0-beta.nupkg new file mode 100644 index 00000000..97f473cb Binary files /dev/null and b/NuGet/PushSharp.2.1.0.0-beta.nupkg differ diff --git a/PushSharp.Amazon.Adm/AdmExceptions.cs b/PushSharp.Amazon.Adm/AdmExceptions.cs new file mode 100644 index 00000000..9af74601 --- /dev/null +++ b/PushSharp.Amazon.Adm/AdmExceptions.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace PushSharp.Amazon.Adm +{ + public class AdmMessageTooLargeException : Exception + { + public AdmMessageTooLargeException(AdmNotification notification, string msg) : base(msg) + { + Notification = notification; + } + + public AdmNotification Notification { get; private set; } + } + + public class AdmRateLimitExceededException : Exception + { + public AdmRateLimitExceededException(AdmNotification notification, string msg) : base(msg) + { + Notification = notification; + } + + public AdmNotification Notification { get; private set; } + } + + public class AdmSendException : Exception + { + public AdmSendException(AdmNotification notification, string msg) : base(msg) + { + Notification = notification; + } + + public AdmNotification Notification { get; private set; } + } + +} diff --git a/PushSharp.Amazon.Adm/AdmFluentNotification.cs b/PushSharp.Amazon.Adm/AdmFluentNotification.cs new file mode 100644 index 00000000..366344c4 --- /dev/null +++ b/PushSharp.Amazon.Adm/AdmFluentNotification.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using PushSharp.Amazon.Adm; + +namespace PushSharp +{ + public static class AdmFluentNotification + { + public static AdmNotification ForRegistrationId(this AdmNotification n, string registrationId) + { + n.RegistrationId = registrationId; + return n; + } + + public static AdmNotification WithConsolidationKey(this AdmNotification n, string consolidationKey) + { + n.ConsolidationKey = consolidationKey; + return n; + } + + public static AdmNotification WithExpiresAfter(this AdmNotification n, int expiresAfterSeconds) + { + n.ExpiresAfter = expiresAfterSeconds; + return n; + } + + public static AdmNotification WithData(this AdmNotification n, IDictionary data) + { + if (n.Data == null) + n.Data = new Dictionary(); + + foreach (var item in data) + { + if (!n.Data.ContainsKey(item.Key)) + n.Data.Add(item.Key, item.Value); + else + n.Data[item.Key] = item.Value; + } + + return n; + } + + public static AdmNotification WithData(this AdmNotification n, string key, string value) + { + if (n.Data == null) + n.Data = new Dictionary(); + + if (!n.Data.ContainsKey(key)) + n.Data.Add(key, value); + else + n.Data[key] = value; + + return n; + } + } +} diff --git a/PushSharp.Amazon.Adm/AdmNotification.cs b/PushSharp.Amazon.Adm/AdmNotification.cs new file mode 100644 index 00000000..ca9766e4 --- /dev/null +++ b/PushSharp.Amazon.Adm/AdmNotification.cs @@ -0,0 +1,52 @@ +using System; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using PushSharp.Core; + +namespace PushSharp.Amazon.Adm +{ + public class AdmNotification : Notification + { + public AdmNotification () + { + Data = new Dictionary (); + } + + public Dictionary Data { get; set; } + + public string ConsolidationKey { get;set; } + + public int? ExpiresAfter { get;set; } + + public string RegistrationId { get;set; } + + public string ToJson() + { + var json = new JObject (); + + var data = new JObject(); + + if (Data != null && Data.Count > 0) + { + foreach (var key in Data.Keys) + data [key] = Data [key]; + } + + json ["data"] = data; + + if (!string.IsNullOrEmpty (ConsolidationKey)) + json ["consolidationKey"] = ConsolidationKey; + + if (ExpiresAfter.HasValue && ExpiresAfter.Value >= 0) + json ["expiresAfter"] = ExpiresAfter.Value; + + return json.ToString(); + } + + public override string ToString () + { + return ToJson (); + } + } +} + diff --git a/PushSharp.Amazon.Adm/AdmPushBrokerExtensions.cs b/PushSharp.Amazon.Adm/AdmPushBrokerExtensions.cs new file mode 100644 index 00000000..1baa45b1 --- /dev/null +++ b/PushSharp.Amazon.Adm/AdmPushBrokerExtensions.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using PushSharp.Amazon.Adm; +using PushSharp.Core; + +namespace PushSharp +{ + public static class GcmPushBrokerExtensions + { + public static void RegisterGcmService(this PushBroker broker, AdmPushChannelSettings channelSettings, PushServiceSettings serviceSettings = null) + { + broker.RegisterService(new AdmPushService(new AdmPushChannelFactory(), channelSettings, serviceSettings)); + } + + public static AdmNotification GcmNotification(this PushBroker broker) + { + return new AdmNotification(); + } + } +} diff --git a/PushSharp.Amazon.Adm/AdmPushChannel.cs b/PushSharp.Amazon.Adm/AdmPushChannel.cs new file mode 100644 index 00000000..03d070fd --- /dev/null +++ b/PushSharp.Amazon.Adm/AdmPushChannel.cs @@ -0,0 +1,168 @@ +using System; +using System.Net.Http.Headers; +using PushSharp.Core; +using System.Net.Http; +using System.Threading.Tasks; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using System.Net; +using System.Threading; + +namespace PushSharp.Amazon.Adm +{ + public class AdmPushChannel : IPushChannel + { + AdmPushChannelSettings admSettings; + long waitCounter = 0; + HttpClient http = new HttpClient(); + + public AdmPushChannel (AdmPushChannelSettings admSettings) + { + this.admSettings = admSettings; + + Expires = DateTime.UtcNow.AddYears(-1); + + //http.DefaultRequestHeaders.Add ("Content-Type", "application/json"); + http.DefaultRequestHeaders.Add ("X-Amzn-Type-Version", "com.amazon.device.messaging.ADMMessage@1.0"); + http.DefaultRequestHeaders.Add ("X-Amzn-Accept-Type", "com.amazon.device.messaging.ADMSendResult@1.0"); + http.DefaultRequestHeaders.Add ("Accept", "application/json"); + + + //http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", this.AccessToken); + http.DefaultRequestHeaders.ConnectionClose = true; + + http.DefaultRequestHeaders.Remove("connection"); + } + + public void SendNotification (INotification notification, SendNotificationCallbackDelegate callback) + { + SendNotificationAsync (notification, callback); + } + + void SendNotificationAsync(INotification notification, SendNotificationCallbackDelegate callback) + { + var n = notification as AdmNotification; + + try + { + Interlocked.Increment(ref waitCounter); + + if (string.IsNullOrEmpty(AccessToken) || Expires <= DateTime.UtcNow) + { + UpdateAccessToken(); + http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", this.AccessToken); + } + + var sc = new StringContent(n.ToJson()); + sc.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + var response = http.PostAsync (string.Format(admSettings.AdmSendUrl, n.RegistrationId), sc).Result; + + if (response.IsSuccessStatusCode) + { + callback(this, new SendNotificationResult(n)); + return; + } + + var data = response.Content.ReadAsStringAsync().Result; + + var json = JObject.Parse (data); + + var reason = json ["reason"].ToString (); + + + var regId = n.RegistrationId; + + if (json["registrationID"] != null) + regId = json["registrationID"].ToString(); + + switch (response.StatusCode) + { + case HttpStatusCode.BadGateway: //400 + case HttpStatusCode.BadRequest: // + if ("InvalidRegistrationId".Equals (reason, StringComparison.InvariantCultureIgnoreCase)) + { + callback (this, new SendNotificationResult (n) + { + IsSubscriptionExpired = true, + OldSubscriptionId = regId + }); + } + callback(this, new SendNotificationResult(n, false, new AdmSendException(n, "Send Failed: " + reason))); + break; + case HttpStatusCode.Unauthorized: //401 + //Access token expired + this.AccessToken = null; + callback(this, new SendNotificationResult(n, true)); + break; + case HttpStatusCode.Forbidden: //403 + callback(this, new SendNotificationResult(n, true, new AdmRateLimitExceededException(n, "Rate Limit Exceeded (" + reason + ")"))); + break; + case HttpStatusCode.RequestEntityTooLarge: //413 + callback(this, new SendNotificationResult(n, false, new AdmMessageTooLargeException(n, "ADM Message too Large, must be <= 6kb"))); + break; + default: + callback(this, new SendNotificationResult(n, false, new AdmSendException(n, "Unknown Failure"))); + break; + } + } + catch (Exception ex) + { + callback(this, new SendNotificationResult(n, false, new AdmSendException(n, "Unknown Failure"))); + } + finally + { + Interlocked.Decrement (ref waitCounter); + } + } + + + void UpdateAccessToken() + { + var http = new HttpClient (); + + var param = new Dictionary (); + param.Add ("grant_type", "client_credentials"); + param.Add ("scope", "messaging:push"); + param.Add ("client_id", admSettings.ClientId); + param.Add ("client_secret", admSettings.ClientSecret); + + + var result = http.PostAsync (admSettings.AdmAuthUrl, new FormUrlEncodedContent (param)).Result; + var data = result.Content.ReadAsStringAsync().Result; + + var json = JObject.Parse (data); + + this.AccessToken = json ["access_token"].ToString (); + + JToken expiresJson = new JValue(3540); + if (json.TryGetValue("expires_in", out expiresJson)) + Expires = DateTime.UtcNow.AddSeconds(expiresJson.ToObject() - 60); + else + Expires = DateTime.UtcNow.AddSeconds(3540); + + if (result.Headers.Contains ("X-Amzn-RequestId")) + this.LastAmazonRequestId = string.Join("; ", result.Headers.GetValues("X-Amzn-RequestId")); + + LastRequest = DateTime.UtcNow; + } + + public DateTime Expires { get; set; } + public DateTime LastRequest { get; private set; } + public string LastAmazonRequestId { get; private set; } + public string AccessToken { get; private set; } + + + public void Dispose () + { + var slept = 0; + while (Interlocked.Read(ref waitCounter) > 0 && slept <= 5000) + { + slept += 100; + Thread.Sleep(100); + } + } + + } +} + diff --git a/PushSharp.Amazon.Adm/AdmPushChannelSettings.cs b/PushSharp.Amazon.Adm/AdmPushChannelSettings.cs new file mode 100644 index 00000000..5f83dfcf --- /dev/null +++ b/PushSharp.Amazon.Adm/AdmPushChannelSettings.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; +using PushSharp.Core; + +namespace PushSharp.Amazon.Adm +{ + public class AdmPushChannelSettings : IPushChannelSettings + { + private const string ADM_SEND_URL = "https://api.amazon.com/messaging/registrations/{0}/messages"; + private const string ADM_AUTH_URL = "https://api.amazon.com/auth/O2/token"; + + public AdmPushChannelSettings(string clientId, string clientSecret) + { + this.ClientId = clientId; + this.ClientSecret = clientSecret; + this.AdmSendUrl = ADM_SEND_URL; + this.AdmAuthUrl = ADM_AUTH_URL; + } + + public string ClientId { get; private set; } + public string ClientSecret { get; private set; } + + public string AdmSendUrl { get; private set; } + public string AdmAuthUrl { get; private set; } + + public void OverrideSendUrl(string url) + { + AdmSendUrl = url; + } + + public void OverrideAuthUrl(string url) + { + AdmAuthUrl = url; + } + } +} diff --git a/PushSharp.Amazon.Adm/AdmPushService.cs b/PushSharp.Amazon.Adm/AdmPushService.cs new file mode 100644 index 00000000..a219828f --- /dev/null +++ b/PushSharp.Amazon.Adm/AdmPushService.cs @@ -0,0 +1,44 @@ +using System; +using System.Net.Http; +using PushSharp.Core; +using System.Collections.Generic; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +namespace PushSharp.Amazon.Adm +{ + public class AdmPushService : PushServiceBase + { + public AdmPushService(AdmPushChannelSettings channelSettings) + : this(default(IPushChannelFactory), channelSettings, default(IPushServiceSettings)) + { + } + + public AdmPushService(AdmPushChannelSettings channelSettings, IPushServiceSettings serviceSettings) + : this(default(IPushChannelFactory), channelSettings, serviceSettings) + { + } + + public AdmPushService(IPushChannelFactory pushChannelFactory, AdmPushChannelSettings channelSettings) + : this(pushChannelFactory, channelSettings, default(IPushServiceSettings)) + { + } + + public AdmPushService(IPushChannelFactory pushChannelFactory, AdmPushChannelSettings channelSettings, IPushServiceSettings serviceSettings) + : base(pushChannelFactory ?? new AdmPushChannelFactory(), channelSettings, serviceSettings) + { + } + } + + public class AdmPushChannelFactory : IPushChannelFactory + { + public IPushChannel CreateChannel(IPushChannelSettings channelSettings) + { + if (!(channelSettings is AdmPushChannelSettings)) + throw new ArgumentException("channelSettings must be of type " + typeof(AdmPushChannelSettings).Name); + + return new AdmPushChannel(channelSettings as AdmPushChannelSettings); + } + } +} + diff --git a/PushSharp.Amazon.Adm/Properties/AssemblyInfo.cs b/PushSharp.Amazon.Adm/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..effe387f --- /dev/null +++ b/PushSharp.Amazon.Adm/Properties/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("PushSharp.Amazon.Adm")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("jonathan")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/PushSharp.Amazon.Adm/PushSharp.Amazon.Adm.csproj b/PushSharp.Amazon.Adm/PushSharp.Amazon.Adm.csproj new file mode 100644 index 00000000..b14772fa --- /dev/null +++ b/PushSharp.Amazon.Adm/PushSharp.Amazon.Adm.csproj @@ -0,0 +1,62 @@ + + + + Debug + AnyCPU + 12.0.0 + 2.0 + {52154303-5315-494C-A741-2F0998795DC3} + Library + PushSharp.Amazon.Adm + PushSharp.Amazon.Adm + + + true + false + bin\Debug + DEBUG; + prompt + 4 + false + + + true + bin\Release + prompt + 4 + false + + + + + ..\packages\Newtonsoft.Json.5.0.5\lib\net40\Newtonsoft.Json.dll + + + + ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + + + ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll + + + + + + + + + + + + + + + + {836F225F-6CD9-48DE-910C-70F8A7CF54AA} + PushSharp.Core + + + + + + \ No newline at end of file diff --git a/PushSharp.Amazon.Adm/packages.config b/PushSharp.Amazon.Adm/packages.config new file mode 100644 index 00000000..c864b175 --- /dev/null +++ b/PushSharp.Amazon.Adm/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/PushSharp.Android/Gcm/GcmNotification.cs b/PushSharp.Android/Gcm/GcmNotification.cs index 13287d3d..0f29ea1e 100644 --- a/PushSharp.Android/Gcm/GcmNotification.cs +++ b/PushSharp.Android/Gcm/GcmNotification.cs @@ -42,7 +42,7 @@ public GcmNotification() } /// - /// Registration ID of the Device + /// Registration ID of the Device(s). Maximum of 1000 registration Id's per notification. /// public List RegistrationIds { @@ -69,7 +69,7 @@ public string JsonData } /// - /// If true, C2DM will only be delivered once the device's screen is on + /// If true, GCM will only be delivered once the device's screen is on /// public bool? DelayWhileIdle { diff --git a/PushSharp.Android/Gcm/GcmPushChannel.cs b/PushSharp.Android/Gcm/GcmPushChannel.cs index 73b6239e..c771f998 100644 --- a/PushSharp.Android/Gcm/GcmPushChannel.cs +++ b/PushSharp.Android/Gcm/GcmPushChannel.cs @@ -18,6 +18,7 @@ public class GcmPushChannel : IPushChannel { GcmPushChannelSettings gcmSettings = null; long waitCounter = 0; + static Version assemblyVerison; public GcmPushChannel(GcmPushChannelSettings channelSettings) { @@ -28,6 +29,7 @@ public GcmPushChannel(GcmPushChannelSettings channelSettings) static GcmPushChannel() { ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, policyErrs) => { return true; }; + assemblyVerison = System.Reflection.Assembly.GetExecutingAssembly ().GetName ().Version; } public void SendNotification(INotification notification, SendNotificationCallbackDelegate callback) @@ -44,7 +46,7 @@ public void SendNotification(INotification notification, SendNotificationCallbac webReq.Method = "POST"; webReq.ContentType = "application/json"; //webReq.ContentType = "application/x-www-form-urlencoded;charset=UTF-8 can be used for plaintext bodies - webReq.UserAgent = "PushSharp (version: 1.0)"; + webReq.UserAgent = "PushSharp (version: " + assemblyVerison.ToString () + ")"; webReq.Headers.Add("Authorization: key=" + gcmSettings.SenderAuthToken); webReq.BeginGetRequestStream(new AsyncCallback(requestStreamCallback), new GcmAsyncParameters() @@ -135,15 +137,18 @@ void processResponseOk(GcmAsyncParameters asyncParam) //Get the response body var json = new JObject(); - try { json = JObject.Parse((new StreamReader(asyncParam.WebResponse.GetResponseStream())).ReadToEnd()); } + var str = string.Empty; + + try { str = (new StreamReader(asyncParam.WebResponse.GetResponseStream())).ReadToEnd(); } catch { } + try { json = JObject.Parse(str); } + catch { } result.NumberOfCanonicalIds = json.Value("canonical_ids"); result.NumberOfFailures = json.Value("failure"); result.NumberOfSuccesses = json.Value("success"); - - + var jsonResults = json["results"] as JArray; if (jsonResults == null) @@ -165,7 +170,7 @@ void processResponseOk(GcmAsyncParameters asyncParam) { var err = r.Value("error") ?? ""; - switch (err.ToLower().Trim()) + switch (err.ToLowerInvariant().Trim()) { case "ok": msgResult.ResponseStatus = GcmMessageTransportResponseStatus.Ok; diff --git a/PushSharp.Android/Gcm/GcmPushService.cs b/PushSharp.Android/Gcm/GcmPushService.cs index d13e89e4..e9f75858 100644 --- a/PushSharp.Android/Gcm/GcmPushService.cs +++ b/PushSharp.Android/Gcm/GcmPushService.cs @@ -34,7 +34,7 @@ public class GcmPushChannelFactory : IPushChannelFactory public IPushChannel CreateChannel(IPushChannelSettings channelSettings) { if (!(channelSettings is GcmPushChannelSettings)) - throw new ArgumentException("channelSettings must be of type GcmPushChannelSettings"); + throw new ArgumentException("channelSettings must be of type " + typeof(GcmPushChannelSettings).Name); return new GcmPushChannel(channelSettings as GcmPushChannelSettings); } diff --git a/PushSharp.Apple/ApplePushChannel.cs b/PushSharp.Apple/ApplePushChannel.cs index 5a5b4b95..a7fac7d1 100644 --- a/PushSharp.Apple/ApplePushChannel.cs +++ b/PushSharp.Apple/ApplePushChannel.cs @@ -210,13 +210,18 @@ public void Dispose() Log.Info("ApplePushChannel->DISPOSE."); } - + + private IAsyncResult readAsyncResult = default(IAsyncResult); + void Reader() { try { - networkStream.BeginRead(readBuffer, 0, 6, new AsyncCallback((asyncResult) => - { + readAsyncResult = networkStream.BeginRead(readBuffer, 0, 6, new AsyncCallback((asyncResult) => + { + if (readAsyncResult != asyncResult) + return; + lock (sentLock) { try @@ -231,9 +236,11 @@ void Reader() try { stream.Close(); stream.Dispose(); } catch { } - try { client.Close(); stream.Dispose(); } + try { client.Close(); } catch { } + client = null; + //Get the enhanced format response // byte 0 is always '1', byte 1 is the status, bytes 2,3,4,5 are the identifier of the notification var status = readBuffer[1]; @@ -443,6 +450,8 @@ void Connect() } } + private IAsyncResult connectAsyncResult = default(IAsyncResult); + void connect() { client = new TcpClient(); @@ -456,12 +465,16 @@ void connect() { var connectDone = new AutoResetEvent(false); + //Connect async so we can utilize a connection timeout - client.BeginConnect( + connectAsyncResult = client.BeginConnect( appleSettings.Host, appleSettings.Port, new AsyncCallback( delegate(IAsyncResult ar) { + if (connectAsyncResult != ar) + return; + try { client.EndConnect(ar); diff --git a/PushSharp.Blackberry/BlackberryExceptions.cs b/PushSharp.Blackberry/BlackberryExceptions.cs new file mode 100644 index 00000000..72ee13ba --- /dev/null +++ b/PushSharp.Blackberry/BlackberryExceptions.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +namespace PushSharp.Blackberry +{ + public class BisNotificationSendFailureException : Exception + { + private readonly string _message; + private readonly Dictionary _data = new Dictionary(); + public BisNotificationSendFailureException(BlackberryMessageStatus msgStatus, string desc) + { + _data.Add("HttpStatus", msgStatus.HttpStatus); + _data.Add("Code", (Int32) msgStatus.NotificationStatus); + _data.Add("Notification", msgStatus.Notification); + _data.Add("Description",desc); + _message = desc; + } + + public override string Message + { + get + { + return _message; + } + } + + public override System.Collections.IDictionary Data + { + get + { + return _data; + } + } + public BlackberryMessageStatus MessageStatus + { + get; + set; + } + } +} diff --git a/PushSharp.Blackberry/BlackberryMessageStatus.cs b/PushSharp.Blackberry/BlackberryMessageStatus.cs new file mode 100644 index 00000000..ae4736b5 --- /dev/null +++ b/PushSharp.Blackberry/BlackberryMessageStatus.cs @@ -0,0 +1,139 @@ +namespace PushSharp.Blackberry +{ + public class BlackberryMessageStatus + { + public BlackberryNotificationStatus NotificationStatus { get; set; } + + public BlackberryNotification Notification { get; set; } + + public System.Net.HttpStatusCode HttpStatus { get; set; } + } + + public enum BlackberryNotificationStatus + { + NotAvailable=0, + /// + /// The request was completed successfully + /// + RequestCompleted = 1000, + /// + /// The request was accepted for processing + /// + RequestAcceptedForProcessing = 1001, + /// + /// The request was accepted for processing, but the daily push count quota was exceeded + /// for the push application and future pushes to the application might start being rejected. + /// Future pushes should be delayed until the next day when more quota is available + /// + RequestAcceptedButPushQuotaExceeded = 1500, + /// + /// The request is invalid + /// + InvalidRequest = 2000, + /// + /// The requested action is forbidden + /// + ForbiddenRequestAction = 2001, + /// + /// The specified PIN or token is not recognized + /// + PinOrTokenNotRecognized = 2002, + /// + /// Could not find the specified Push ID + /// + PushIdNotFound = 2004, + /// + /// The supplied Push ID is not unique + /// + PushIdNotUnique = 2007, + /// + /// The Push ID is valid, but the push request could not be cancelled + /// + PushCantBeCancelled = 2008, + /// + /// The Push ID is valid, but the corresponding PINs or tokens are still being processed. + /// Status query is not possible at this time and should be tried again later + /// + StatusCodeNotPossible = 2009, + /// + /// The request was rejected because the daily push count quota was exceeded for the push application. + /// Future pushes should be delayed until the next day when more quota is available + /// + PushQuotaExceeded = 2500, + /// + /// The PPG could not complete the request due to an internal error + /// + InternalError = 3000, + /// + /// The server does not support the operation that was requested + /// + OperationNotSupported = 3001, + /// + /// The server does not support the PAP version specified in the request + /// + ProvidedPapVersionNotSupported = 3002, + /// + /// The PPG could not deliver the message using the specified method + /// + DeliveryFailed = 3007, + /// + /// The service failed + /// + ServiceFailed = 4000, + /// + /// The server is busy + /// + ServerBusy = 4001, + /// + /// The request expired + /// + RequestExpired = 4500, + /// + /// The request failed + /// + RequestFailed = 4501, + /// + /// The request failed because no application on the device is listening to receive the push + /// (either the application is closed and cannot be launched or it was removed from the device)" + /// + NoAppReceivePush = 4502, + /// + /// The device is unable to receive the push due to the push service being blocked + /// + PushServiceBlocked = 4503, + /// + /// Specific request was completed successfully + /// + SpecifiedRequestCompleted = 21000, + /// + /// Specific request is badly formed + /// + SpecifiedRequestMalformed = 22000, + /// + /// Could not find the specified application ID for the specific request + /// + SpecifiedRequestAppIdNotFound = 22001, + /// + /// The specified PIN or token in the specific request is invalid + /// + SpecifiedRequestInvalidPinOrToken = 22002, + /// + /// The specific request provides an incorrect status + /// + SpecifiedRequestIncorrectStatus = 22003, + /// + /// The specific request produces no results + /// + SpecifiedRequestNoResults = 22004, + /// + /// The specific request exceeds the number of calls allowed + /// within the specified time period + /// + SpecifiedRequestNumCallsExceeded = 22005, + /// + /// Internal error has prevented the request from being completed + /// + SpecifiedRequestInternalError = 23000 + } + +} diff --git a/PushSharp.Blackberry/BlackberryNotification.cs b/PushSharp.Blackberry/BlackberryNotification.cs index 20ec37ac..0857f4d1 100644 --- a/PushSharp.Blackberry/BlackberryNotification.cs +++ b/PushSharp.Blackberry/BlackberryNotification.cs @@ -4,22 +4,141 @@ using System.Text; using System.IO; using System.Net; -using PushSharp.Common; +using System.Xml.Linq; +using System.Globalization; namespace PushSharp.Blackberry { - public class BlackberryNotification : Notification + public enum QualityOfServiceLevel + { + NotSpecified, + Unconfirmed, + PreferConfirmed, + Confirmed + } + + public class BlackberryNotification : Core.Notification { public BlackberryNotification() - : base() { - this.Platform = PlatformType.Blackberry; + PushId = Guid.NewGuid ().ToString (); + Recipients = new List (); + DeliverBeforeTimestamp = DateTime.UtcNow.AddSeconds(30); } - public string WidgetNotificationUrl { get; set; } + public string PushId { get; private set; } + public QualityOfServiceLevel? QualityOfService { get;set; } + public string PpgNotifyRequestedTo { get; set; } + public DateTime? DeliverBeforeTimestamp { get; set; } + public DateTime? DeliverAfterTimestamp { get; set; } + public List Recipients { get;set; } + public string SourceReference { get; set; } + + public BlackberryMessageContent Content { get; set; } + + public string ToPapXml() + { + var doc = new XDocument (); + + var docType = new XDocumentType("pap", "-//WAPFORUM//DTD PAP 2.1//EN", "http://www.openmobilealliance.org/tech/DTD/pap_2.1.dtd", ""); + + doc.AddFirst (docType); + + var pap = new XElement ("pap"); + + var pushMsg = new XElement ("push-message"); + + pushMsg.Add (new XAttribute ("push-id", this.PushId)); + pushMsg.Add(new XAttribute("source-reference", this.SourceReference)); + + if (this.QualityOfService.HasValue && !string.IsNullOrEmpty (this.PpgNotifyRequestedTo)) + pushMsg.Add(new XAttribute("ppg-notify-requested-to", this.PpgNotifyRequestedTo)); + + if (this.DeliverAfterTimestamp.HasValue) + pushMsg.Add (new XAttribute ("deliver-after-timestamp", this.DeliverAfterTimestamp.Value.ToUniversalTime ().ToString("s", CultureInfo.InvariantCulture) + "Z")); + if (this.DeliverBeforeTimestamp.HasValue) + pushMsg.Add (new XAttribute ("deliver-before-timestamp", this.DeliverBeforeTimestamp.Value.ToUniversalTime ().ToString("s", CultureInfo.InvariantCulture) + "Z")); + + //Add all the recipients + foreach (var r in Recipients) + { + var address = new XElement("address"); + + var addrValue = r.Recipient; + + if (!string.IsNullOrEmpty(r.RecipientType)) + { + addrValue = string.Format("WAPPUSH={0}%3A{1}/TYPE={2}", System.Web.HttpUtility.UrlEncode(r.Recipient), + r.Port, r.RecipientType); + } + + address.Add(new XAttribute("address-value", addrValue)); + pushMsg.Add (address); + } + + if (this.QualityOfService.HasValue) + pushMsg.Add (new XElement ("quality-of-service", new XAttribute ("delivery-method", this.QualityOfService.Value.ToString ().ToLowerInvariant ()))); - public string PushPin { get; set; } + pap.Add(pushMsg); + doc.Add (pap); - public string Message { get; set; } + return "" + Environment.NewLine + doc.ToString (SaveOptions.None); + } + + + protected string XmlEncode(string text) + { + return System.Security.SecurityElement.Escape(text); + } + + } + + public class BlackberryRecipient + { + public BlackberryRecipient(string recipient) + { + Recipient = recipient; + } + + public BlackberryRecipient(string recipient, int port, string recipientType) + { + Recipient = recipient; + Port = port; + RecipientType = recipientType; + } + + public string Recipient { get;set; } + public int Port { get;set; } + public string RecipientType { get;set; } } + + public class BlackberryMessageContent + { + + public BlackberryMessageContent(string contentType, string content) + { + this.Headers = new Dictionary(); + this.ContentType = contentType; + this.Content = Encoding.UTF8.GetBytes(content); + } + + public BlackberryMessageContent(string content) + { + this.Headers = new Dictionary(); + this.ContentType = "text/plain"; + this.Content = Encoding.UTF8.GetBytes(content); + } + + public BlackberryMessageContent(string contentType, byte[] content) + { + this.Headers = new Dictionary(); + this.ContentType = contentType; + this.Content = content; + } + + public string ContentType { get; private set; } + public byte[] Content { get; private set; } + + public Dictionary Headers { get; private set; } + } } diff --git a/PushSharp.Blackberry/BlackberryPushBrokerExtensions.cs b/PushSharp.Blackberry/BlackberryPushBrokerExtensions.cs new file mode 100644 index 00000000..eca9fcab --- /dev/null +++ b/PushSharp.Blackberry/BlackberryPushBrokerExtensions.cs @@ -0,0 +1,18 @@ +using PushSharp.Blackberry; +using PushSharp.Core; + +namespace PushSharp +{ + public static class BISPushBrokerExtensions + { + public static void RegisterBlackberryService(this PushBroker broker, BlackberryPushChannelSettings channelSettings, IPushServiceSettings serviceSettings = null) + { + broker.RegisterService(new BlackberryPushService(channelSettings, serviceSettings)); + } + + public static BlackberryNotification BlackberryNotification(this PushBroker broker) + { + return new BlackberryNotification(); + } + } +} diff --git a/PushSharp.Blackberry/BlackberryPushChannel.cs b/PushSharp.Blackberry/BlackberryPushChannel.cs index 2fb79054..7d3bd61f 100644 --- a/PushSharp.Blackberry/BlackberryPushChannel.cs +++ b/PushSharp.Blackberry/BlackberryPushChannel.cs @@ -1,96 +1,143 @@ using System; -using System.Collections.Generic; -using System.IO; +using System.Globalization; using System.Linq; using System.Net; +using System.Net.Http.Headers; using System.Text; -using PushSharp.Common; +using System.Xml.Linq; +using PushSharp.Core; +using System.Net.Http; +using System.Threading.Tasks; namespace PushSharp.Blackberry { - public class BlackberryPushChannel : PushChannelBase - { - BlackberryPushChannelSettings blackberrySettings = null; + public class BlackberryPushChannel : IPushChannel + { + BlackberryPushChannelSettings bisChannelSettings; - public BlackberryPushChannel(BlackberryPushChannelSettings channelSettings, Common.PushServiceSettings serviceSettings = null) : base(channelSettings, serviceSettings) - { - blackberrySettings = channelSettings; - } - - public override PlatformType PlatformType + public BlackberryPushChannel(BlackberryPushChannelSettings channelSettings) { - get { return PlatformType.Blackberry; } + bisChannelSettings = channelSettings; + + http = new BlackberryHttpClient(bisChannelSettings); } - - protected override void SendNotification(Notification notification) + + public class BlackberryHttpClient : HttpClient { - var bbn = notification as BlackberryNotification; + private BlackberryPushChannelSettings channelSettings; - if (bbn != null) - push(bbn); - } + public BlackberryHttpClient(BlackberryPushChannelSettings channelSettings) : base() + { + this.channelSettings = channelSettings; + var authInfo = channelSettings.ApplicationId + ":" + channelSettings.Password; + authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo)); - bool push(BlackberryNotification notification) - { - bool success = true; - byte[] bytes = Encoding.ASCII.GetBytes(notification.Message); + this.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authInfo); + this.DefaultRequestHeaders.ConnectionClose = true; - Stream requestStream = null; - HttpWebResponse HttpWRes = null; - HttpWebRequest HttpWReq = null; + this.DefaultRequestHeaders.Remove("connection"); + } - try + public Task PostNotification(BlackberryPushChannelSettings channelSettings, BlackberryNotification n) { - //http://:/push?DESTINATTION=&PORT=&REQUESTURI=/ - // Build the URL to define our connection to the BES. - string httpURL = "http://" + this.blackberrySettings.BESAddress + ":" + blackberrySettings.BESWebServerListenPort.ToString() - + "/push?DESTINATION=" + notification.PushPin + "&PORT=" + blackberrySettings.BESPushPort.ToString() - + "&REQUESTURI=/"; - - //make the connection - HttpWReq = (HttpWebRequest)WebRequest.Create(httpURL); - HttpWReq.Method = ("POST"); - //add the headers nessecary for the push - HttpWReq.ContentType = "text/plain"; - HttpWReq.ContentLength = bytes.Length; - // ******* Test this ******* - HttpWReq.Headers.Add("X-Rim-Push-Id", notification.PushPin + "~" + DateTime.Now); //"~" +pushedMessage + - HttpWReq.Headers.Add("X-Rim-Push-Reliability", "application-preferred"); - HttpWReq.Headers.Add("X-Rim-Push-NotifyURL", (notification.WidgetNotificationUrl + notification.PushPin + "~" + notification.Message + "~" + DateTime.Now).Replace(" ", "")); - - // ************************* - HttpWReq.Credentials = new NetworkCredential(blackberrySettings.PushUsername, blackberrySettings.PushPassword); + var c = new MultipartContent ("related", channelSettings.Boundary); + c.Headers.Remove("Content-Type"); + c.Headers.TryAddWithoutValidation("Content-Type", "multipart/related; boundary=" + channelSettings.Boundary + "; type=application/xml"); - requestStream = HttpWReq.GetRequestStream(); - //Write the data from the source - requestStream.Write(bytes, 0, bytes.Length); - - //get the response - HttpWRes = (HttpWebResponse)HttpWReq.GetResponse(); - - var pushStatus = HttpWRes.Headers["X-RIM-Push-Status"]; - - //if the MDS received the push parameters correctly it will either respond with okay or accepted - if (HttpWRes.StatusCode == HttpStatusCode.OK || HttpWRes.StatusCode == HttpStatusCode.Accepted) - { - success = true; - } - else - { - success = false; - } - //Close the streams - - HttpWRes.Close(); - requestStream.Close(); - } - catch (System.Exception) - { - success = false; + var xml = n.ToPapXml (); + + + c.Add (new StringContent (xml, Encoding.UTF8, "application/xml")); + + var bc = new ByteArrayContent(n.Content.Content); + bc.Headers.Add("Content-Type", n.Content.ContentType); + + foreach (var header in n.Content.Headers) + bc.Headers.Add(header.Key, header.Value); + + c.Add(bc); + + return PostAsync (channelSettings.SendUrl, c); } + } - return success; + private BlackberryHttpClient http; + + public void SendNotification(INotification notification, SendNotificationCallbackDelegate callback) + { + var n = notification as BlackberryNotification; + + try + { + var response = http.PostNotification(bisChannelSettings, n).Result; + var description = string.Empty; + + var status = new BlackberryMessageStatus + { + Notification = n, + HttpStatus = HttpStatusCode.ServiceUnavailable + }; + + var bbNotStatus = string.Empty; + status.HttpStatus = response.StatusCode; + + var doc = XDocument.Load(response.Content.ReadAsStreamAsync().Result); + + var result = doc.Descendants("response-result").SingleOrDefault(); + if (result != null) + { + bbNotStatus = result.Attribute("code").Value; + description = result.Attribute("desc").Value; + } + else + { + result = doc.Descendants("badmessage-response").SingleOrDefault(); + if (result != null) + { + bbNotStatus = result.Attribute("code").Value; + description = result.Attribute("desc").Value; + } + } + + BlackberryNotificationStatus notStatus; + Enum.TryParse(bbNotStatus, true, out notStatus); + status.NotificationStatus = notStatus; + + if (status.NotificationStatus == BlackberryNotificationStatus.NoAppReceivePush) + { + if (callback != null) + callback(this, + new SendNotificationResult(notification, false, new Exception("Device Subscription Expired")) + { + IsSubscriptionExpired = true + }); + + return; + } + + if (status.HttpStatus == HttpStatusCode.OK + && status.NotificationStatus == BlackberryNotificationStatus.RequestAcceptedForProcessing) + { + if (callback != null) + callback(this, new SendNotificationResult(notification)); + return; + } + + if (callback != null) + callback(this, + new SendNotificationResult(status.Notification, false, + new BisNotificationSendFailureException(status, description))); + } + catch (Exception ex) + { + if (callback != null) + callback(this, new SendNotificationResult(notification, false, ex)); + } } - } + + public void Dispose() + { + } + } } diff --git a/PushSharp.Blackberry/BlackberryPushChannelSettings.cs b/PushSharp.Blackberry/BlackberryPushChannelSettings.cs index 5f92e42b..569f7c4d 100644 --- a/PushSharp.Blackberry/BlackberryPushChannelSettings.cs +++ b/PushSharp.Blackberry/BlackberryPushChannelSettings.cs @@ -1,17 +1,32 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using PushSharp.Core; namespace PushSharp.Blackberry { - public class BlackberryPushChannelSettings : Common.PushChannelSettings + public class BlackberryPushChannelSettings : IPushChannelSettings { - public string BESAddress { get; set; } - public int BESWebServerListenPort { get; set; } - public int BESPushPort { get; set; } + public string ApplicationId { get; set; } + public string Password { get; set; } + public string Boundary { get { return "ASDFaslkdfjasfaSfdasfhpoiurwqrwm"; } } - public string PushUsername { get; set; } - public string PushPassword { get; set; } + private const string SEND_URL = "https://pushapi.eval.blackberry.com/mss/PD_pushRequest"; + + public BlackberryPushChannelSettings() + { + SendUrl = SEND_URL; + } + + public BlackberryPushChannelSettings(string applicationId, string password) + { + ApplicationId = applicationId; + Password = password; + SendUrl = SEND_URL; + } + + public string SendUrl { get; private set; } + + public void OverrideSendUrl(string url) + { + SendUrl = url; + } } } diff --git a/PushSharp.Blackberry/BlackberryPushService.cs b/PushSharp.Blackberry/BlackberryPushService.cs index d39d8ef3..874a880d 100644 --- a/PushSharp.Blackberry/BlackberryPushService.cs +++ b/PushSharp.Blackberry/BlackberryPushService.cs @@ -2,24 +2,46 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using PushSharp.Core; namespace PushSharp.Blackberry { - public class BlackberryPushService : Common.PushServiceBase - { - public BlackberryPushService(BlackberryPushChannelSettings channelSettings, Common.PushServiceSettings serviceSettings) - : base(channelSettings, serviceSettings) - { - } - protected override Common.PushChannelBase CreateChannel(Common.PushChannelSettings channelSettings) - { - return new BlackberryPushChannel(channelSettings as BlackberryPushChannelSettings); - } + public class BlackberryPushService : PushServiceBase + { + public BlackberryPushService() + : this(default(IPushChannelFactory), null, default(IPushServiceSettings)) + { + } - public override Common.PlatformType Platform - { - get { return Common.PlatformType.Blackberry; } - } - } + public BlackberryPushService(BlackberryPushChannelSettings channelSettings) + : this(default(IPushChannelFactory), channelSettings, default(IPushServiceSettings)) + { + } + + public BlackberryPushService(BlackberryPushChannelSettings channelSettings, IPushServiceSettings serviceSettings) + : this(default(IPushChannelFactory), channelSettings, serviceSettings) + { + } + + public BlackberryPushService(IPushChannelFactory pushChannelFactory, BlackberryPushChannelSettings channelSettings) + : this(pushChannelFactory, channelSettings, default(IPushServiceSettings)) + { + } + + public BlackberryPushService(IPushChannelFactory pushChannelFactory, BlackberryPushChannelSettings channelSettings, IPushServiceSettings serviceSettings) + : base(pushChannelFactory ?? new BisPushChannelFactory(), channelSettings ?? new BlackberryPushChannelSettings(), serviceSettings) + { + } + } + public class BisPushChannelFactory : IPushChannelFactory + { + public IPushChannel CreateChannel(IPushChannelSettings channelSettings) + { + if (!(channelSettings is BlackberryPushChannelSettings)) + throw new ArgumentException("channelSettings must be of type: " + typeof(BlackberryPushChannelSettings).Name); + + return new BlackberryPushChannel(channelSettings as BlackberryPushChannelSettings); + } + } } diff --git a/PushSharp.Blackberry/Properties/AssemblyInfo.cs b/PushSharp.Blackberry/Properties/AssemblyInfo.cs index 23c69727..092b6d6d 100644 --- a/PushSharp.Blackberry/Properties/AssemblyInfo.cs +++ b/PushSharp.Blackberry/Properties/AssemblyInfo.cs @@ -21,3 +21,5 @@ // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("055417c8-aac4-488f-9418-642e456c4545")] +[assembly: AssemblyVersionAttribute("2.0.4")] +[assembly: AssemblyFileVersionAttribute("2.0.4")] diff --git a/PushSharp.Blackberry/PushSharp.Blackberry.csproj b/PushSharp.Blackberry/PushSharp.Blackberry.csproj index faed395a..83a7a5bf 100644 --- a/PushSharp.Blackberry/PushSharp.Blackberry.csproj +++ b/PushSharp.Blackberry/PushSharp.Blackberry.csproj @@ -43,25 +43,49 @@ + + ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + + + ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll + + + ..\packages\Microsoft.Bcl.1.0.19\lib\net40\System.Runtime.dll + + + ..\packages\Microsoft.Bcl.1.0.19\lib\net40\System.Threading.Tasks.dll + + + ..\packages\Microsoft.Bcl.Async.1.0.16\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll + + + ..\packages\Microsoft.Bcl.Async.1.0.16\lib\net40\Microsoft.Threading.Tasks.Extensions.dll + + + ..\packages\Microsoft.Bcl.Async.1.0.16\lib\net40\Microsoft.Threading.Tasks.dll + + + - - AssemblyVersionInfo.cs - - + + + + - + - + {836F225F-6CD9-48DE-910C-70F8A7CF54AA} PushSharp.Core +