├── .gitignore ├── JdSoft.Apple.Apns.Feedback.Test ├── JdSoft.Apple.Apns.Feedback.Test.csproj ├── Program.cs └── Properties │ └── AssemblyInfo.cs ├── JdSoft.Apple.Apns.Feedback ├── Feedback.cs ├── FeedbackService.cs ├── JdSoft.Apple.Apns.Feedback.csproj └── Properties │ └── AssemblyInfo.cs ├── JdSoft.Apple.Apns.Notifications.JsonTest ├── AssemblyInfo.cs ├── JdSoft.Apple.Apns.Notifications.JsonTest.csproj ├── JdSoft.Apple.Apns.Notifications.JsonTest.pidb └── Main.cs ├── JdSoft.Apple.Apns.Notifications.Test ├── JdSoft.Apple.Apns.Notifications.Test.csproj ├── Program.cs └── Properties │ └── AssemblyInfo.cs ├── JdSoft.Apple.Apns.Notifications ├── BadDeviceTokenException.cs ├── JdSoft.Apple.Apns.Notifications.csproj ├── Notification.cs ├── NotificationAlert.cs ├── NotificationBatchException.cs ├── NotificationChannel.cs ├── NotificationConnection.cs ├── NotificationDeliveryError.cs ├── NotificationException.cs ├── NotificationLengthException.cs ├── NotificationPayload.cs ├── NotificationService.cs ├── NotificationServiceDistributionType.cs ├── Properties │ └── AssemblyInfo.cs ├── ThreadSafeQueue.cs └── packages.config ├── JdSoft.Apple.Apns.sln ├── JdSoft.Apple.Apns.vsmdi ├── JdSoft.Apple.AppStore.Test ├── JdSoft.Apple.AppStore.Test.csproj ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ └── Resources.resources ├── frmReceiptData.Designer.cs ├── frmReceiptData.cs ├── frmReceiptData.resources └── frmReceiptData.resx ├── JdSoft.Apple.AppStore ├── JdSoft.Apple.AppStore.csproj ├── Properties │ └── AssemblyInfo.cs ├── Receipt.cs ├── ReceiptVerification.cs └── packages.config ├── Local.testsettings ├── README.md ├── References └── Newtonsoft.Json.Compact.dll ├── Tests.Notifications ├── Properties │ └── AssemblyInfo.cs ├── Tests.Notifications.csproj └── UnitTest1.cs ├── TraceAndTestImpact.testsettings └── nuget ├── apns-icon.png ├── apns-sharp.1.0.4.1.nupkg └── apns-sharp.1.0.4.3.nupkg /.gitignore: -------------------------------------------------------------------------------- 1 | debug/* 2 | release/* 3 | Bin 4 | Bin/* 5 | obj/* 6 | *.csuser 7 | *.suo 8 | *.ReSharper 9 | *.user 10 | *.tmp 11 | *.db 12 | *.orig 13 | *.bak 14 | *.zip 15 | *.rar 16 | .hg/* 17 | bin 18 | bin/* 19 | obj 20 | TestResults/* 21 | Logs 22 | Logs/* 23 | ErrorLogs 24 | ErrorLogs/* 25 | packages 26 | packages/* 27 | _ReSharper* 28 | _ReSharper*/* 29 | *.pidb 30 | App_Data/HelpIndex 31 | *.userprefs 32 | AutoBidLogs 33 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Feedback.Test/JdSoft.Apple.Apns.Feedback.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {46CAEA10-404B-4E0A-B1A0-C1B128B55A8C} 9 | Exe 10 | Properties 11 | JdSoft.Apple.Apns.Feedback.Test 12 | JdSoft.Apple.Apns.Feedback.Test 13 | v3.5 14 | 512 15 | 16 | 17 | 18 | 19 | 3.5 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {B87C2903-2B6B-49FF-B735-630A5CB69521} 50 | JdSoft.Apple.Apns.Feedback 51 | 52 | 53 | 54 | 61 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Feedback.Test/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using JdSoft.Apple.Apns.Feedback; 5 | 6 | namespace JdSoft.Apple.Apns.Feedback.Test 7 | { 8 | class Program 9 | { 10 | [STAThread] 11 | static void Main(string[] args) 12 | { 13 | //Variables you may need to edit: 14 | //--------------------------------- 15 | 16 | //True if you are using sandbox certificate, or false if using production 17 | bool sandbox = true; 18 | 19 | //Put your PKCS12 .p12 or .pfx filename here. 20 | // Assumes it is in the same directory as your app 21 | string p12File = "apn_developer_identity.p12"; 22 | 23 | //This is the password that you protected your p12File 24 | // If you did not use a password, set it as null or an empty string 25 | string p12FilePassword = "password"; 26 | 27 | 28 | //Actual Code starts below: 29 | //-------------------------------- 30 | 31 | //Get the filename assuming the file is in the same directory as this app 32 | string p12Filename = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, p12File); 33 | 34 | //Create the feedback service consumer 35 | FeedbackService service = new FeedbackService(sandbox, p12Filename, p12FilePassword); 36 | 37 | //Wireup the events 38 | service.Error += new FeedbackService.OnError(service_Error); 39 | service.Feedback += new FeedbackService.OnFeedback(service_Feedback); 40 | 41 | //Run it. This actually connects and receives the feedback 42 | // the Feedback event will fire for each feedback object 43 | // received from the server 44 | service.Run(); 45 | 46 | Console.WriteLine("Done."); 47 | Console.WriteLine("Cleaning up..."); 48 | 49 | //Clean up 50 | service.Dispose(); 51 | 52 | Console.WriteLine("Press enter to exit..."); 53 | Console.ReadLine(); 54 | } 55 | 56 | static void service_Feedback(object sender, Feedback feedback) 57 | { 58 | Console.WriteLine(string.Format("Feedback - Timestamp: {0} - DeviceId: {1}", feedback.Timestamp, feedback.DeviceToken)); 59 | } 60 | 61 | static void service_Error(object sender, Exception ex) 62 | { 63 | Console.WriteLine(string.Format("Error: {0}", ex.Message)); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Feedback.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("JdSoft.Apple.Apns.Feedback.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("JdSoft.Apple.Apns.Feedback.Test")] 13 | [assembly: AssemblyCopyright("Copyright © 2009")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("2872e317-d9c4-44f4-9257-4f2266a7bf51")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.4.4")] 36 | [assembly: AssemblyFileVersion("1.0.4.4")] 37 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Feedback/Feedback.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JdSoft.Apple.Apns.Feedback 6 | { 7 | /// 8 | /// Feedback object 9 | /// 10 | public class Feedback 11 | { 12 | 13 | /// 14 | /// Constructor 15 | /// 16 | public Feedback() 17 | { 18 | this.DeviceToken = string.Empty; 19 | this.Timestamp = DateTime.MinValue; 20 | } 21 | 22 | /// 23 | /// Device Token string in hex form without any spaces or dashes 24 | /// 25 | public string DeviceToken 26 | { 27 | get; 28 | set; 29 | } 30 | 31 | /// 32 | /// Timestamp of the Feedback for when Apple received the notice to stop sending notifications to the device 33 | /// 34 | public DateTime Timestamp 35 | { 36 | get; 37 | set; 38 | } 39 | 40 | /// 41 | /// For whatever use you please :) 42 | /// 43 | public object Tag 44 | { 45 | get; 46 | set; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Feedback/FeedbackService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Net.Security; 8 | using System.Security.Cryptography; 9 | using System.Security.Cryptography.X509Certificates; 10 | 11 | namespace JdSoft.Apple.Apns.Feedback 12 | { 13 | /// 14 | /// Feedback Service Consumer 15 | /// 16 | public class FeedbackService : IDisposable 17 | { 18 | #region Delegates and Events 19 | /// 20 | /// Handles General Error Exceptions 21 | /// 22 | /// FeedbackService Instance 23 | /// Exception Instance 24 | public delegate void OnError(object sender, Exception ex); 25 | /// 26 | /// Occurs when a General Exception is thrown 27 | /// 28 | public event OnError Error; 29 | 30 | /// 31 | /// Handles Feedback Received Event 32 | /// 33 | /// FeedbackService Instance 34 | /// Feedback Instance 35 | public delegate void OnFeedback(object sender, Feedback feedback); 36 | /// 37 | /// Occurs when Feedback Information is Received 38 | /// 39 | public event OnFeedback Feedback; 40 | #endregion 41 | 42 | #region Constants 43 | private const string hostSandbox = "feedback.sandbox.push.apple.com"; 44 | private const string hostProduction = "feedback.push.apple.com"; 45 | #endregion 46 | 47 | #region Instance Variables 48 | private bool disposing; 49 | private Encoding encoding; 50 | private X509Certificate certificate; 51 | private X509CertificateCollection certificates; 52 | private string P12FilePassword; 53 | private TcpClient apnsClient; 54 | private SslStream apnsStream; 55 | #endregion 56 | 57 | #region Constructors 58 | /// 59 | /// Constructor 60 | /// 61 | /// Push Notification Feedback Host 62 | /// Push Notification Feedback Port 63 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 64 | public FeedbackService(string host, int port, string p12File) 65 | { 66 | Id = System.Guid.NewGuid().ToString("N"); 67 | Host = host; 68 | Port = port; 69 | P12File = p12File; 70 | ConnectAttempts = 3; 71 | ReconnectDelay = 10000; 72 | } 73 | 74 | /// 75 | /// Constructor 76 | /// 77 | /// Push Notification Feedback Host 78 | /// Push Notification Feedback Port 79 | /// Byte array representation of PKCS12 .p12 or .pfx File containing Public and Private Keys 80 | public FeedbackService(string host, int port, byte[] p12FileBytes) 81 | { 82 | Id = System.Guid.NewGuid().ToString("N"); 83 | Host = host; 84 | Port = port; 85 | // Fixed by danielgindi@gmail.com : 86 | // The default is UserKeySet, which has caused internal encryption errors, 87 | // Because of lack of permissions on most hosting services. 88 | // So MachineKeySet should be used instead. 89 | certificate = new X509Certificate2(p12FileBytes, (string)null, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); 90 | ConnectAttempts = 3; 91 | ReconnectDelay = 10000; 92 | } 93 | 94 | /// 95 | /// Constructor 96 | /// 97 | /// Push Notification Feedback Host 98 | /// Push Notification Feedback Port 99 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 100 | /// Password protecting the p12File 101 | public FeedbackService(string host, int port, string p12File, string p12FilePassword) 102 | { 103 | Id = System.Guid.NewGuid().ToString("N"); 104 | Host = host; 105 | Port = port; 106 | P12File = p12File; 107 | P12FilePassword = p12FilePassword; 108 | ConnectAttempts = 3; 109 | ReconnectDelay = 10000; 110 | } 111 | 112 | /// 113 | /// Constructor 114 | /// 115 | /// Push Notification Feedback Host 116 | /// Push Notification Feedback Port 117 | /// Byte array representation of PKCS12 .p12 or .pfx File containing Public and Private Keys 118 | /// Password protecting the p12File 119 | public FeedbackService(string host, int port, byte[] p12FileBytes, string p12FilePassword) 120 | { 121 | Id = System.Guid.NewGuid().ToString("N"); 122 | Host = host; 123 | Port = port; 124 | // Fixed by danielgindi@gmail.com : 125 | // The default is UserKeySet, which has caused internal encryption errors, 126 | // Because of lack of permissions on most hosting services. 127 | // So MachineKeySet should be used instead. 128 | certificate = new X509Certificate2(p12FileBytes, p12FilePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); 129 | P12FilePassword = p12FilePassword; 130 | ConnectAttempts = 3; 131 | ReconnectDelay = 10000; 132 | } 133 | 134 | /// 135 | /// Constructor 136 | /// 137 | /// Boolean flag indicating whether the default Sandbox or Production Host and Port should be used 138 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 139 | public FeedbackService(bool sandbox, string p12File) 140 | { 141 | Id = System.Guid.NewGuid().ToString("N"); 142 | Host = sandbox ? hostSandbox : hostProduction; 143 | Port = 2196; 144 | P12File = p12File; 145 | ConnectAttempts = 3; 146 | } 147 | 148 | /// 149 | /// Constructor 150 | /// 151 | /// Boolean flag indicating whether the default Sandbox or Production Host and Port should be used 152 | /// Byte array representation of PKCS12 .p12 or .pfx File containing Public and Private Keys 153 | public FeedbackService(bool sandbox, byte[] p12FileBytes) 154 | { 155 | Id = System.Guid.NewGuid().ToString("N"); 156 | Host = sandbox ? hostSandbox : hostProduction; 157 | Port = 2196; 158 | // Fixed by danielgindi@gmail.com : 159 | // The default is UserKeySet, which has caused internal encryption errors, 160 | // Because of lack of permissions on most hosting services. 161 | // So MachineKeySet should be used instead. 162 | certificate = new X509Certificate2(p12FileBytes, (string)null, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); 163 | ConnectAttempts = 3; 164 | } 165 | 166 | /// 167 | /// Constructor 168 | /// 169 | /// Boolean flag indicating whether the default Sandbox or Production Host and Port should be used 170 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 171 | /// Password protecting the p12File 172 | public FeedbackService(bool sandbox, string p12File, string p12FilePassword) 173 | { 174 | Id = System.Guid.NewGuid().ToString("N"); 175 | Host = sandbox ? hostSandbox : hostProduction; 176 | Port = 2196; 177 | P12File = p12File; 178 | P12FilePassword = p12FilePassword; 179 | ConnectAttempts = 3; 180 | ReconnectDelay = 10000; 181 | } 182 | 183 | /// 184 | /// Constructor 185 | /// 186 | /// Boolean flag indicating whether the default Sandbox or Production Host and Port should be used 187 | /// Byte array representation of PKCS12 .p12 or .pfx File containing Public and Private Keys 188 | /// Password protecting the p12File 189 | public FeedbackService(bool sandbox, byte[] p12FileBytes, string p12FilePassword) 190 | { 191 | Id = System.Guid.NewGuid().ToString("N"); 192 | Host = sandbox ? hostSandbox : hostProduction; 193 | Port = 2196; 194 | // Fixed by danielgindi@gmail.com : 195 | // The default is UserKeySet, which has caused internal encryption errors, 196 | // Because of lack of permissions on most hosting services. 197 | // So MachineKeySet should be used instead. 198 | certificate = new X509Certificate2(p12FileBytes, p12FilePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); 199 | P12FilePassword = p12FilePassword; 200 | ConnectAttempts = 3; 201 | ReconnectDelay = 10000; 202 | } 203 | 204 | #endregion 205 | 206 | #region Public Methods 207 | /// 208 | /// Ensures the Connection is closed and all resources are cleaned up 209 | /// 210 | public void Dispose() 211 | { 212 | disposing = true; 213 | 214 | ensureDisconnected(); 215 | } 216 | 217 | /// 218 | /// Initiates the Connection to the Feedback Server and receives all Feedback data then closes the connection 219 | /// 220 | public void Run() 221 | { 222 | disposing = false; 223 | 224 | encoding = Encoding.ASCII; 225 | 226 | // certificate will already be set if one of the constructors that takes a byte array was used. 227 | if (certificate == null) 228 | { 229 | // Fixed by danielgindi@gmail.com : 230 | // The default is UserKeySet, which has caused internal encryption errors, 231 | // Because of lack of permissions on most hosting services. 232 | // So MachineKeySet should be used instead. 233 | certificate = new X509Certificate2(System.IO.File.ReadAllBytes(P12File), P12FilePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); 234 | } 235 | 236 | certificates = new X509CertificateCollection(); 237 | certificates.Add(certificate); 238 | 239 | if (ensureConnected() && !disposing) 240 | { 241 | //Set up 242 | byte[] buffer = new byte[38]; 243 | int recd = 0; 244 | DateTime minTimestamp = DateTime.Now.AddYears(-1); 245 | 246 | //Get the first feedback 247 | recd = apnsStream.Read(buffer, 0, buffer.Length); 248 | 249 | //Continue while we have results and are not disposing 250 | while (recd > 0 && !disposing) 251 | { 252 | try 253 | { 254 | Feedback fb = new Feedback(); 255 | 256 | //Get our seconds since 1970 ? 257 | byte[] bSeconds = new byte[4]; 258 | byte[] bDeviceToken = new byte[32]; 259 | 260 | Array.Copy(buffer, 0, bSeconds, 0, 4); 261 | 262 | //Check endianness 263 | if (BitConverter.IsLittleEndian) 264 | Array.Reverse(bSeconds); 265 | 266 | int tSeconds = BitConverter.ToInt32(bSeconds, 0); 267 | 268 | //Add seconds since 1970 to that date, in UTC and then get it locally 269 | fb.Timestamp = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(tSeconds).ToLocalTime(); 270 | 271 | 272 | //Now copy out the device token 273 | Array.Copy(buffer, 6, bDeviceToken, 0, 32); 274 | 275 | fb.DeviceToken = BitConverter.ToString(bDeviceToken).Replace("-", "").ToLower().Trim(); 276 | 277 | //Make sure we have a good feedback tuple 278 | if (fb.DeviceToken.Length == 64 279 | && fb.Timestamp > minTimestamp 280 | && this.Feedback != null) 281 | { 282 | //Raise event 283 | this.Feedback(this, fb); 284 | } 285 | 286 | } 287 | catch (Exception ex) 288 | { 289 | if (this.Error != null) 290 | this.Error(this, ex); 291 | } 292 | 293 | //Clear our array to reuse it 294 | Array.Clear(buffer, 0, buffer.Length); 295 | 296 | //Read the next feedback 297 | recd = apnsStream.Read(buffer, 0, buffer.Length); 298 | } 299 | } 300 | 301 | ensureDisconnected(); 302 | } 303 | #endregion 304 | 305 | #region Properties 306 | /// 307 | /// Gets the Unique Id for this Instance 308 | /// 309 | public string Id 310 | { 311 | get; 312 | private set; 313 | } 314 | 315 | /// 316 | /// Gets the Push Notification Feedback Host 317 | /// 318 | public string Host 319 | { 320 | get; 321 | private set; 322 | } 323 | 324 | /// 325 | /// Gets the Push Notification Feedback Port 326 | /// 327 | public int Port 328 | { 329 | get; 330 | private set; 331 | } 332 | 333 | /// 334 | /// Gets the PKCS12 .p12 or .pfx File Used 335 | /// 336 | public string P12File 337 | { 338 | get; 339 | private set; 340 | } 341 | 342 | /// 343 | /// How many times to try connecting before giving up 344 | /// 345 | public int ConnectAttempts 346 | { 347 | get; 348 | set; 349 | } 350 | 351 | /// 352 | /// Number of milliseconds to wait between connection attempts 353 | /// 354 | public int ReconnectDelay 355 | { 356 | get; 357 | set; 358 | } 359 | 360 | /// 361 | /// For whatever use you please :) 362 | /// 363 | public object Tag 364 | { 365 | get; 366 | set; 367 | } 368 | #endregion 369 | 370 | #region Private Methods 371 | private bool ensureConnected() 372 | { 373 | bool connected = false; 374 | 375 | if (apnsStream == null || !apnsStream.CanWrite) 376 | connected = false; 377 | 378 | if (apnsClient == null || !apnsClient.Connected) 379 | connected = false; 380 | 381 | int tries = 0; 382 | 383 | while (!connected && !disposing && tries < ConnectAttempts) 384 | { 385 | tries++; 386 | 387 | try 388 | { 389 | apnsClient = new TcpClient(Host, Port); 390 | 391 | apnsStream = new SslStream(apnsClient.GetStream(), true, 392 | new RemoteCertificateValidationCallback(validateServerCertificate), 393 | new LocalCertificateSelectionCallback(selectLocalCertificate)); 394 | 395 | apnsStream.AuthenticateAsClient(Host, 396 | certificates, 397 | System.Security.Authentication.SslProtocols.Tls, 398 | false); 399 | 400 | connected = apnsStream.CanWrite; 401 | } 402 | catch (Exception ex) 403 | { 404 | if (this.Error != null) 405 | this.Error(this, ex); 406 | 407 | connected = false; 408 | 409 | } 410 | 411 | if (!connected) 412 | { 413 | int wait = ReconnectDelay; 414 | int waited = 0; 415 | 416 | while (waited < wait && !disposing) 417 | { 418 | System.Threading.Thread.Sleep(250); 419 | waited += 250; 420 | } 421 | } 422 | 423 | } 424 | 425 | return connected; 426 | } 427 | 428 | private void ensureDisconnected() 429 | { 430 | try { apnsStream.Close(); } 431 | catch { } 432 | 433 | try { apnsStream.Dispose(); } 434 | catch { } 435 | 436 | try { apnsClient.Client.Shutdown(SocketShutdown.Both); } 437 | catch { } 438 | 439 | try { apnsClient.Client.Close(); } 440 | catch { } 441 | 442 | try { apnsClient.Close(); } 443 | catch { } 444 | } 445 | 446 | 447 | private bool validateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) 448 | { 449 | return true; // Dont care about server's cert 450 | } 451 | 452 | private X509Certificate selectLocalCertificate(object sender, string targetHost, X509CertificateCollection localCertificates, 453 | X509Certificate remoteCertificate, string[] acceptableIssuers) 454 | { 455 | return certificate; 456 | } 457 | #endregion 458 | } 459 | } 460 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Feedback/JdSoft.Apple.Apns.Feedback.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {B87C2903-2B6B-49FF-B735-630A5CB69521} 9 | Library 10 | Properties 11 | JdSoft.Apple.Apns.Feedback 12 | JdSoft.Apns.Feedback 13 | v3.5 14 | 512 15 | 16 | 17 | 18 | 19 | 3.5 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 56 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Feedback/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("JdSoft.Apns.Feedback")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("JdSoft.Apns.Feedback")] 13 | [assembly: AssemblyCopyright("Copyright © 2009")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("46fea548-9a0c-4863-9c89-3f3098aed44c")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.4.4")] 36 | [assembly: AssemblyFileVersion("1.0.4.4")] 37 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications.JsonTest/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle("JdSoft.Apple.Test")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("")] 12 | [assembly: AssemblyCopyright("")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | [assembly: AssemblyVersion("1.0.4.4")] 21 | 22 | // The following attributes are used to specify the signing key for the assembly, 23 | // if desired. See the Mono documentation for more information about signing. 24 | 25 | [assembly: AssemblyDelaySign(false)] 26 | [assembly: AssemblyKeyFile("")] 27 | [assembly: AssemblyFileVersion("1.0.4.4")] 28 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications.JsonTest/JdSoft.Apple.Apns.Notifications.JsonTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {1A7ACE5F-EF8A-4C5E-87B5-CBBFA432DFE4} 9 | Exe 10 | JdSoft.Apple.Test 11 | v3.5 12 | 13 | 14 | 15 | 16 | 3.5 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug 23 | DEBUG 24 | prompt 25 | 4 26 | 27 | 28 | none 29 | false 30 | bin\Release 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | {4CFB9AA8-55F8-46DC-B7BD-9E18B9939110} 45 | JdSoft.Apple.Apns.Notifications 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications.JsonTest/JdSoft.Apple.Apns.Notifications.JsonTest.pidb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Redth/APNS-Sharp/e30a9990a466f8fc0d054224bbf8d599a67311f3/JdSoft.Apple.Apns.Notifications.JsonTest/JdSoft.Apple.Apns.Notifications.JsonTest.pidb -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications.JsonTest/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JdSoft.Apple.Apns.Notifications; 3 | 4 | namespace JdSoft.Apple.Test 5 | { 6 | class MainClass 7 | { 8 | [STAThread] 9 | public static void Main(string[] args) 10 | { 11 | //Empty aps, just custom 12 | Notification notification = new Notification(); 13 | notification.Payload.AddCustom("bar", 42); 14 | 15 | Console.WriteLine(notification.ToString()); 16 | Console.WriteLine(); 17 | System.Windows.Forms.Clipboard.SetText(notification.ToString()); 18 | Console.ReadLine(); 19 | 20 | 21 | 22 | //More complex notification 23 | Notification notification1 = new Notification(); 24 | 25 | notification1.Payload.Alert.LocalizedKey = "GAME_PLAY_REQUEST_FORMAT"; 26 | notification1.Payload.Alert.AddLocalizedArgs("Jenna", "Frank"); 27 | 28 | notification1.Payload.Badge = 5; 29 | 30 | notification1.Payload.Sound = "chime"; 31 | 32 | notification1.Payload.AddCustom("acme1", "bar"); 33 | notification1.Payload.AddCustom("acme2", "bang", "whiz"); 34 | 35 | 36 | Console.WriteLine(notification1.ToString()); 37 | Console.WriteLine(); 38 | System.Windows.Forms.Clipboard.SetText(notification1.ToString()); 39 | Console.ReadLine(); 40 | 41 | 42 | 43 | ////Simpler notification 44 | Notification notification2 = new Notification(); 45 | 46 | notification2.Payload.Alert.Body = "Bob wants to play poker"; 47 | notification2.Payload.Alert.ActionLocalizedKey = "PLAY"; 48 | 49 | notification2.Payload.Badge = 5; 50 | 51 | notification2.Payload.Sound = "chime"; 52 | 53 | notification2.Payload.AddCustom("acme1", "bar"); 54 | notification2.Payload.AddCustom("acme2", "bang", "whiz"); 55 | 56 | 57 | Console.WriteLine(notification2.ToString()); 58 | Console.WriteLine(); 59 | System.Windows.Forms.Clipboard.SetText(notification2.ToString()); 60 | Console.ReadLine(); 61 | 62 | 63 | ////Very simple notification 64 | Notification notification3 = new Notification(); 65 | 66 | notification3.Payload.Alert.Body = "Bob wants to play poker"; 67 | 68 | notification3.Payload.Badge = 5; 69 | 70 | notification3.Payload.Sound = "chime"; 71 | 72 | Console.WriteLine(notification3.ToString()); 73 | Console.WriteLine(); 74 | System.Windows.Forms.Clipboard.SetText(notification3.ToString()); 75 | Console.ReadLine(); 76 | 77 | 78 | ////Badge update and sound only 79 | Notification notification4 = new Notification(); 80 | 81 | notification4.Payload.Badge = 5; 82 | 83 | notification4.Payload.Sound = "chime"; 84 | notification4.Payload.AddCustom("test", 4, 2, 12); 85 | 86 | Console.WriteLine(notification4.ToString()); 87 | System.Windows.Forms.Clipboard.SetText(notification4.ToString()); 88 | 89 | 90 | Console.WriteLine("Press enter to exit..."); 91 | Console.ReadLine(); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications.Test/JdSoft.Apple.Apns.Notifications.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {3500A7AA-A8C8-4221-8265-48B02ECF2463} 9 | Exe 10 | Properties 11 | JdSoft.Apple.Apns.Test 12 | JdSoft.Apns.Test 13 | v3.5 14 | 512 15 | 16 | 17 | 18 | 19 | 3.5 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | {4CFB9AA8-55F8-46DC-B7BD-9E18B9939110} 51 | JdSoft.Apple.Apns.Notifications 52 | 53 | 54 | 55 | 62 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications.Test/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using JdSoft.Apple.Apns.Notifications; 5 | 6 | namespace JdSoft.Apple.Apns.Test 7 | { 8 | class Program 9 | { 10 | [STAThread] 11 | static void Main(string[] args) 12 | { 13 | //Variables you may need to edit: 14 | //--------------------------------- 15 | 16 | //True if you are using sandbox certificate, or false if using production 17 | bool sandbox = true; 18 | 19 | //Put your device token in here 20 | string testDeviceToken = "fe58fc8f527c363d1b775dca133e04bff24dc5032d08836992395cc56bfa62ef"; 21 | 22 | //Put your PKCS12 .p12 or .pfx filename here. 23 | // Assumes it is in the same directory as your app 24 | string p12File = "apn_developer_identity.p12"; 25 | 26 | //This is the password that you protected your p12File 27 | // If you did not use a password, set it as null or an empty string 28 | string p12FilePassword = "yourpassword"; 29 | 30 | //Number of notifications to send 31 | int count = 3; 32 | 33 | //Number of milliseconds to wait in between sending notifications in the loop 34 | // This is just to demonstrate that the APNS connection stays alive between messages 35 | int sleepBetweenNotifications = 15000; 36 | 37 | 38 | //Actual Code starts below: 39 | //-------------------------------- 40 | 41 | string p12Filename = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, p12File); 42 | 43 | NotificationService service = new NotificationService(sandbox, p12Filename, p12FilePassword, 1); 44 | 45 | service.SendRetries = 5; //5 retries before generating notificationfailed event 46 | service.ReconnectDelay = 5000; //5 seconds 47 | 48 | service.Error += new NotificationService.OnError(service_Error); 49 | service.NotificationTooLong += new NotificationService.OnNotificationTooLong(service_NotificationTooLong); 50 | 51 | service.BadDeviceToken += new NotificationService.OnBadDeviceToken(service_BadDeviceToken); 52 | service.NotificationFailed += new NotificationService.OnNotificationFailed(service_NotificationFailed); 53 | service.NotificationSuccess += new NotificationService.OnNotificationSuccess(service_NotificationSuccess); 54 | service.Connecting += new NotificationService.OnConnecting(service_Connecting); 55 | service.Connected += new NotificationService.OnConnected(service_Connected); 56 | service.Disconnected += new NotificationService.OnDisconnected(service_Disconnected); 57 | 58 | //The notifications will be sent like this: 59 | // Testing: 1... 60 | // Testing: 2... 61 | // Testing: 3... 62 | // etc... 63 | for (int i = 1; i <= count; i++) 64 | { 65 | //Create a new notification to send 66 | Notification alertNotification = new Notification(testDeviceToken); 67 | 68 | alertNotification.Payload.Alert.Body = string.Format("Testing {0}...", i); 69 | alertNotification.Payload.Sound = "default"; 70 | alertNotification.Payload.Badge = i; 71 | 72 | //Queue the notification to be sent 73 | if (service.QueueNotification(alertNotification)) 74 | Console.WriteLine("Notification Queued!"); 75 | else 76 | Console.WriteLine("Notification Failed to be Queued!"); 77 | 78 | //Sleep in between each message 79 | if (i < count) 80 | { 81 | Console.WriteLine("Sleeping " + sleepBetweenNotifications + " milliseconds before next Notification..."); 82 | System.Threading.Thread.Sleep(sleepBetweenNotifications); 83 | } 84 | } 85 | 86 | Console.WriteLine("Cleaning Up..."); 87 | 88 | //First, close the service. 89 | //This ensures any queued notifications get sent befor the connections are closed 90 | service.Close(); 91 | 92 | //Clean up 93 | service.Dispose(); 94 | 95 | Console.WriteLine("Done!"); 96 | Console.WriteLine("Press enter to exit..."); 97 | Console.ReadLine(); 98 | } 99 | 100 | static void service_BadDeviceToken(object sender, BadDeviceTokenException ex) 101 | { 102 | Console.WriteLine("Bad Device Token: {0}", ex.Message); 103 | } 104 | 105 | static void service_Disconnected(object sender) 106 | { 107 | Console.WriteLine("Disconnected..."); 108 | } 109 | 110 | static void service_Connected(object sender) 111 | { 112 | Console.WriteLine("Connected..."); 113 | } 114 | 115 | static void service_Connecting(object sender) 116 | { 117 | Console.WriteLine("Connecting..."); 118 | } 119 | 120 | static void service_NotificationTooLong(object sender, NotificationLengthException ex) 121 | { 122 | Console.WriteLine(string.Format("Notification Too Long: {0}", ex.Notification.ToString())); 123 | } 124 | 125 | static void service_NotificationSuccess(object sender, Notification notification) 126 | { 127 | Console.WriteLine(string.Format("Notification Success: {0}", notification.ToString())); 128 | } 129 | 130 | static void service_NotificationFailed(object sender, Notification notification) 131 | { 132 | Console.WriteLine(string.Format("Notification Failed: {0}", notification.ToString())); 133 | } 134 | 135 | static void service_Error(object sender, Exception ex) 136 | { 137 | Console.WriteLine(string.Format("Error: {0}", ex.Message)); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("JdSoft.Apns.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("JdSoft.Apns.Test")] 13 | [assembly: AssemblyCopyright("Copyright © 2009")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("38d47213-ef7e-45d2-a5e8-516aecd83676")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.4.4")] 36 | [assembly: AssemblyFileVersion("1.0.4.4")] 37 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications/BadDeviceTokenException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JdSoft.Apple.Apns.Notifications 6 | { 7 | public class BadDeviceTokenException : Exception 8 | { 9 | public BadDeviceTokenException(string deviceToken) 10 | : base(string.Format("Device Token Length ({0}) Is not the required length of {1} characters!", deviceToken.Length, Notification.DEVICE_TOKEN_STRING_SIZE)) 11 | { 12 | this.DeviceToken = deviceToken; 13 | } 14 | 15 | public string DeviceToken 16 | { 17 | get; 18 | private set; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications/JdSoft.Apple.Apns.Notifications.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {4CFB9AA8-55F8-46DC-B7BD-9E18B9939110} 9 | Library 10 | Properties 11 | JdSoft.Apple.Apns.Notifications 12 | JdSoft.Apns.Notifications 13 | v3.5 14 | 512 15 | 16 | 17 | 18 | 19 | 3.5 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | ..\packages\Newtonsoft.Json.4.0.8\lib\net35\Newtonsoft.Json.dll 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 73 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications/Notification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | using System.Text; 6 | 7 | namespace JdSoft.Apple.Apns.Notifications 8 | { 9 | public class Notification 10 | { 11 | public string DeviceToken { get; set; } 12 | public NotificationPayload Payload { get; set; } 13 | /// 14 | /// The expiration date after which Apple will no longer store and forward this push notification. 15 | /// If no value is provided, an assumed value of one year from now is used. If you do not wish 16 | /// for Apple to store and forward, set this value to Notification.DoNotStore. 17 | /// 18 | public DateTime? Expiration { get; set; } 19 | public const int DEVICE_TOKEN_BINARY_SIZE = 32; 20 | public const int DEVICE_TOKEN_STRING_SIZE = 64; 21 | public const int MAX_PAYLOAD_SIZE = 256; 22 | public static readonly DateTime DoNotStore = DateTime.MinValue; 23 | private static readonly DateTime UNIX_EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 24 | 25 | public Notification() 26 | { 27 | DeviceToken = string.Empty; 28 | Payload = new NotificationPayload(); 29 | } 30 | 31 | public Notification(string deviceToken) 32 | { 33 | if (!string.IsNullOrEmpty(deviceToken) && deviceToken.Length != DEVICE_TOKEN_STRING_SIZE) 34 | throw new BadDeviceTokenException(deviceToken); 35 | 36 | DeviceToken = deviceToken; 37 | Payload = new NotificationPayload(); 38 | } 39 | 40 | public Notification(string deviceToken, NotificationPayload payload) 41 | { 42 | if (!string.IsNullOrEmpty(deviceToken) && deviceToken.Length != DEVICE_TOKEN_STRING_SIZE) 43 | throw new BadDeviceTokenException(deviceToken); 44 | 45 | DeviceToken = deviceToken; 46 | Payload = payload; 47 | } 48 | 49 | /// 50 | /// Object for storing state. This does not affect the actual notification! 51 | /// 52 | public object Tag 53 | { 54 | get; 55 | set; 56 | } 57 | 58 | public override string ToString() 59 | { 60 | return Payload.ToJson(); 61 | } 62 | 63 | public static String HexEncode(byte[] data) 64 | { 65 | int len = data.Length; 66 | 67 | if (len == 0) 68 | { 69 | throw new BadDeviceTokenException(@""); 70 | } 71 | 72 | StringBuilder hexString = new StringBuilder(len); 73 | 74 | foreach (byte tokenByte in data) 75 | { 76 | hexString.Append(tokenByte.ToString(@"x2")); 77 | } 78 | 79 | return hexString.ToString(); 80 | } 81 | 82 | public byte[] ToBytes() 83 | { 84 | // Without reading the response which would make any identifier useful, it seems silly to 85 | // expose the value in the object model, although that would be easy enough to do. For 86 | // now we'll just use zero. 87 | int identifier = 0; 88 | byte[] identifierBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(identifier)); 89 | 90 | // APNS will not store-and-forward a notification with no expiry, so set it one year in the future 91 | // if the client does not provide it. 92 | int expiryTimeStamp = -1; 93 | if (Expiration != DoNotStore) 94 | { 95 | DateTime concreteExpireDateUtc = (Expiration ?? DateTime.UtcNow.AddMonths(1)).ToUniversalTime(); 96 | TimeSpan epochTimeSpan = concreteExpireDateUtc - UNIX_EPOCH; 97 | expiryTimeStamp = (int)epochTimeSpan.TotalSeconds; 98 | } 99 | 100 | byte[] expiry = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(expiryTimeStamp)); 101 | 102 | 103 | byte[] deviceToken = new byte[DeviceToken.Length / 2]; 104 | for (int i = 0; i < deviceToken.Length; i++) 105 | deviceToken[i] = byte.Parse(DeviceToken.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber); 106 | 107 | if (deviceToken.Length != DEVICE_TOKEN_BINARY_SIZE) 108 | throw new BadDeviceTokenException(DeviceToken); 109 | 110 | 111 | byte[] deviceTokenSize = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Convert.ToInt16(deviceToken.Length))); 112 | 113 | byte[] payload = Encoding.UTF8.GetBytes(Payload.ToJson()); 114 | if (payload.Length > MAX_PAYLOAD_SIZE) 115 | { 116 | int newSize = Payload.Alert.Body.Length - (payload.Length - MAX_PAYLOAD_SIZE); 117 | if (newSize > 0) 118 | { 119 | Payload.Alert.Body = Payload.Alert.Body.Substring(0, newSize); 120 | payload = Encoding.UTF8.GetBytes(Payload.ToString()); 121 | } 122 | else 123 | { 124 | do 125 | { 126 | Payload.Alert.Body = Payload.Alert.Body.Remove(Payload.Alert.Body.Length - 1); 127 | payload = Encoding.UTF8.GetBytes(Payload.ToString()); 128 | } 129 | while (payload.Length > MAX_PAYLOAD_SIZE && !string.IsNullOrEmpty(Payload.Alert.Body)); 130 | } 131 | 132 | if (payload.Length > MAX_PAYLOAD_SIZE) 133 | throw new NotificationLengthException(this); 134 | } 135 | byte[] payloadSize = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Convert.ToInt16(payload.Length))); 136 | 137 | int bufferSize = sizeof(Byte) + deviceTokenSize.Length + deviceToken.Length + payloadSize.Length + payload.Length; 138 | byte[] buffer = new byte[bufferSize]; 139 | 140 | List notificationParts = new List(); 141 | 142 | notificationParts.Add(new byte[] { 0x01 }); // Enhanced notification format command 143 | notificationParts.Add(identifierBytes); 144 | notificationParts.Add(expiry); 145 | notificationParts.Add(deviceTokenSize); 146 | notificationParts.Add(deviceToken); 147 | notificationParts.Add(payloadSize); 148 | notificationParts.Add(payload); 149 | 150 | return BuildBufferFrom(notificationParts); 151 | } 152 | 153 | private byte[] BuildBufferFrom(IList bufferParts) 154 | { 155 | int bufferSize = 0; 156 | for (int i = 0; i < bufferParts.Count; i++) 157 | bufferSize += bufferParts[i].Length; 158 | 159 | byte[] buffer = new byte[bufferSize]; 160 | int position = 0; 161 | for (int i = 0; i < bufferParts.Count; i++) 162 | { 163 | byte[] part = bufferParts[i]; 164 | Buffer.BlockCopy(bufferParts[i], 0, buffer, position, part.Length); 165 | position += part.Length; 166 | } 167 | return buffer; 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications/NotificationAlert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Newtonsoft.Json.Linq; 5 | 6 | namespace JdSoft.Apple.Apns.Notifications 7 | { 8 | /// 9 | /// Alert Portion of the Notification Payload 10 | /// 11 | public class NotificationAlert 12 | { 13 | /// 14 | /// Constructor 15 | /// 16 | public NotificationAlert() 17 | { 18 | Body = null; 19 | ActionLocalizedKey = null; 20 | LocalizedKey = null; 21 | LocalizedArgs = new List(); 22 | } 23 | 24 | /// 25 | /// Body Text of the Notification's Alert 26 | /// 27 | public string Body 28 | { 29 | get; 30 | set; 31 | } 32 | 33 | /// 34 | /// Action Button's Localized Key 35 | /// 36 | public string ActionLocalizedKey 37 | { 38 | get; 39 | set; 40 | } 41 | 42 | /// 43 | /// Localized Key 44 | /// 45 | public string LocalizedKey 46 | { 47 | get; 48 | set; 49 | } 50 | 51 | /// 52 | /// Localized Argument List 53 | /// 54 | public List LocalizedArgs 55 | { 56 | get; 57 | set; 58 | } 59 | 60 | public void AddLocalizedArgs(params object[] values) 61 | { 62 | this.LocalizedArgs.AddRange(values); 63 | } 64 | 65 | /// 66 | /// Determines if the Alert is empty and should be excluded from the Notification Payload 67 | /// 68 | public bool IsEmpty 69 | { 70 | get 71 | { 72 | if (!string.IsNullOrEmpty(Body) 73 | || !string.IsNullOrEmpty(ActionLocalizedKey) 74 | || !string.IsNullOrEmpty(LocalizedKey) 75 | || (LocalizedArgs != null && LocalizedArgs.Count > 0)) 76 | return false; 77 | else 78 | return true; 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications/NotificationBatchException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JdSoft.Apple.Apns.Notifications 6 | { 7 | public class NotificationBatchException : Exception 8 | { 9 | internal NotificationBatchException(List errors) 10 | : base(String.Format("There were delivery problems with {0} notifications in batch. See the DeliveryErrors property for details.", errors.Count)) 11 | { 12 | DeliveryErrors = errors.AsReadOnly(); 13 | } 14 | 15 | public IList DeliveryErrors { get; private set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications/NotificationChannel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Security.Cryptography.X509Certificates; 5 | using System.Net.Sockets; 6 | using System.Net.Security; 7 | using System.Threading; 8 | using System.Diagnostics; 9 | using System.Net; 10 | using System.IO; 11 | 12 | namespace JdSoft.Apple.Apns.Notifications 13 | { 14 | /// 15 | /// Apns Notification Channel - NOT THREAD SAFE - Only to be used for specialized scenarios. 16 | /// Use the NotificationService or NotificationConnection instead. 17 | /// 18 | /// 19 | /// The purpose of the NotificationChannel is to provide direct access to the Apns stream 20 | /// with no thread safety and no queueing notifications in memory. 21 | /// 22 | /// If you use this class directly, the onus is on you to protect access to the class with 23 | /// synchronization locks or another scheme to ensure that two threads cannot call the Send 24 | /// method simultaneously. 25 | /// 26 | /// One use for this class would be a messaging/service-oriented scenario where notifications 27 | /// are sent to a messaging endpoint to be sent. 28 | /// 29 | /// If using the NotificationService or NotificationConnection classes, if the application 30 | /// failed between when the notification was enqueued and when the notification was sent, 31 | /// that notification would be lost. 32 | /// 33 | /// In this messaging scenario, these notifications would instead be queued in MSMQ or something 34 | /// similar, and when taken off that queue, would need to be sent immediately, to ensure that 35 | /// no notifications are lost in an in-memory queue. 36 | /// 37 | public class NotificationChannel : IDisposable 38 | { 39 | #region Constants 40 | private const string hostSandbox = "gateway.sandbox.push.apple.com"; 41 | private const string hostProduction = "gateway.push.apple.com"; 42 | #endregion 43 | 44 | #region Delegates and Events 45 | /// 46 | /// Handles General Exceptions 47 | /// 48 | /// NotificationChannel Instance that generated the Exception 49 | /// Exception Instance 50 | public delegate void OnError(object sender, Exception ex); 51 | /// 52 | /// Occurs when a General Error is thrown 53 | /// 54 | public event OnError Error; 55 | 56 | /// 57 | /// Handles Connecting Event 58 | /// 59 | /// NotificationChannel Instance 60 | public delegate void OnConnecting(object sender); 61 | /// 62 | /// Occurs when Connecting to Apple's servers 63 | /// 64 | public event OnConnecting Connecting; 65 | 66 | /// 67 | /// Handles Connected Event 68 | /// 69 | /// NotificationChannel Instance 70 | public delegate void OnConnected(object sender); 71 | /// 72 | /// Occurs when successfully connected and authenticated via SSL to Apple's Servers 73 | /// 74 | public event OnConnected Connected; 75 | 76 | /// 77 | /// Handles Disconnected Event 78 | /// 79 | /// NotificationChannel Instance 80 | public delegate void OnDisconnected(object sender); 81 | /// 82 | /// Occurs when the connection to Apple's Servers has been lost 83 | /// 84 | public event OnDisconnected Disconnected; 85 | #endregion 86 | 87 | #region Instance Variables 88 | private bool connected; 89 | private bool firstConnect; 90 | 91 | private X509Certificate certificate; 92 | private X509CertificateCollection certificates; 93 | private TcpClient apnsClient; 94 | private SslStream apnsStream; 95 | #endregion 96 | 97 | #region Constructors 98 | 99 | /// 100 | /// Constructor 101 | /// 102 | /// Push Notification Gateway Host 103 | /// Push Notification Gateway Port 104 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 105 | public NotificationChannel(string host, int port, string p12File) 106 | : this(host, port, p12File, null) 107 | { 108 | } 109 | 110 | /// 111 | /// Constructor 112 | /// 113 | /// Push Notification Gateway Host 114 | /// Push Notification Gateway Port 115 | /// Byte array representation of PKCS12 .p12 or .pfx File containing Public and Private Keys 116 | public NotificationChannel(string host, int port, byte[] p12FileBytes) 117 | : this(host, port, p12FileBytes, null) 118 | { 119 | } 120 | 121 | /// 122 | /// Constructor 123 | /// 124 | /// Boolean flag indicating whether the default Sandbox or Production Host and Port should be used 125 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 126 | public NotificationChannel(bool sandbox, string p12File) 127 | : this(sandbox ? hostSandbox : hostProduction, 2195, p12File, null) 128 | { 129 | } 130 | 131 | /// 132 | /// Constructor 133 | /// 134 | /// Boolean flag indicating whether the default Sandbox or Production Host and Port should be used 135 | /// Byte array representation of PKCS12 .p12 or .pfx File containing Public and Private Keys 136 | public NotificationChannel(bool sandbox, byte[] p12FileBytes) 137 | : this(sandbox ? hostSandbox : hostProduction, 2195, p12FileBytes, null) 138 | { 139 | } 140 | 141 | /// 142 | /// Constructor 143 | /// 144 | /// Boolean flag indicating whether the default Sandbox or Production Host and Port should be used 145 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 146 | /// Password protecting the p12File 147 | public NotificationChannel(bool sandbox, string p12File, string p12FilePassword) 148 | : this(sandbox ? hostSandbox : hostProduction, 2195, p12File, p12FilePassword) 149 | { 150 | } 151 | 152 | /// 153 | /// Constructor 154 | /// 155 | /// Boolean flag indicating whether the default Sandbox or Production Host and Port should be used 156 | /// Byte array representation of PKCS12 .p12 or .pfx File containing Public and Private Keys 157 | /// Password protecting the p12File 158 | public NotificationChannel(bool sandbox, byte[] p12FileBytes, string p12FilePassword) 159 | : this(sandbox ? hostSandbox : hostProduction, 2195, p12FileBytes, p12FilePassword) 160 | { 161 | } 162 | 163 | /// 164 | /// Constructor 165 | /// 166 | /// Push Notification Gateway Host 167 | /// Push Notification Gateway Port 168 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 169 | /// Password protecting the p12File 170 | public NotificationChannel(string host, int port, string p12File, string p12FilePassword) 171 | : this(host, port, System.IO.File.ReadAllBytes(p12File), p12FilePassword) 172 | { 173 | } 174 | 175 | /// 176 | /// Constructor 177 | /// 178 | /// Push Notification Gateway Host 179 | /// Push Notification Gateway Port 180 | /// Byte array representation of PKCS12 .p12 or .pfx File containing Public and Private Keys 181 | /// Password protecting the p12File 182 | public NotificationChannel(string host, int port, byte[] p12FileBytes, string p12FilePassword) 183 | { 184 | Host = host; 185 | Port = port; 186 | 187 | connected = false; 188 | firstConnect = true; 189 | ReconnectDelay = 3000; 190 | ConnectRetries = 6; 191 | 192 | //Need to load the private key seperately from apple 193 | // Fixed by danielgindi@gmail.com : 194 | // The default is UserKeySet, which has caused internal encryption errors, 195 | // Because of lack of permissions on most hosting services. 196 | // So MachineKeySet should be used instead. 197 | certificate = new X509Certificate2(p12FileBytes, p12FilePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); 198 | 199 | certificates = new X509CertificateCollection(); 200 | certificates.Add(certificate); 201 | } 202 | 203 | #endregion 204 | 205 | #region Properties 206 | 207 | /// 208 | /// Gets or Sets the Number of Milliseconds to wait before Reconnecting to the Apns Host if the connection was lost or failed 209 | /// 210 | public int ReconnectDelay 211 | { 212 | get; 213 | set; 214 | } 215 | 216 | /// 217 | /// Gets or Sets the number of times to retry establishing a connection before giving up. 218 | /// 219 | public int ConnectRetries 220 | { 221 | get; 222 | set; 223 | } 224 | 225 | /// 226 | /// Gets the Push Notification Gateway Host 227 | /// 228 | public string Host 229 | { 230 | get; 231 | private set; 232 | } 233 | 234 | /// 235 | /// Gets the Push Notification Gateway Port 236 | /// 237 | public int Port 238 | { 239 | get; 240 | private set; 241 | } 242 | 243 | #endregion 244 | 245 | #region Public Methods 246 | 247 | public void SendNotifications(Notification[] notifications) 248 | { 249 | NotificationBatch batch = new NotificationBatch(this, notifications); 250 | bool complete = false; 251 | while(!complete) 252 | complete = batch.SendMessages(); 253 | batch.Complete(); 254 | } 255 | 256 | class NotificationBatch 257 | { 258 | private NotificationChannel channel; 259 | private Notification[] notifications; 260 | private List errors; 261 | private ManualResetEvent mre = new ManualResetEvent(false); 262 | private byte[] readBuffer; 263 | private int current; 264 | private bool faulted; 265 | 266 | internal NotificationBatch(NotificationChannel channel, Notification[] notifications) 267 | { 268 | this.channel = channel; 269 | this.notifications = notifications; 270 | this.errors = new List(); 271 | this.readBuffer = new byte[6]; 272 | this.current = 0; 273 | } 274 | 275 | internal bool SendMessages() 276 | { 277 | faulted = false; 278 | channel.EnsureConnection(); 279 | mre.Reset(); 280 | IAsyncResult ar = this.channel.apnsStream.BeginRead(this.readBuffer, 0, 6, new AsyncCallback(OnAsyncRead), null); 281 | while (!faulted && current < notifications.Length) 282 | { 283 | Notification notification = notifications[current]; 284 | byte[] notificationBytes = null; 285 | 286 | try 287 | { 288 | notificationBytes = notification.ToBytes(); 289 | } 290 | catch (Exception x) 291 | { 292 | errors.Add(new NotificationDeliveryError(x, notification)); 293 | current++; 294 | continue; 295 | } 296 | 297 | try 298 | { 299 | if (!faulted) 300 | { 301 | this.channel.apnsStream.Write(notificationBytes); 302 | Console.WriteLine("Sent {0}", notification.DeviceToken); 303 | current++; 304 | } 305 | } 306 | catch (IOException) 307 | { 308 | } 309 | catch (ObjectDisposedException) 310 | { 311 | } 312 | } 313 | if (!ar.IsCompleted) 314 | { 315 | // Give Apple a chance to let us know something went wrong 316 | ar.AsyncWaitHandle.WaitOne(500); 317 | if (!ar.IsCompleted) 318 | { 319 | // Dispose the channel, which will force the async callback, 320 | // resulting in an ObjectDisposedException on EndRead. 321 | channel.apnsStream.Dispose(); 322 | } 323 | } 324 | mre.WaitOne(); 325 | return !faulted; 326 | } 327 | 328 | private void OnAsyncRead(IAsyncResult ar) 329 | { 330 | try 331 | { 332 | if (channel.apnsStream.EndRead(ar) == 6 && readBuffer[0] == 8) 333 | { 334 | DeliveryErrorType error = (DeliveryErrorType)readBuffer[1]; 335 | faulted = true; 336 | int index = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(readBuffer, 2)); 337 | Notification faultedNotification = notifications[index]; 338 | errors.Add(new NotificationDeliveryError(error, faultedNotification)); 339 | current = index + 1; 340 | channel.ForceReconnect(); 341 | } 342 | } 343 | catch (ObjectDisposedException) 344 | { 345 | // This is how we "cancel" the asynchronous read, so just make sure 346 | // the channel must reconnect to try again. 347 | channel.ForceReconnect(); 348 | } 349 | catch (Exception x) 350 | { 351 | faulted = true; 352 | Console.WriteLine(x.Message); 353 | } 354 | mre.Set(); 355 | } 356 | 357 | internal void Complete() 358 | { 359 | if (this.errors.Count > 0) 360 | throw new NotificationBatchException(this.errors); 361 | } 362 | } 363 | 364 | /// 365 | /// Send a notification to a connected channel immediately. Must call EnsureConnection() before starting to send. 366 | /// 367 | /// The Notification to send. 368 | public void Send(Notification notification) 369 | { 370 | apnsStream.Write(notification.ToBytes()); 371 | } 372 | 373 | /// 374 | /// Ensure that the connection is established before starting to send notifications. 375 | /// 376 | public void EnsureConnection() 377 | { 378 | while (!connected) 379 | Reconnect(); 380 | } 381 | 382 | /// 383 | /// Force a reconnection, for example, if an exception is received, in which case Apple normally 384 | /// closes the existing channel. 385 | /// 386 | public void ForceReconnect() 387 | { 388 | this.connected = false; 389 | } 390 | 391 | /// 392 | /// Disposes of the NotificationChannel and all associated resources. 393 | /// 394 | public void Dispose() 395 | { 396 | try { apnsStream.Close(); } 397 | catch { } 398 | 399 | try { apnsStream.Dispose(); } 400 | catch { } 401 | 402 | try { apnsClient.Client.Shutdown(SocketShutdown.Both); } 403 | catch { } 404 | 405 | try { apnsClient.Client.Close(); } 406 | catch { } 407 | 408 | try { apnsClient.Close(); } 409 | catch { } 410 | } 411 | 412 | #endregion 413 | 414 | #region Private Methods 415 | 416 | private bool Reconnect() 417 | { 418 | if (!firstConnect) 419 | { 420 | for (int i = 0; i < this.ReconnectDelay; i += 100) 421 | System.Threading.Thread.Sleep(100); 422 | } 423 | else 424 | { 425 | firstConnect = false; 426 | } 427 | 428 | 429 | if (apnsStream != null && apnsStream.CanWrite) 430 | { 431 | try { Disconnect(); } 432 | catch { } 433 | } 434 | 435 | if (apnsClient != null && apnsClient.Connected) 436 | { 437 | try { CloseSslStream(); } 438 | catch { } 439 | } 440 | 441 | if (Connect()) 442 | { 443 | this.connected = OpenSslStream(); 444 | 445 | return this.connected; 446 | } 447 | 448 | this.connected = false; 449 | 450 | return this.connected; 451 | } 452 | 453 | private bool Connect() 454 | { 455 | int connectionAttempts = 0; 456 | while (connectionAttempts < this.ConnectRetries && (apnsClient == null || !apnsClient.Connected)) 457 | { 458 | if (connectionAttempts > 0) 459 | Thread.Sleep(this.ReconnectDelay); 460 | 461 | connectionAttempts++; 462 | 463 | try 464 | { 465 | if (this.Connecting != null) 466 | this.Connecting(this); 467 | 468 | apnsClient = new TcpClient(); 469 | apnsClient.Connect(this.Host, this.Port); 470 | 471 | } 472 | catch (SocketException ex) 473 | { 474 | if (this.Error != null) 475 | this.Error(this, ex); 476 | 477 | return false; 478 | } 479 | } 480 | if (connectionAttempts >= 3) 481 | { 482 | if (this.Error != null) 483 | this.Error(this, new NotificationException(3, "Too many connection attempts")); 484 | 485 | return false; 486 | } 487 | 488 | return true; 489 | } 490 | 491 | private bool OpenSslStream() 492 | { 493 | apnsStream = new SslStream(apnsClient.GetStream(), false, new RemoteCertificateValidationCallback(validateServerCertificate), new LocalCertificateSelectionCallback(selectLocalCertificate)); 494 | 495 | try 496 | { 497 | apnsStream.AuthenticateAsClient(this.Host, this.certificates, System.Security.Authentication.SslProtocols.Tls, false); 498 | } 499 | catch (System.Security.Authentication.AuthenticationException ex) 500 | { 501 | if (this.Error != null) 502 | this.Error(this, ex); 503 | 504 | return false; 505 | } 506 | 507 | if (!apnsStream.IsMutuallyAuthenticated) 508 | { 509 | if (this.Error != null) 510 | this.Error(this, new NotificationException(4, "Ssl Stream Failed to Authenticate")); 511 | 512 | return false; 513 | } 514 | 515 | if (!apnsStream.CanWrite) 516 | { 517 | if (this.Error != null) 518 | this.Error(this, new NotificationException(5, "Ssl Stream is not Writable")); 519 | 520 | return false; 521 | } 522 | 523 | if (this.Connected != null) 524 | this.Connected(this); 525 | 526 | return true; 527 | } 528 | 529 | private void EnsureDisconnected() 530 | { 531 | if (apnsStream != null) 532 | CloseSslStream(); 533 | if (apnsClient != null) 534 | Disconnect(); 535 | } 536 | 537 | private void CloseSslStream() 538 | { 539 | try 540 | { 541 | apnsStream.Close(); 542 | apnsStream.Dispose(); 543 | apnsStream = null; 544 | } 545 | catch (Exception ex) 546 | { 547 | if (this.Error != null) 548 | this.Error(this, ex); 549 | } 550 | 551 | if (this.Disconnected != null) 552 | this.Disconnected(this); 553 | } 554 | 555 | private void Disconnect() 556 | { 557 | try 558 | { 559 | apnsClient.Close(); 560 | } 561 | catch (Exception ex) 562 | { 563 | if (this.Error != null) 564 | this.Error(this, ex); 565 | } 566 | } 567 | 568 | private bool validateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) 569 | { 570 | return true; // Dont care about server's cert 571 | } 572 | 573 | private X509Certificate selectLocalCertificate(object sender, string targetHost, X509CertificateCollection localCertificates, 574 | X509Certificate remoteCertificate, string[] acceptableIssuers) 575 | { 576 | return certificate; 577 | } 578 | 579 | #endregion 580 | } 581 | } 582 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications/NotificationConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Net.Security; 8 | using System.Security.Cryptography; 9 | using System.Security.Cryptography.X509Certificates; 10 | 11 | namespace JdSoft.Apple.Apns.Notifications 12 | { 13 | /// 14 | /// Apns Notification Server Connection 15 | /// 16 | public class NotificationConnection : IDisposable 17 | { 18 | #region Delegates and Events 19 | /// 20 | /// Handles General Exceptions 21 | /// 22 | /// NotificationConnection Instance that generated the Exception 23 | /// Exception Instance 24 | public delegate void OnError(object sender, Exception ex); 25 | /// 26 | /// Occurs when a General Error is thrown 27 | /// 28 | public event OnError Error; 29 | 30 | /// 31 | /// Handles Notification Too Long Exceptions when a Notification's payload that is being sent is too long 32 | /// 33 | /// NotificationConnection Instance that generated the Exception 34 | /// NotificationTooLongException Instance 35 | public delegate void OnNotificationTooLong(object sender, NotificationLengthException ex); 36 | /// 37 | /// Occurs when a Notification that is being sent has a payload longer than the allowable limit of 256 bytes as per Apple's specifications 38 | /// 39 | public event OnNotificationTooLong NotificationTooLong; 40 | 41 | /// 42 | /// Handles Bad Device Token Exceptions when the device token provided is not the right length 43 | /// 44 | /// NotificatioConnection Instance that generated the Exception 45 | /// BadDeviceTokenException Instance 46 | public delegate void OnBadDeviceToken(object sender, BadDeviceTokenException ex); 47 | /// 48 | /// Occurs when a Device Token that's specified is not the right length 49 | /// 50 | public event OnBadDeviceToken BadDeviceToken; 51 | 52 | /// 53 | /// Handles Successful Notification Send Events 54 | /// 55 | /// NotificationConnection Instance 56 | /// Notification object that was Sent 57 | public delegate void OnNotificationSuccess(object sender, Notification notification); 58 | /// 59 | /// Occurs when a Notification has been successfully sent to Apple's Servers 60 | /// 61 | public event OnNotificationSuccess NotificationSuccess; 62 | 63 | /// 64 | /// Handles Failed Notification Deliveries 65 | /// 66 | /// NotificationConnection Instance 67 | /// Notification object that failed to send 68 | public delegate void OnNotificationFailed(object sender, Notification failed); 69 | /// 70 | /// Occurs when a Notification has failed to send to Apple's Servers. This is event is raised after the NotificationConnection has attempted to resend the notification the number of SendRetries specified. 71 | /// 72 | public event OnNotificationFailed NotificationFailed; 73 | 74 | /// 75 | /// Handles Connecting Event 76 | /// 77 | /// NotificationConnection Instance 78 | public delegate void OnConnecting(object sender); 79 | /// 80 | /// Occurs when Connecting to Apple's servers 81 | /// 82 | public event OnConnecting Connecting; 83 | 84 | /// 85 | /// Handles Connected Event 86 | /// 87 | /// NotificationConnection Instance 88 | public delegate void OnConnected(object sender); 89 | /// 90 | /// Occurs when successfully connected and authenticated via SSL to Apple's Servers 91 | /// 92 | public event OnConnected Connected; 93 | 94 | /// 95 | /// Handles Disconnected Event 96 | /// 97 | /// NotificationConnection Instance 98 | public delegate void OnDisconnected(object sender); 99 | /// 100 | /// Occurs when the connection to Apple's Servers has been lost 101 | /// 102 | public event OnDisconnected Disconnected; 103 | #endregion 104 | 105 | #region Instance Variables 106 | private bool disposing; 107 | private bool closing; 108 | private bool accepting; 109 | 110 | private ThreadSafeQueue notifications; 111 | private Thread workerThread; 112 | private NotificationChannel apnsChannel; 113 | #endregion 114 | 115 | #region Constructors 116 | /// 117 | /// Constructor 118 | /// 119 | /// Push Notification Gateway Host 120 | /// Push Notification Gateway Port 121 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 122 | public NotificationConnection(string host, int port, string p12File) 123 | { 124 | apnsChannel = new NotificationChannel(host, port, p12File); 125 | start(); 126 | } 127 | 128 | /// 129 | /// Constructor 130 | /// 131 | /// Push Notification Gateway Host 132 | /// Push Notification Gateway Port 133 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys, as a byte array 134 | /// Password protecting the p12File 135 | public NotificationConnection( string host, int port, byte[] p12FileBytes, string p12FilePassword ) 136 | { 137 | apnsChannel = new NotificationChannel( host, port, p12FileBytes, p12FilePassword ); 138 | start(); 139 | } 140 | 141 | /// 142 | /// Constructor 143 | /// 144 | /// Push Notification Gateway Host 145 | /// Push Notification Gateway Port 146 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 147 | /// Password protecting the p12File 148 | public NotificationConnection(string host, int port, string p12File, string p12FilePassword) 149 | { 150 | apnsChannel = new NotificationChannel(host, port, p12File, p12FilePassword); 151 | start(); 152 | } 153 | 154 | /// 155 | /// Constructor 156 | /// 157 | /// Boolean flag indicating whether the default Sandbox or Production Host and Port should be used 158 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 159 | public NotificationConnection(bool sandbox, string p12File) 160 | { 161 | apnsChannel = new NotificationChannel(sandbox, p12File); 162 | start(); 163 | } 164 | 165 | /// 166 | /// Constructor 167 | /// 168 | /// Boolean flag indicating whether the default Sandbox or Production Host and Port should be used 169 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 170 | /// Password protecting the p12File 171 | public NotificationConnection(bool sandbox, string p12File, string p12FilePassword) 172 | { 173 | apnsChannel = new NotificationChannel(sandbox, p12File, p12FilePassword); 174 | start(); 175 | } 176 | 177 | #endregion 178 | 179 | #region Properties 180 | /// 181 | /// Unique Identifier for this Instance 182 | /// 183 | public string Id 184 | { 185 | get; 186 | private set; 187 | } 188 | 189 | /// 190 | /// Gets or Sets the Number of Milliseconds to wait before Reconnecting to the Apns Host if the connection was lost or failed 191 | /// 192 | public int ReconnectDelay 193 | { 194 | get; 195 | set; 196 | } 197 | 198 | /// 199 | /// Gets or Sets the Number of times to try resending a Notification before the NotificationFailed event is raised 200 | /// 201 | public int SendRetries 202 | { 203 | get; 204 | set; 205 | } 206 | 207 | /// 208 | /// Gets the Push Notification Gateway Host 209 | /// 210 | public string Host 211 | { 212 | get { return apnsChannel.Host; } 213 | } 214 | 215 | /// 216 | /// Gets the Push Notification Gateway Port 217 | /// 218 | public int Port 219 | { 220 | get { return apnsChannel.Port; } 221 | } 222 | 223 | /// 224 | /// For whatever use you please :) 225 | /// 226 | public object Tag 227 | { 228 | get; 229 | set; 230 | } 231 | 232 | /// 233 | /// Gets the number of notifications currently in the queue 234 | /// 235 | public int QueuedNotificationsCount 236 | { 237 | get { return notifications.Count; } 238 | } 239 | #endregion 240 | 241 | #region Public Methods 242 | /// 243 | /// Queue's a Notification to be Sent as soon as possible in a First in First out pattern 244 | /// 245 | /// Notification object to send 246 | /// If true, the notification was queued successfully, otherwise it was not and will not be sent 247 | public bool QueueNotification(Notification notification) 248 | { 249 | if (!disposing && !closing && accepting) 250 | { 251 | notifications.Enqueue(notification); 252 | return true; 253 | } 254 | 255 | return false; 256 | } 257 | 258 | /// 259 | /// Closes the Apns Connection but first waits for all Queued Notifications to be sent. This will cause QueueNotification to always return false after this method is called. 260 | /// 261 | public void Close() 262 | { 263 | accepting = false; 264 | 265 | //Sleep here to prevent a race condition 266 | // in which a notification could be queued while the worker thread 267 | // is sleeping after its loop, but if we set closing true within that 100 ms, 268 | // the queued notifications during that time would not get dequeued as the loop 269 | // would exit due to closing = true; 270 | // Changed to keep looping until the notifications count is 0, meaning all have been dequeued, 271 | // or a timeout of 10 seconds has occurred. 272 | int slept = 0; 273 | int maxSleep = 10000; //10 seconds 274 | 275 | while (notifications.Count > 0 && slept <= maxSleep) 276 | { 277 | Thread.Sleep(100); 278 | slept += 100; 279 | } 280 | 281 | closing = true; 282 | 283 | //Wait for buffer to be flushed out 284 | if (workerThread != null && workerThread.IsAlive) 285 | workerThread.Join(); 286 | } 287 | 288 | /// 289 | /// Closes the Apns Connections without waiting for Queued Notifications to be sent. This will cause QueueNotification to always return false after this method is called. 290 | /// 291 | public void Dispose() 292 | { 293 | 294 | accepting = false; 295 | 296 | //We don't really care about the race condition here 297 | // since disposing does NOT wait for all notifications to be sent 298 | 299 | disposing = true; 300 | 301 | //Wait for the worker to finish cleanly 302 | if (workerThread != null && workerThread.IsAlive) 303 | workerThread.Join(); 304 | 305 | try { apnsChannel.Dispose(); } 306 | catch { } 307 | } 308 | #endregion 309 | 310 | #region Private Methods 311 | private void start() 312 | { 313 | accepting = true; 314 | disposing = false; 315 | closing = false; 316 | 317 | notifications = new ThreadSafeQueue(); 318 | Id = System.Guid.NewGuid().ToString("N"); 319 | ReconnectDelay = 3000; //3 seconds 320 | SendRetries = 3; 321 | 322 | apnsChannel.ReconnectDelay = this.ReconnectDelay; 323 | apnsChannel.ConnectRetries = SendRetries * 2; 324 | apnsChannel.Error += new NotificationChannel.OnError(OnChannelError); 325 | apnsChannel.Connected += new NotificationChannel.OnConnected(OnChannelConnected); 326 | apnsChannel.Connecting += new NotificationChannel.OnConnecting(OnChannelConnecting); 327 | apnsChannel.Disconnected += new NotificationChannel.OnDisconnected(OnChannelDisconnected); 328 | 329 | workerThread = new Thread(new ThreadStart(workerMethod)); 330 | workerThread.Start(); 331 | } 332 | 333 | void OnChannelDisconnected(object sender) 334 | { 335 | var onDisconnected = Disconnected; 336 | if (onDisconnected != null) 337 | onDisconnected(this); 338 | } 339 | 340 | void OnChannelConnecting(object sender) 341 | { 342 | var onConnecting = Connecting; 343 | if (onConnecting != null) 344 | onConnecting(this); 345 | } 346 | 347 | void OnChannelConnected(object sender) 348 | { 349 | var onConnected = Connected; 350 | if (onConnected != null) 351 | onConnected(this); 352 | } 353 | 354 | void OnChannelError(object sender, Exception ex) 355 | { 356 | var onError = Error; 357 | if (onError != null) 358 | onError(this, ex); 359 | } 360 | 361 | private void workerMethod() 362 | { 363 | while (!disposing && !closing) 364 | { 365 | try 366 | { 367 | while (this.notifications.Count > 0 && !disposing) 368 | { 369 | Notification notification = this.notifications.Dequeue(); 370 | 371 | int tries = 0; 372 | bool sent = false; 373 | 374 | while (!sent && tries < this.SendRetries) 375 | { 376 | try 377 | { 378 | if (!disposing) 379 | { 380 | apnsChannel.EnsureConnection(); 381 | 382 | try 383 | { 384 | apnsChannel.Send(notification); 385 | } 386 | catch (BadDeviceTokenException btex) 387 | { 388 | var onBadDeviceToken = BadDeviceToken; 389 | if (onBadDeviceToken != null) 390 | onBadDeviceToken(this, btex); 391 | } 392 | catch (NotificationLengthException nlex) 393 | { 394 | var onNotificationTooLong = NotificationTooLong; 395 | if (onNotificationTooLong != null) 396 | onNotificationTooLong(this, nlex); 397 | } 398 | 399 | var onNotificationSuccess = NotificationSuccess; 400 | if (onNotificationSuccess != null) 401 | onNotificationSuccess(this, notification); 402 | 403 | sent = true; 404 | } 405 | else 406 | { 407 | apnsChannel.ForceReconnect(); 408 | } 409 | } 410 | catch (Exception ex) 411 | { 412 | var onError = Error; 413 | if (onError != null) 414 | onError(this, ex); 415 | 416 | apnsChannel.ForceReconnect(); 417 | } 418 | 419 | tries++; 420 | } 421 | 422 | //Didn't send in 3 tries 423 | var onNotificationFailed = NotificationFailed; 424 | if (!sent && onNotificationFailed != null) 425 | onNotificationFailed(this, notification); 426 | } 427 | } 428 | catch (Exception ex) 429 | { 430 | var onError = Error; 431 | if (onError != null) 432 | onError(this, ex); 433 | 434 | apnsChannel.ForceReconnect(); 435 | } 436 | 437 | if (!disposing) 438 | Thread.Sleep(500); 439 | } 440 | } 441 | 442 | 443 | 444 | 445 | 446 | #endregion 447 | } 448 | } -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications/NotificationDeliveryError.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JdSoft.Apple.Apns.Notifications 6 | { 7 | public class NotificationDeliveryError 8 | { 9 | public NotificationDeliveryError(DeliveryErrorType type, Notification notification) 10 | { 11 | this.ErrorType = type; 12 | this.Notification = notification; 13 | } 14 | 15 | public NotificationDeliveryError(Exception exception, Notification notification) 16 | { 17 | this.ErrorType = DeliveryErrorType.Unknown; 18 | this.Exception = exception; 19 | this.Notification = notification; 20 | } 21 | 22 | public DeliveryErrorType ErrorType { get; private set; } 23 | public Notification Notification { get; private set; } 24 | public Exception Exception { get; private set; } 25 | 26 | public bool IsException 27 | { 28 | get { return Exception != null; } 29 | } 30 | } 31 | 32 | public enum DeliveryErrorType : byte 33 | { 34 | NoErrors = 0, 35 | ProcessingError = 1, 36 | MissingDeviceToken = 2, 37 | MissingTopic = 3, 38 | MissingPayload = 4, 39 | InvalidTokenSize = 5, 40 | InvalidTopicSize = 6, 41 | InvalidPayloadSize = 7, 42 | InvalidToken = 8, 43 | Unknown = 255, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications/NotificationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JdSoft.Apple.Apns.Notifications 6 | { 7 | public class NotificationException : Exception 8 | { 9 | public NotificationException() 10 | : base() 11 | { 12 | } 13 | 14 | public NotificationException(int code, string message) 15 | : base(message) 16 | { 17 | this.Code = code; 18 | } 19 | 20 | public int Code 21 | { 22 | get; 23 | set; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications/NotificationLengthException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JdSoft.Apple.Apns.Notifications 6 | { 7 | public class NotificationLengthException : Exception 8 | { 9 | /// 10 | /// Constructor 11 | /// 12 | /// Notification that caused the Exception 13 | public NotificationLengthException(Notification notification) 14 | : base(string.Format("Notification Payload Length ({0}) Exceeds the maximum length of {1} characters", notification.Payload.ToJson().Length, Notification.MAX_PAYLOAD_SIZE)) 15 | { 16 | this.Notification = notification; 17 | } 18 | 19 | /// 20 | /// Notification that caused the Exception 21 | /// 22 | public Notification Notification 23 | { 24 | get; 25 | private set; 26 | } 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications/NotificationPayload.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | using System.Text; 6 | using Newtonsoft.Json.Linq; 7 | 8 | namespace JdSoft.Apple.Apns.Notifications 9 | { 10 | public class NotificationPayload 11 | { 12 | public NotificationAlert Alert { get; set; } 13 | 14 | public int? ContentAvailable { get; set; } 15 | 16 | public int? Badge { get; set; } 17 | 18 | public string Sound { get; set; } 19 | 20 | public bool HideActionButton { get; set; } 21 | 22 | public Dictionary CustomItems 23 | { 24 | get; 25 | private set; 26 | } 27 | 28 | public NotificationPayload() 29 | { 30 | HideActionButton = false; 31 | Alert = new NotificationAlert(); 32 | CustomItems = new Dictionary(); 33 | } 34 | 35 | public NotificationPayload(string alert) 36 | { 37 | HideActionButton = false; 38 | Alert = new NotificationAlert() { Body = alert }; 39 | CustomItems = new Dictionary(); 40 | } 41 | 42 | public NotificationPayload(string alert, int badge) 43 | { 44 | HideActionButton = false; 45 | Alert = new NotificationAlert() { Body = alert }; 46 | Badge = badge; 47 | CustomItems = new Dictionary(); 48 | } 49 | 50 | public NotificationPayload(string alert, int badge, string sound) 51 | { 52 | HideActionButton = false; 53 | Alert = new NotificationAlert() { Body = alert }; 54 | Badge = badge; 55 | Sound = sound; 56 | CustomItems = new Dictionary(); 57 | } 58 | 59 | public void AddCustom(string key, params object[] values) 60 | { 61 | if (values != null) 62 | this.CustomItems.Add(key, values); 63 | } 64 | 65 | public string ToJson() 66 | { 67 | JObject json = new JObject(); 68 | 69 | JObject aps = new JObject(); 70 | 71 | if (!this.Alert.IsEmpty) 72 | { 73 | if (!string.IsNullOrEmpty(this.Alert.Body) 74 | && string.IsNullOrEmpty(this.Alert.LocalizedKey) 75 | && string.IsNullOrEmpty(this.Alert.ActionLocalizedKey) 76 | && (this.Alert.LocalizedArgs == null || this.Alert.LocalizedArgs.Count <= 0) 77 | && !this.HideActionButton) 78 | { 79 | aps["alert"] = new JValue(this.Alert.Body); 80 | } 81 | else 82 | { 83 | JObject jsonAlert = new JObject(); 84 | 85 | if (!string.IsNullOrEmpty(this.Alert.LocalizedKey)) 86 | jsonAlert["loc-key"] = new JValue(this.Alert.LocalizedKey); 87 | 88 | if (this.Alert.LocalizedArgs != null && this.Alert.LocalizedArgs.Count > 0) 89 | jsonAlert["loc-args"] = new JArray(this.Alert.LocalizedArgs.ToArray()); 90 | 91 | if (!string.IsNullOrEmpty(this.Alert.Body)) 92 | jsonAlert["body"] = new JValue(this.Alert.Body); 93 | 94 | if (this.HideActionButton) 95 | jsonAlert["action-loc-key"] = new JValue((string)null); 96 | else if (!string.IsNullOrEmpty(this.Alert.ActionLocalizedKey)) 97 | jsonAlert["action-loc-key"] = new JValue(this.Alert.ActionLocalizedKey); 98 | 99 | aps["alert"] = jsonAlert; 100 | } 101 | } 102 | 103 | if (this.Badge.HasValue) 104 | aps["badge"] = new JValue(this.Badge.Value); 105 | 106 | if (!string.IsNullOrEmpty(this.Sound)) 107 | aps["sound"] = new JValue(this.Sound); 108 | 109 | if (this.ContentAvailable.HasValue) 110 | aps["content-available"] = new JValue(this.ContentAvailable.Value); 111 | 112 | json["aps"] = aps; 113 | 114 | foreach (string key in this.CustomItems.Keys) 115 | { 116 | if (this.CustomItems[key].Length == 1) 117 | json[key] = new JValue(this.CustomItems[key][0]); 118 | else if (this.CustomItems[key].Length > 1) 119 | json[key] = new JArray(this.CustomItems[key]); 120 | } 121 | 122 | string rawString = json.ToString(Newtonsoft.Json.Formatting.None, null); 123 | 124 | StringBuilder encodedString = new StringBuilder(); 125 | foreach (char c in rawString) 126 | { 127 | if ((int)c < 32 || (int)c > 127) 128 | encodedString.Append("\\u" + String.Format("{0:x4}", Convert.ToUInt32(c))); 129 | else 130 | encodedString.Append(c); 131 | } 132 | return rawString;// encodedString.ToString(); 133 | } 134 | 135 | public override string ToString() 136 | { 137 | return ToJson(); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications/NotificationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JdSoft.Apple.Apns.Notifications 6 | { 7 | using System.IO; 8 | 9 | public class NotificationService : IDisposable 10 | { 11 | #region Constants 12 | private const string hostSandbox = "gateway.sandbox.push.apple.com"; 13 | private const string hostProduction = "gateway.push.apple.com"; 14 | private const int apnsPort = 2195; 15 | #endregion 16 | 17 | #region Delegates and Events 18 | /// 19 | /// Handles General Exceptions 20 | /// 21 | /// NotificationConnection Instance that generated the Exception 22 | /// Exception Instance 23 | public delegate void OnError(object sender, Exception ex); 24 | /// 25 | /// Occurs when a General Error is thrown 26 | /// 27 | public event OnError Error; 28 | 29 | /// 30 | /// Handles Notification Too Long Exceptions when a Notification's payload that is being sent is too long 31 | /// 32 | /// NotificationConnection Instance that generated the Exception 33 | /// NotificationTooLongException Instance 34 | public delegate void OnNotificationTooLong(object sender, NotificationLengthException ex); 35 | /// 36 | /// Occurs when a Notification that is being sent has a payload longer than the allowable limit of 256 bytes as per Apple's specifications 37 | /// 38 | public event OnNotificationTooLong NotificationTooLong; 39 | 40 | /// 41 | /// Handles Bad Device Token Exceptions when the device token provided is not the right length 42 | /// 43 | /// NotificatioConnection Instance that generated the Exception 44 | /// BadDeviceTokenException Instance 45 | public delegate void OnBadDeviceToken(object sender, BadDeviceTokenException ex); 46 | /// 47 | /// Occurs when a Device Token that's specified is not the right length 48 | /// 49 | public event OnBadDeviceToken BadDeviceToken; 50 | 51 | /// 52 | /// Handles Successful Notification Send Events 53 | /// 54 | /// NotificationConnection Instance 55 | /// Notification object that was Sent 56 | public delegate void OnNotificationSuccess(object sender, Notification notification); 57 | /// 58 | /// Occurs when a Notification has been successfully sent to Apple's Servers 59 | /// 60 | public event OnNotificationSuccess NotificationSuccess; 61 | 62 | /// 63 | /// Handles Failed Notification Deliveries 64 | /// 65 | /// NotificationConnection Instance 66 | /// Notification object that failed to send 67 | public delegate void OnNotificationFailed(object sender, Notification failed); 68 | /// 69 | /// Occurs when a Notification has failed to send to Apple's Servers. This is event is raised after the NotificationConnection has attempted to resend the notification the number of SendRetries specified. 70 | /// 71 | public event OnNotificationFailed NotificationFailed; 72 | 73 | /// 74 | /// Handles Connecting Event 75 | /// 76 | /// NotificationConnection Instance 77 | public delegate void OnConnecting(object sender); 78 | /// 79 | /// Occurs when Connecting to Apple's servers 80 | /// 81 | public event OnConnecting Connecting; 82 | 83 | /// 84 | /// Handles Connected Event 85 | /// 86 | /// NotificationConnection Instance 87 | public delegate void OnConnected(object sender); 88 | /// 89 | /// Occurs when successfully connected and authenticated via SSL to Apple's Servers 90 | /// 91 | public event OnConnected Connected; 92 | 93 | /// 94 | /// Handles Disconnected Event 95 | /// 96 | /// NotificationConnection Instance 97 | public delegate void OnDisconnected(object sender); 98 | /// 99 | /// Occurs when the connection to Apple's Servers has been lost 100 | /// 101 | public event OnDisconnected Disconnected; 102 | #endregion 103 | 104 | #region Instance Variables 105 | private List notificationConnections = new List(); 106 | private Random rand = new Random((int)DateTime.Now.Ticks); 107 | private int sequential = 0; 108 | private int reconnectDelay = 5000; 109 | private int sendRetries = 1; 110 | 111 | private bool closing = false; 112 | private bool disposing = false; 113 | #endregion 114 | 115 | #region Constructors 116 | 117 | /// 118 | /// Constructor 119 | /// 120 | /// Push Notification Gateway Host 121 | /// Push Notification Gateway Port 122 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 123 | /// Number of Apns Connections to start with 124 | public NotificationService(string host, int port, string p12File, int connections) 125 | : this(host, port, p12File, null, connections) 126 | { 127 | } 128 | 129 | /// 130 | /// Constructor 131 | /// 132 | /// Push Notification Gateway Host 133 | /// Push Notification Gateway Port 134 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 135 | /// Password protecting the p12File 136 | /// Number of Apns Connections to start with 137 | public NotificationService( string host, int port, string p12File, string p12FilePassword, int connections ) 138 | : this( host, port, System.IO.File.ReadAllBytes( p12File ), p12FilePassword, connections ) 139 | { 140 | } 141 | 142 | /// 143 | /// Constructor 144 | /// 145 | /// Push Notification Gateway Host 146 | /// Push Notification Gateway Port 147 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 148 | /// Password protecting the p12File 149 | /// Number of Apns Connections to start with 150 | public NotificationService( string host, int port, byte[] p12FileBytes, string p12FilePassword, int connections ) 151 | { 152 | this.SendRetries = 1; 153 | closing = false; 154 | disposing = false; 155 | Host = host; 156 | Port = port; 157 | P12FileBytes = p12FileBytes; 158 | P12FilePassword = p12FilePassword; 159 | DistributionType = NotificationServiceDistributionType.Sequential; 160 | Connections = connections; 161 | } 162 | 163 | /// 164 | /// Constructor 165 | /// 166 | /// Boolean flag indicating whether the default Sandbox or Production Host and Port should be used 167 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 168 | /// Number of Apns Connections to start with 169 | public NotificationService(bool sandbox, string p12File, int connections) 170 | : this(sandbox ? hostSandbox : hostProduction, apnsPort, p12File, null, connections) 171 | { 172 | } 173 | 174 | /// 175 | /// Constructor 176 | /// 177 | /// Boolean flag indicating whether the default Sandbox or Production Host and Port should be used 178 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 179 | /// Password protecting the p12File 180 | /// Number of Apns Connections to start with 181 | public NotificationService(bool sandbox, string p12File, string p12FilePassword, int connections) : 182 | this(sandbox ? hostSandbox : hostProduction, apnsPort, p12File, p12FilePassword, connections) 183 | { 184 | 185 | } 186 | 187 | /// 188 | /// Constructor 189 | /// 190 | /// Boolean flag indicating whether the default Sandbox or Production Host and Port should be used 191 | /// PKCS12 .p12 or .pfx File containing Public and Private Keys 192 | /// Password protecting the p12File 193 | /// Number of Apns Connections to start with 194 | public NotificationService( bool sandbox, byte[] p12FileBytes, string p12FilePassword, int connections ) : 195 | this( sandbox ? hostSandbox : hostProduction, apnsPort, p12FileBytes, p12FilePassword, connections ) 196 | { 197 | } 198 | 199 | /// 200 | /// Constructor 201 | /// 202 | /// Push Notification Gateway Host 203 | /// Push Notification Gateway Port 204 | /// Stream to PKCS12 .p12 or .pfx file containing Public and Private Keys 205 | /// Password protecting the p12File 206 | /// Number of Apns Connections to start with 207 | public NotificationService(bool sandbox, Stream p12FileStream, string p12FilePassword, int connections) : 208 | this(sandbox ? hostSandbox : hostProduction, apnsPort, getAllBytesFromStream(p12FileStream), p12FilePassword, connections) 209 | { 210 | } 211 | 212 | #endregion 213 | 214 | #region Properties 215 | /// 216 | /// Unique Identifier for this Instance 217 | /// 218 | public string Id 219 | { 220 | get; 221 | private set; 222 | } 223 | 224 | /// 225 | /// Gets or Sets the Number of Milliseconds to wait before Reconnecting to the Apns Host if the connection was lost or failed 226 | /// 227 | public int ReconnectDelay 228 | { 229 | get { return reconnectDelay; } 230 | set 231 | { 232 | reconnectDelay = value; 233 | 234 | foreach (NotificationConnection con in notificationConnections) 235 | con.ReconnectDelay = reconnectDelay; 236 | } 237 | } 238 | 239 | /// 240 | /// Gets or Sets the Number of times to try resending a Notification before the NotificationFailed event is raised 241 | /// 242 | public int SendRetries 243 | { 244 | get { return sendRetries; } 245 | set 246 | { 247 | sendRetries = value; 248 | 249 | foreach (NotificationConnection con in notificationConnections) 250 | con.SendRetries = sendRetries; 251 | } 252 | } 253 | 254 | /// 255 | /// Gets or Sets the method used to distribute Queued Notifications over all open Apns Connections 256 | /// 257 | public NotificationServiceDistributionType DistributionType 258 | { 259 | get; 260 | set; 261 | } 262 | 263 | /// 264 | /// Gets the PKCS12 .p12 or .pfx File being used 265 | /// 266 | public byte[] P12FileBytes 267 | { 268 | get; 269 | private set; 270 | } 271 | 272 | private string P12FilePassword; 273 | 274 | /// 275 | /// Gets the Push Notification Gateway Host 276 | /// 277 | public string Host 278 | { 279 | get; 280 | private set; 281 | } 282 | 283 | /// 284 | /// Gets the Push Notification Gateway Port 285 | /// 286 | public int Port 287 | { 288 | get; 289 | private set; 290 | } 291 | 292 | /// 293 | /// For whatever use you please :) 294 | /// 295 | public object Tag 296 | { 297 | get; 298 | set; 299 | } 300 | 301 | /// 302 | /// Gets or Sets the number of Apns Connections in use. Changing this property will dynamically change the number of connections in use. If it is decreased, connections will be Closed (waiting for the Queue to be Emptied first), or if raised, new connections will be added. 303 | /// 304 | public int Connections 305 | { 306 | get 307 | { 308 | return notificationConnections.Count; 309 | } 310 | set 311 | { 312 | //Don't want 0 connections or less 313 | if (value <= 0) 314 | return; 315 | 316 | //Get the delta 317 | int difference = value - notificationConnections.Count; 318 | 319 | if (difference > 0) 320 | { 321 | //Need to add connections 322 | for (int i = 0; i < difference; i++) 323 | { 324 | NotificationConnection newCon = new NotificationConnection(Host, Port, P12FileBytes, P12FilePassword); 325 | newCon.SendRetries = SendRetries; 326 | newCon.ReconnectDelay = ReconnectDelay; 327 | 328 | newCon.Error += new NotificationConnection.OnError(newCon_Error); 329 | newCon.NotificationFailed += new NotificationConnection.OnNotificationFailed(newCon_NotificationFailed); 330 | newCon.NotificationTooLong += new NotificationConnection.OnNotificationTooLong(newCon_NotificationTooLong); 331 | newCon.NotificationSuccess += new NotificationConnection.OnNotificationSuccess(newCon_NotificationSuccess); 332 | newCon.Connecting += new NotificationConnection.OnConnecting(newCon_Connecting); 333 | newCon.Connected += new NotificationConnection.OnConnected(newCon_Connected); 334 | newCon.Disconnected += new NotificationConnection.OnDisconnected(newCon_Disconnected); 335 | newCon.BadDeviceToken += new NotificationConnection.OnBadDeviceToken(newCon_BadDeviceToken); 336 | notificationConnections.Add(newCon); 337 | } 338 | 339 | } 340 | else if (difference < 0) 341 | { 342 | //Need to remove connections 343 | for (int i = 0; i < difference * -1; i++) 344 | { 345 | if (notificationConnections.Count > 0) 346 | { 347 | NotificationConnection toClose = notificationConnections[0]; 348 | notificationConnections.RemoveAt(0); 349 | 350 | toClose.Close(); 351 | toClose.Dispose(); 352 | toClose = null; 353 | } 354 | } 355 | } 356 | } 357 | } 358 | #endregion 359 | 360 | #region Public Methods 361 | /// 362 | /// Queues a Notification to one of the Apns Connections using the DistributionType specified by the property. 363 | /// 364 | /// Notification object to send 365 | /// If true, the Notification has been successfully queued 366 | public bool QueueNotification(Notification notification) 367 | { 368 | bool queued = false; 369 | 370 | if (!disposing && !closing) 371 | { 372 | int tries = 0; 373 | 374 | while (tries < SendRetries && !queued) 375 | { 376 | if (DistributionType == NotificationServiceDistributionType.Sequential) 377 | queued = queueSequential(notification); 378 | else if (DistributionType == NotificationServiceDistributionType.Random) 379 | queued = queueRandom(notification); 380 | 381 | tries++; 382 | } 383 | } 384 | 385 | return queued; 386 | } 387 | 388 | /// 389 | /// Closes all of the Apns Connections but first waits for all Queued Notifications on each Apns Connection to be sent. This will cause QueueNotification to always return false after this method is called. 390 | /// 391 | public void Close() 392 | { 393 | closing = true; 394 | 395 | foreach (NotificationConnection con in notificationConnections) 396 | con.Close(); 397 | } 398 | 399 | /// 400 | /// Closes all of the Apns Connections without waiting for Queued Notifications on each Apns Connection to be sent. This will cause QueueNotification to always return false after this method is called. 401 | /// 402 | public void Dispose() 403 | { 404 | disposing = true; 405 | 406 | foreach (NotificationConnection con in notificationConnections) 407 | con.Dispose(); 408 | } 409 | #endregion 410 | 411 | #region Private Methods 412 | void newCon_NotificationSuccess(object sender, Notification notification) 413 | { 414 | var onNotificationSuccess = NotificationSuccess; 415 | if (onNotificationSuccess != null) 416 | NotificationSuccess(sender, notification); 417 | } 418 | 419 | void newCon_NotificationTooLong(object sender, NotificationLengthException ex) 420 | { 421 | var onNotificationTooLong = NotificationTooLong; 422 | if (onNotificationTooLong != null) 423 | NotificationTooLong(sender, ex); 424 | } 425 | 426 | void newCon_BadDeviceToken(object sender, BadDeviceTokenException ex) 427 | { 428 | var onBadDeviceToken = BadDeviceToken; 429 | if (onBadDeviceToken != null) 430 | BadDeviceToken(this, ex); 431 | } 432 | 433 | void newCon_NotificationFailed(object sender, Notification failed) 434 | { 435 | var onNotificationFailed = NotificationFailed; 436 | if (onNotificationFailed != null) 437 | NotificationFailed(sender, failed); 438 | } 439 | 440 | void newCon_Error(object sender, Exception ex) 441 | { 442 | var onError = Error; 443 | if (onError != null) 444 | Error(sender, ex); 445 | } 446 | 447 | void newCon_Disconnected(object sender) 448 | { 449 | var onDisconnected = Disconnected; 450 | if (onDisconnected != null) 451 | Disconnected(sender); 452 | } 453 | 454 | void newCon_Connected(object sender) 455 | { 456 | var onConnected = Connected; 457 | if (onConnected != null) 458 | Connected(sender); 459 | } 460 | 461 | void newCon_Connecting(object sender) 462 | { 463 | var onConnecting = Connecting; 464 | if (onConnecting != null) 465 | onConnecting(sender); 466 | } 467 | 468 | private bool queueSequential(Notification notification) 469 | { 470 | if (sequential > notificationConnections.Count - 1) 471 | sequential = 0; 472 | 473 | if (notificationConnections[sequential] != null) 474 | return notificationConnections[sequential++].QueueNotification(notification); 475 | 476 | return false; 477 | } 478 | 479 | private bool queueRandom(Notification notification) 480 | { 481 | int index = rand.Next(0, notificationConnections.Count - 1); 482 | 483 | if (notificationConnections[index] != null) 484 | return notificationConnections[index].QueueNotification(notification); 485 | 486 | return false; 487 | } 488 | 489 | private static byte[] getAllBytesFromStream(Stream s) 490 | { 491 | byte[] buffer = new byte[16 * 1024]; 492 | 493 | using (MemoryStream ms = new MemoryStream()) 494 | { 495 | int read; 496 | 497 | while ((read = s.Read(buffer, 0, buffer.Length)) > 0) 498 | ms.Write(buffer, 0, read); 499 | 500 | return ms.ToArray(); 501 | } 502 | } 503 | #endregion 504 | } 505 | } 506 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications/NotificationServiceDistributionType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JdSoft.Apple.Apns.Notifications 6 | { 7 | /// 8 | /// Method used to load balance Notification Sending over the Apns Connections 9 | /// 10 | public enum NotificationServiceDistributionType 11 | { 12 | /// 13 | /// Loops through all connections in sequential order to ensure completely even distribution 14 | /// 15 | Sequential, 16 | /// 17 | /// Randomly chooses a connection to use 18 | /// 19 | Random 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("JdSoft.Apns.Notifications")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("JdSoft.Apns.Notifications")] 13 | [assembly: AssemblyCopyright("Copyright © 2009")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("5f2b5404-6924-4f28-94a2-6fd974be98bb")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.4.4")] 36 | [assembly: AssemblyFileVersion("1.0.4.4")] 37 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications/ThreadSafeQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JdSoft.Apple.Apns.Notifications 6 | { 7 | public class ThreadSafeQueue 8 | { 9 | 10 | public ThreadSafeQueue() 11 | { 12 | queue = new Queue(); 13 | lockObj = new object(); 14 | } 15 | 16 | Queue queue; 17 | object lockObj; 18 | 19 | public T Dequeue() 20 | { 21 | lock (lockObj) 22 | { 23 | return queue.Dequeue(); 24 | } 25 | } 26 | 27 | public void Enqueue(T item) 28 | { 29 | lock (lockObj) 30 | { 31 | queue.Enqueue(item); 32 | } 33 | } 34 | 35 | public int Count 36 | { 37 | get { return queue.Count; } 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.Notifications/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JdSoft.Apple.Apns.Notifications", "JdSoft.Apple.Apns.Notifications\JdSoft.Apple.Apns.Notifications.csproj", "{4CFB9AA8-55F8-46DC-B7BD-9E18B9939110}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JdSoft.Apple.Apns.Feedback", "JdSoft.Apple.Apns.Feedback\JdSoft.Apple.Apns.Feedback.csproj", "{B87C2903-2B6B-49FF-B735-630A5CB69521}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JdSoft.Apple.AppStore", "JdSoft.Apple.AppStore\JdSoft.Apple.AppStore.csproj", "{0B43E620-8E6F-4F13-B40E-89CA30C436D3}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JdSoft.Apple.Apns.Feedback.Test", "JdSoft.Apple.Apns.Feedback.Test\JdSoft.Apple.Apns.Feedback.Test.csproj", "{46CAEA10-404B-4E0A-B1A0-C1B128B55A8C}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JdSoft.Apple.AppStore.Test", "JdSoft.Apple.AppStore.Test\JdSoft.Apple.AppStore.Test.csproj", "{730A216F-B055-4C44-A095-65A098945A2C}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JdSoft.Apple.Apns.Notifications.Test", "JdSoft.Apple.Apns.Notifications.Test\JdSoft.Apple.Apns.Notifications.Test.csproj", "{3500A7AA-A8C8-4221-8265-48B02ECF2463}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JdSoft.Apple.Apns.Notifications.JsonTest", "JdSoft.Apple.Apns.Notifications.JsonTest\JdSoft.Apple.Apns.Notifications.JsonTest.csproj", "{1A7ACE5F-EF8A-4C5E-87B5-CBBFA432DFE4}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BA4010CC-021A-4968-9867-145F7CB17D8E}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Notifications", "Tests.Notifications\Tests.Notifications.csproj", "{0AD797AA-2793-4E8A-A481-60B89DE36680}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{85551CB3-03EB-4A50-8104-9810A38B860D}" 23 | ProjectSection(SolutionItems) = preProject 24 | JdSoft.Apple.Apns.vsmdi = JdSoft.Apple.Apns.vsmdi 25 | Local.testsettings = Local.testsettings 26 | TraceAndTestImpact.testsettings = TraceAndTestImpact.testsettings 27 | EndProjectSection 28 | EndProject 29 | Global 30 | GlobalSection(TestCaseManagementSettings) = postSolution 31 | CategoryFile = JdSoft.Apple.Apns.vsmdi 32 | EndGlobalSection 33 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 34 | Debug|Any CPU = Debug|Any CPU 35 | Release|Any CPU = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 38 | {4CFB9AA8-55F8-46DC-B7BD-9E18B9939110}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {4CFB9AA8-55F8-46DC-B7BD-9E18B9939110}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {4CFB9AA8-55F8-46DC-B7BD-9E18B9939110}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {4CFB9AA8-55F8-46DC-B7BD-9E18B9939110}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {B87C2903-2B6B-49FF-B735-630A5CB69521}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {B87C2903-2B6B-49FF-B735-630A5CB69521}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {B87C2903-2B6B-49FF-B735-630A5CB69521}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {B87C2903-2B6B-49FF-B735-630A5CB69521}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {0B43E620-8E6F-4F13-B40E-89CA30C436D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {0B43E620-8E6F-4F13-B40E-89CA30C436D3}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {0B43E620-8E6F-4F13-B40E-89CA30C436D3}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {0B43E620-8E6F-4F13-B40E-89CA30C436D3}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {46CAEA10-404B-4E0A-B1A0-C1B128B55A8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {46CAEA10-404B-4E0A-B1A0-C1B128B55A8C}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {46CAEA10-404B-4E0A-B1A0-C1B128B55A8C}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {46CAEA10-404B-4E0A-B1A0-C1B128B55A8C}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {730A216F-B055-4C44-A095-65A098945A2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {730A216F-B055-4C44-A095-65A098945A2C}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {730A216F-B055-4C44-A095-65A098945A2C}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {730A216F-B055-4C44-A095-65A098945A2C}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {3500A7AA-A8C8-4221-8265-48B02ECF2463}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {3500A7AA-A8C8-4221-8265-48B02ECF2463}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {3500A7AA-A8C8-4221-8265-48B02ECF2463}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {3500A7AA-A8C8-4221-8265-48B02ECF2463}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {1A7ACE5F-EF8A-4C5E-87B5-CBBFA432DFE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {1A7ACE5F-EF8A-4C5E-87B5-CBBFA432DFE4}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {1A7ACE5F-EF8A-4C5E-87B5-CBBFA432DFE4}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {1A7ACE5F-EF8A-4C5E-87B5-CBBFA432DFE4}.Release|Any CPU.Build.0 = Release|Any CPU 66 | {0AD797AA-2793-4E8A-A481-60B89DE36680}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {0AD797AA-2793-4E8A-A481-60B89DE36680}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {0AD797AA-2793-4E8A-A481-60B89DE36680}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {0AD797AA-2793-4E8A-A481-60B89DE36680}.Release|Any CPU.Build.0 = Release|Any CPU 70 | EndGlobalSection 71 | GlobalSection(SolutionProperties) = preSolution 72 | HideSolutionNode = FALSE 73 | EndGlobalSection 74 | GlobalSection(NestedProjects) = preSolution 75 | {0AD797AA-2793-4E8A-A481-60B89DE36680} = {BA4010CC-021A-4968-9867-145F7CB17D8E} 76 | {85551CB3-03EB-4A50-8104-9810A38B860D} = {BA4010CC-021A-4968-9867-145F7CB17D8E} 77 | EndGlobalSection 78 | GlobalSection(MonoDevelopProperties) = preSolution 79 | StartupItem = JdSoft.Apple.Apns.Notifications\JdSoft.Apple.Apns.Notifications.csproj 80 | version = 0.1 81 | EndGlobalSection 82 | EndGlobal 83 | -------------------------------------------------------------------------------- /JdSoft.Apple.Apns.vsmdi: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /JdSoft.Apple.AppStore.Test/JdSoft.Apple.AppStore.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {730A216F-B055-4C44-A095-65A098945A2C} 9 | WinExe 10 | Properties 11 | JdSoft.Apple.AppStore.Test 12 | JdSoft.Apple.AppStore.Test 13 | v3.5 14 | 512 15 | 16 | 17 | 18 | 19 | 3.5 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | Form 49 | 50 | 51 | frmReceiptData.cs 52 | 53 | 54 | 55 | 56 | frmReceiptData.cs 57 | 58 | 59 | 60 | 61 | {0B43E620-8E6F-4F13-B40E-89CA30C436D3} 62 | JdSoft.Apple.AppStore 63 | 64 | 65 | 66 | 73 | -------------------------------------------------------------------------------- /JdSoft.Apple.AppStore.Test/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows.Forms; 4 | using JdSoft.Apple.AppStore; 5 | 6 | namespace JdSoft.Apple.AppStore.Test 7 | { 8 | static class Program 9 | { 10 | /// 11 | /// The main entry point for the application. 12 | /// 13 | [STAThread] 14 | static void Main() 15 | { 16 | Application.EnableVisualStyles(); 17 | Application.SetCompatibleTextRenderingDefault(false); 18 | Application.Run(new frmReceiptData()); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /JdSoft.Apple.AppStore.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("JdSoft.Apple.AppStore.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("JdSoft.Apple.AppStore.Test")] 13 | [assembly: AssemblyCopyright("Copyright © 2009")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("d2992b1a-496e-4267-b5fe-c149bf299d62")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.4.4")] 36 | [assembly: AssemblyFileVersion("1.0.4.4")] 37 | -------------------------------------------------------------------------------- /JdSoft.Apple.AppStore.Test/Properties/Resources.resources: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Redth/APNS-Sharp/e30a9990a466f8fc0d054224bbf8d599a67311f3/JdSoft.Apple.AppStore.Test/Properties/Resources.resources -------------------------------------------------------------------------------- /JdSoft.Apple.AppStore.Test/frmReceiptData.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace JdSoft.Apple.AppStore.Test 2 | { 3 | partial class frmReceiptData 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.groupBox1 = new System.Windows.Forms.GroupBox(); 32 | this.textReceiptData = new System.Windows.Forms.TextBox(); 33 | this.checkSandbox = new System.Windows.Forms.CheckBox(); 34 | this.buttonPaste = new System.Windows.Forms.Button(); 35 | this.buttonOk = new System.Windows.Forms.Button(); 36 | this.groupBox2 = new System.Windows.Forms.GroupBox(); 37 | this.textResults = new System.Windows.Forms.TextBox(); 38 | this.groupBox1.SuspendLayout(); 39 | this.groupBox2.SuspendLayout(); 40 | this.SuspendLayout(); 41 | // 42 | // groupBox1 43 | // 44 | this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 45 | | System.Windows.Forms.AnchorStyles.Right))); 46 | this.groupBox1.Controls.Add(this.textReceiptData); 47 | this.groupBox1.Controls.Add(this.checkSandbox); 48 | this.groupBox1.Location = new System.Drawing.Point(12, 12); 49 | this.groupBox1.Name = "groupBox1"; 50 | this.groupBox1.Size = new System.Drawing.Size(481, 199); 51 | this.groupBox1.TabIndex = 0; 52 | this.groupBox1.TabStop = false; 53 | this.groupBox1.Text = "Paste the Receipt Data Below (it should look like JSON) as you received it from t" + 54 | "he iPhone:"; 55 | // 56 | // textReceiptData 57 | // 58 | this.textReceiptData.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 59 | | System.Windows.Forms.AnchorStyles.Left) 60 | | System.Windows.Forms.AnchorStyles.Right))); 61 | this.textReceiptData.Location = new System.Drawing.Point(5, 36); 62 | this.textReceiptData.Multiline = true; 63 | this.textReceiptData.Name = "textReceiptData"; 64 | this.textReceiptData.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; 65 | this.textReceiptData.Size = new System.Drawing.Size(470, 157); 66 | this.textReceiptData.TabIndex = 0; 67 | // 68 | // checkSandbox 69 | // 70 | this.checkSandbox.AutoSize = true; 71 | this.checkSandbox.Location = new System.Drawing.Point(6, 19); 72 | this.checkSandbox.Name = "checkSandbox"; 73 | this.checkSandbox.Size = new System.Drawing.Size(98, 17); 74 | this.checkSandbox.TabIndex = 3; 75 | this.checkSandbox.Text = "Sandbox Mode"; 76 | this.checkSandbox.UseVisualStyleBackColor = true; 77 | // 78 | // buttonPaste 79 | // 80 | this.buttonPaste.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 81 | this.buttonPaste.Location = new System.Drawing.Point(344, 217); 82 | this.buttonPaste.Name = "buttonPaste"; 83 | this.buttonPaste.Size = new System.Drawing.Size(149, 23); 84 | this.buttonPaste.TabIndex = 1; 85 | this.buttonPaste.Text = "Paste From Clipboard"; 86 | this.buttonPaste.UseVisualStyleBackColor = true; 87 | this.buttonPaste.Click += new System.EventHandler(this.buttonPaste_Click); 88 | // 89 | // buttonOk 90 | // 91 | this.buttonOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 92 | this.buttonOk.Location = new System.Drawing.Point(344, 424); 93 | this.buttonOk.Name = "buttonOk"; 94 | this.buttonOk.Size = new System.Drawing.Size(149, 23); 95 | this.buttonOk.TabIndex = 2; 96 | this.buttonOk.Text = "Verify Receipt"; 97 | this.buttonOk.UseVisualStyleBackColor = true; 98 | this.buttonOk.Click += new System.EventHandler(this.buttonOk_Click); 99 | // 100 | // groupBox2 101 | // 102 | this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 103 | | System.Windows.Forms.AnchorStyles.Left) 104 | | System.Windows.Forms.AnchorStyles.Right))); 105 | this.groupBox2.Controls.Add(this.textResults); 106 | this.groupBox2.Location = new System.Drawing.Point(12, 242); 107 | this.groupBox2.Name = "groupBox2"; 108 | this.groupBox2.Size = new System.Drawing.Size(480, 172); 109 | this.groupBox2.TabIndex = 4; 110 | this.groupBox2.TabStop = false; 111 | this.groupBox2.Text = "Results:"; 112 | // 113 | // textResults 114 | // 115 | this.textResults.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 116 | | System.Windows.Forms.AnchorStyles.Left) 117 | | System.Windows.Forms.AnchorStyles.Right))); 118 | this.textResults.Location = new System.Drawing.Point(6, 19); 119 | this.textResults.Multiline = true; 120 | this.textResults.Name = "textResults"; 121 | this.textResults.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; 122 | this.textResults.Size = new System.Drawing.Size(468, 146); 123 | this.textResults.TabIndex = 0; 124 | // 125 | // frmReceiptData 126 | // 127 | this.AcceptButton = this.buttonOk; 128 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 129 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 130 | this.ClientSize = new System.Drawing.Size(509, 462); 131 | this.Controls.Add(this.groupBox2); 132 | this.Controls.Add(this.buttonOk); 133 | this.Controls.Add(this.buttonPaste); 134 | this.Controls.Add(this.groupBox1); 135 | this.MinimumSize = new System.Drawing.Size(525, 500); 136 | this.Name = "frmReceiptData"; 137 | this.Text = "Receipt Data"; 138 | this.Load += new System.EventHandler(this.frmReceiptData_Load); 139 | this.groupBox1.ResumeLayout(false); 140 | this.groupBox1.PerformLayout(); 141 | this.groupBox2.ResumeLayout(false); 142 | this.groupBox2.PerformLayout(); 143 | this.ResumeLayout(false); 144 | 145 | } 146 | 147 | #endregion 148 | 149 | private System.Windows.Forms.GroupBox groupBox1; 150 | private System.Windows.Forms.TextBox textReceiptData; 151 | private System.Windows.Forms.Button buttonPaste; 152 | private System.Windows.Forms.Button buttonOk; 153 | private System.Windows.Forms.CheckBox checkSandbox; 154 | private System.Windows.Forms.GroupBox groupBox2; 155 | private System.Windows.Forms.TextBox textResults; 156 | } 157 | } 158 | 159 | -------------------------------------------------------------------------------- /JdSoft.Apple.AppStore.Test/frmReceiptData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Text; 7 | using System.Windows.Forms; 8 | 9 | namespace JdSoft.Apple.AppStore.Test 10 | { 11 | public partial class frmReceiptData : Form 12 | { 13 | 14 | public frmReceiptData() 15 | { 16 | InitializeComponent(); 17 | } 18 | 19 | private void frmReceiptData_Load(object sender, EventArgs e) 20 | { 21 | } 22 | 23 | private void buttonOk_Click(object sender, EventArgs e) 24 | { 25 | string receiptData = this.textReceiptData.Text; 26 | 27 | if (!string.IsNullOrEmpty(receiptData)) 28 | { 29 | //New verification instance 30 | Receipt receipt = ReceiptVerification.GetReceipt(this.checkSandbox.Checked, receiptData); 31 | 32 | //Do the actual verification 33 | // this makes the https post to itunes verification servers 34 | if (ReceiptVerification.IsReceiptValid(receipt)) 35 | AppendText("iTunes Receipt Verification: OK!"); 36 | else 37 | AppendText("iTunes Receipt Verification: Failed!"); 38 | 39 | AppendText(string.Empty); 40 | 41 | //Spit out the info 42 | if (receipt != null) 43 | { 44 | AppendText("RECEIPT DATA:"); 45 | //AppendText(string.Format(" AppItemId: {0}", receipt.ProjectId)); 46 | //AppendText(string.Format(" Bid: {0}", receipt.Bid)); 47 | AppendText(string.Format(" Bvrs: {0}", receipt.Bvrs)); 48 | AppendText(string.Format(" OriginalPurchaseDate: {0}", receipt.OriginalPurchaseDate)); 49 | AppendText(string.Format(" OriginalTransactionId: {0}",receipt.OriginalTransactionId)); 50 | AppendText(string.Format(" ProductId: {0}", receipt.ProductId)); 51 | AppendText(string.Format(" PurchaseDate: {0}", receipt.PurchaseDate)); 52 | AppendText(string.Format(" Quantity: {0}", receipt.Quantity)); 53 | //AppendText(string.Format(" Timestamp: {0}", receipt.Timestamp)); 54 | AppendText(string.Format(" TransactionId: {0}", receipt.TransactionId)); 55 | //AppendText(string.Format(" VerifyStatus: {0}", receipt.VerifyStatus)); 56 | //AppendText(string.Format(" VersionExternalIdentifier: {0}", receipt.VersionExternalIdentifier)); 57 | AppendText(string.Empty); 58 | } 59 | } 60 | else 61 | { 62 | AppendText("No Receipt Data Entered"); 63 | } 64 | 65 | 66 | AppendText(string.Empty); 67 | } 68 | 69 | private void buttonPaste_Click(object sender, EventArgs e) 70 | { 71 | this.textReceiptData.Text = Clipboard.GetText(); 72 | } 73 | 74 | private void AppendText(string text) 75 | { 76 | this.textResults.AppendText(text + Environment.NewLine); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /JdSoft.Apple.AppStore.Test/frmReceiptData.resources: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Redth/APNS-Sharp/e30a9990a466f8fc0d054224bbf8d599a67311f3/JdSoft.Apple.AppStore.Test/frmReceiptData.resources -------------------------------------------------------------------------------- /JdSoft.Apple.AppStore.Test/frmReceiptData.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /JdSoft.Apple.AppStore/JdSoft.Apple.AppStore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {0B43E620-8E6F-4F13-B40E-89CA30C436D3} 9 | Library 10 | Properties 11 | JdSoft.Apple.AppStore 12 | JdSoft.Apple.AppStore 13 | v3.5 14 | 512 15 | 16 | 17 | 18 | 19 | 3.5 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | ..\packages\Newtonsoft.Json.4.0.8\lib\net35\Newtonsoft.Json.dll 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 62 | -------------------------------------------------------------------------------- /JdSoft.Apple.AppStore/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("JdSoft.Apple.AppStore")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("JdSoft.Apple.AppStore")] 13 | [assembly: AssemblyCopyright("Copyright © 2009")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("0ee8c31f-21eb-447e-991c-e28046a180cf")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.4.4")] 36 | [assembly: AssemblyFileVersion("1.0.4.4")] 37 | -------------------------------------------------------------------------------- /JdSoft.Apple.AppStore/Receipt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Newtonsoft.Json.Linq; 5 | 6 | namespace JdSoft.Apple.AppStore 7 | { 8 | [Serializable()] 9 | public class Receipt 10 | { 11 | #region Constructor 12 | 13 | /// 14 | /// Creates the receipt from Apple's Response 15 | /// 16 | /// 17 | public Receipt(string receipt) 18 | { 19 | JObject json = JObject.Parse(receipt); 20 | 21 | int status = -1; 22 | 23 | int.TryParse(json["status"].ToString(), out status); 24 | this.Status = status; 25 | 26 | // Receipt is actually a child 27 | json = (JObject)json["receipt"]; 28 | 29 | 30 | this.OriginalTransactionId = json["original_transaction_id"].ToString(); 31 | this.Bvrs = json["bvrs"].ToString(); 32 | this.ProductId = json["product_id"].ToString(); 33 | 34 | DateTime purchaseDate = DateTime.MinValue; 35 | if (DateTime.TryParseExact(json["purchase_date"].ToString().Replace(" Etc/GMT", string.Empty).Replace("\"", string.Empty).Trim(), "yyyy-MM-dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out purchaseDate)) 36 | this.PurchaseDate = purchaseDate; 37 | 38 | DateTime originalPurchaseDate = DateTime.MinValue; 39 | if (DateTime.TryParseExact(json["original_purchase_date"].ToString().Replace(" Etc/GMT", string.Empty).Replace("\"", string.Empty).Trim(), "yyyy-MM-dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out originalPurchaseDate)) 40 | this.OriginalPurchaseDate = originalPurchaseDate; 41 | 42 | int quantity = 1; 43 | int.TryParse(json["quantity"].ToString(), out quantity); 44 | this.Quantity = quantity; 45 | 46 | this.BundleIdentifier = json["bid"].ToString(); 47 | 48 | this.TransactionId = json["transaction_id"].ToString(); 49 | } 50 | 51 | #endregion Constructor 52 | 53 | #region Properties 54 | 55 | public string OriginalTransactionId 56 | { 57 | get; 58 | set; 59 | } 60 | 61 | public string Bvrs 62 | { 63 | get; 64 | set; 65 | } 66 | 67 | public string ProductId 68 | { 69 | get; 70 | set; 71 | } 72 | 73 | public DateTime? PurchaseDate 74 | { 75 | get; 76 | set; 77 | } 78 | 79 | public int Quantity 80 | { 81 | get; 82 | set; 83 | } 84 | 85 | public string BundleIdentifier 86 | { 87 | get; 88 | set; 89 | } 90 | 91 | public DateTime? OriginalPurchaseDate 92 | { 93 | get; 94 | set; 95 | } 96 | 97 | public string TransactionId 98 | { 99 | get; 100 | set; 101 | } 102 | 103 | public int Status 104 | { 105 | get; 106 | set; 107 | } 108 | 109 | /// 110 | /// For whatever use you please :) 111 | /// 112 | public object Tag 113 | { 114 | get; 115 | set; 116 | } 117 | #endregion Properties 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /JdSoft.Apple.AppStore/ReceiptVerification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Text.RegularExpressions; 5 | using System.Net; 6 | 7 | namespace JdSoft.Apple.AppStore 8 | { 9 | public class ReceiptVerification 10 | { 11 | #region Constants 12 | private const string urlSandbox = "https://sandbox.itunes.apple.com/verifyReceipt"; 13 | private const string urlProduction = "https://buy.itunes.apple.com/verifyReceipt"; 14 | #endregion 15 | 16 | #region Public Static Methods 17 | /// 18 | /// Sends the ReceiptData to the Verification Url to be verified. 19 | /// 20 | /// If true, the Receipt Verification Server indicates a valid transaction response 21 | public static bool IsReceiptValid(Receipt receipt) 22 | { 23 | return (receipt != null && receipt.Status == 0); 24 | } 25 | 26 | public static Receipt GetReceipt(bool sandbox, string receiptData) 27 | { 28 | return GetReceipt(sandbox ? urlSandbox : urlProduction, receiptData); 29 | } 30 | 31 | public static Receipt GetReceipt(string url, string receiptData) 32 | { 33 | Receipt result = null; 34 | 35 | string post = PostRequest(url, ConvertReceiptToPost(receiptData)); 36 | 37 | if (!string.IsNullOrEmpty(post)) 38 | { 39 | try { result = new Receipt(post); } 40 | catch { result = null; } 41 | } 42 | 43 | return result; 44 | } 45 | #endregion 46 | 47 | #region Private Static Methods 48 | 49 | /// 50 | /// Make a string with the receipt encoded 51 | /// 52 | /// 53 | /// 54 | private static string ConvertReceiptToPost(string receipt) 55 | { 56 | //string itunesDecodedReceipt = Encoding.UTF8.GetString(ReceiptVerification.ConvertAppStoreTokenToBytes(receipt.Replace("<", string.Empty).Replace(">", string.Empty))).Trim(); 57 | string itunesDecodedReceipt = receipt.Replace("<", string.Empty).Replace(">", string.Empty).Trim(); 58 | string encodedReceipt = Base64Encode(itunesDecodedReceipt); 59 | return string.Format(@"{{""receipt-data"":""{0}""}}", encodedReceipt); 60 | } 61 | 62 | /// 63 | /// Base64 Encoding 64 | /// 65 | /// 66 | /// 67 | private static string Base64Encode(string str) 68 | { 69 | byte[] encbuff = System.Text.Encoding.UTF8.GetBytes(str); 70 | return Convert.ToBase64String(encbuff); 71 | } 72 | 73 | /// 74 | /// Base64 Decoding 75 | /// 76 | /// 77 | /// 78 | private static string Base64Decode(string str) 79 | { 80 | byte[] decbuff = Convert.FromBase64String(str); 81 | return System.Text.Encoding.UTF8.GetString(decbuff); 82 | } 83 | 84 | /// 85 | /// Sends a request to the server and reads the response 86 | /// 87 | /// 88 | /// 89 | /// 90 | private static string PostRequest(string url, string postData) 91 | { 92 | byte[] byteArray = Encoding.UTF8.GetBytes(postData); 93 | return PostRequest(url, byteArray); 94 | } 95 | 96 | /// 97 | /// Sends a request to the server and reads the response 98 | /// 99 | /// 100 | /// 101 | /// 102 | private static string PostRequest(string url, byte[] byteArray) 103 | { 104 | try 105 | { 106 | WebRequest request = HttpWebRequest.Create(url); 107 | request.Method = "POST"; 108 | request.ContentLength = byteArray.Length; 109 | request.ContentType = "text/plain"; 110 | 111 | using (System.IO.Stream dataStream = request.GetRequestStream()) 112 | { 113 | dataStream.Write(byteArray, 0, byteArray.Length); 114 | dataStream.Close(); 115 | } 116 | 117 | using (WebResponse r = request.GetResponse()) 118 | { 119 | using (System.IO.StreamReader sr = new System.IO.StreamReader(r.GetResponseStream())) 120 | { 121 | return sr.ReadToEnd(); 122 | } 123 | } 124 | } 125 | catch (Exception ex) 126 | { 127 | return string.Empty; 128 | } 129 | } 130 | 131 | /// 132 | /// Takes the receipt from Apple's App Store and converts it to bytes 133 | /// that we can understand 134 | /// 135 | /// 136 | /// 137 | private static byte[] ConvertAppStoreTokenToBytes(string token) 138 | { 139 | token = token.Replace(" ", string.Empty); 140 | int i = 0; 141 | int b = 0; 142 | List bytes = new List(); 143 | while (i < token.Length) 144 | { 145 | bytes.Add(Convert.ToByte(token.Substring(i, 2), 16)); 146 | i += 2; 147 | b++; 148 | } 149 | 150 | return bytes.ToArray(); 151 | } 152 | 153 | #endregion Private Static Methods 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /JdSoft.Apple.AppStore/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /Local.testsettings: -------------------------------------------------------------------------------- 1 |  2 | 3 | These are default test settings for a local test run. 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ATTENTION: THIS LIBRARY IS NOW OBSOLETE / DEPRECATED! 2 | ===================================================== 3 | I've recently started a new project called [PushSharp](https://github.com/Redth/PushSharp/). Its goal is to combine APNS-Sharp as well as C2DM-Sharp into a single project. It takes some of the same great code from APNS-Sharp, and still allows you to easily send push notifications, but it also includes an optional abstraction layer for sending notifications to multiple platforms. Please go check it out. Once PushSharp is a bit more mature, this project will be deprecated. For now I will not be adding any major new functionality to this library. 4 | 5 | So go check out [PushSharp](https://github.com/Redth/PushSharp/)! It's open source (under Apache 2.0), and a solid step forward for push notifications! 6 | 7 | (https://github.com/Redth/PushSharp/) 8 | 9 | 10 | 11 | Apple Push Notification & Feedback Services Client C# Library 12 | ============================================================= 13 | 14 | A free, open source, independent and mono compatible C#/.NET Library for interacting with Apple's Push Notification & Feedback Services for the iPhone/iPod. 15 | 16 | ##News 17 | + **May 9, 2011** Moved project to Github! 18 | + **March 27, 2010** Updated to 1.0.3.0 with a couple bug fixes 19 | + **January 18, 2010** Added a very simple sample solution for MonoTouch that shows how implement Push Notifications in a MonoTouch? application 20 | + **December 22, 2009** [Apns-Sharp now Powering G-Push Mail](http://redth.info/2009/12/22/apns-sharp-updated-and-now-powering-g-push-mail/) (New Version: 1.0.2.0) 21 | 22 | ##Features 23 | + Push Notifications Client 24 | + Feedback Service Client 25 | + iTunes Receipt Verification Client 26 | 27 | ##Details 28 | This is a pretty simplistic library, but it should give all C# developers a jump start into developing for Apple's Push Notifications platform. Feel free to use it as needed. 29 | 30 | One of the goals was to make this mono compatible, since I needed it myself to run on Debian/Mono 2.4 on Amazon's EC2 Platform. This works great on mono 2.4, I have not tested it on anything below 2.4. 31 | 32 | I have also included a simple iTunes/AppStore Receipt Verification Library that simplifies the process for verifying the receipt data from In-App Purchases. 33 | 34 | ##Testing 35 | The code for the Apns Notifications is fairly well tested and currently in use in a production app. The code for the Feedback service should be ok, however I'm working on integrating it into my production app at this point. The AppStore? code is the least tested of all, please report any bugs you run into with this! 36 | 37 | I would encourage you to checkout the source and use that! Please use this project page's issue tracker to send me bugs, and I will fix them! 38 | 39 | ##Contributing 40 | If you have improvements to my code, by all means let me know. If you'd like to become a contributer to the project, I'd be happy to make that happen too! 41 | 42 | ##License 43 | This code is licensed under the MIT License. Feel free to use it, abuse it, bundle it, repackage it, and make money off it! Seriously, go nuts! 44 | 45 | ##MIT License 46 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 47 | 48 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 49 | 50 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /References/Newtonsoft.Json.Compact.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Redth/APNS-Sharp/e30a9990a466f8fc0d054224bbf8d599a67311f3/References/Newtonsoft.Json.Compact.dll -------------------------------------------------------------------------------- /Tests.Notifications/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Tests.Notifications")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Tests.Notifications")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("915e6cb7-569f-42a7-be6c-baa27bbd88e5")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /Tests.Notifications/Tests.Notifications.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 7 | 8 | 2.0 9 | {0AD797AA-2793-4E8A-A481-60B89DE36680} 10 | Library 11 | Properties 12 | Tests.Notifications 13 | Tests.Notifications 14 | v4.0 15 | 512 16 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 3.5 40 | 41 | 42 | 43 | 44 | False 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {4CFB9AA8-55F8-46DC-B7BD-9E18B9939110} 54 | JdSoft.Apple.Apns.Notifications 55 | 56 | 57 | 58 | 65 | -------------------------------------------------------------------------------- /Tests.Notifications/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using JdSoft.Apple.Apns.Notifications; 6 | 7 | namespace Tests.Notifications 8 | { 9 | [TestClass] 10 | public class NotificationCreationTest 11 | { 12 | [TestMethod] 13 | public void NewNotificationTest() 14 | { 15 | var n = new Notification("", new NotificationPayload("This is a test", 9, null)); 16 | 17 | Assert.IsNotNull(n); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TraceAndTestImpact.testsettings: -------------------------------------------------------------------------------- 1 |  2 | 3 | These are test settings for Trace and Test Impact. 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /nuget/apns-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Redth/APNS-Sharp/e30a9990a466f8fc0d054224bbf8d599a67311f3/nuget/apns-icon.png -------------------------------------------------------------------------------- /nuget/apns-sharp.1.0.4.1.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Redth/APNS-Sharp/e30a9990a466f8fc0d054224bbf8d599a67311f3/nuget/apns-sharp.1.0.4.1.nupkg -------------------------------------------------------------------------------- /nuget/apns-sharp.1.0.4.3.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Redth/APNS-Sharp/e30a9990a466f8fc0d054224bbf8d599a67311f3/nuget/apns-sharp.1.0.4.3.nupkg --------------------------------------------------------------------------------