Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

restructured the project

  • Loading branch information...
commit 72cfc042e201ba933d1b85a9e14ac458fc04d52f 1 parent 3555801
Daniel Draper authored
View
13 .gitignore
@@ -0,0 +1,13 @@
+[Oo]bj/
+[Bb]in/
+_ReSharper.*
+*.csproj.user
+*.resharper.user
+*.resharper
+*.suo
+*.cache
+*~
+*.swp
+*.resharper.user
+*.rptproj.user
+*.db
View
53 Application/MoonApns/Feedback.cs
@@ -0,0 +1,53 @@
+/*Copyright 2011 Arash Norouzi
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+using System;
+
+namespace MoonAPNS
+{
+ /// <summary>
+ /// Feedback object
+ /// </summary>
+ public class Feedback
+ {
+
+ /// <summary>
+ /// Constructor
+ /// </summary>
+ public Feedback()
+ {
+ this.DeviceToken = string.Empty;
+ this.Timestamp = DateTime.MinValue;
+ }
+
+ /// <summary>
+ /// Device Token string in hex form without any spaces or dashes
+ /// </summary>
+ public string DeviceToken
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// Timestamp of the Feedback for when Apple received the notice to stop sending notifications to the device
+ /// </summary>
+ public DateTime Timestamp
+ {
+ get;
+ set;
+ }
+ }
+}
View
113 Application/MoonApns/MoonAPNS.csproj
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{A91E03EF-6532-4896-89C4-9FCEC1D8D4DB}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>MoonAPNS</RootNamespace>
+ <AssemblyName>MoonAPNS</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <TargetFrameworkProfile>Client</TargetFrameworkProfile>
+ <FileAlignment>512</FileAlignment>
+ <PublishUrl>publish\</PublishUrl>
+ <Install>true</Install>
+ <InstallFrom>Disk</InstallFrom>
+ <UpdateEnabled>false</UpdateEnabled>
+ <UpdateMode>Foreground</UpdateMode>
+ <UpdateInterval>7</UpdateInterval>
+ <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+ <UpdatePeriodically>false</UpdatePeriodically>
+ <UpdateRequired>false</UpdateRequired>
+ <MapFileExtensions>true</MapFileExtensions>
+ <ApplicationRevision>0</ApplicationRevision>
+ <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+ <IsWebBootstrapper>false</IsWebBootstrapper>
+ <UseApplicationTrust>false</UseApplicationTrust>
+ <BootstrapperEnabled>true</BootstrapperEnabled>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+ <PlatformTarget>x86</PlatformTarget>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+ <PlatformTarget>x86</PlatformTarget>
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup>
+ <StartupObject />
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Newtonsoft.Json">
+ <HintPath>..\packages\Newtonsoft.Json.4.0.2\lib\net40\Newtonsoft.Json.dll</HintPath>
+ </Reference>
+ <Reference Include="NLog">
+ <HintPath>..\packages\NLog.2.0.0.2000\lib\net40\NLog.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Feedback.cs" />
+ <Compile Include="NotificationAlert.cs" />
+ <Compile Include="NotificationPayload.cs" />
+ <Compile Include="Program.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="PushNotification.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="NLog.config">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </None>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client">
+ <Visible>False</Visible>
+ <ProductName>Microsoft .NET Framework 4 Client Profile %28x86 and x64%29</ProductName>
+ <Install>true</Install>
+ </BootstrapperPackage>
+ <BootstrapperPackage Include="Microsoft.Net.Client.3.5">
+ <Visible>False</Visible>
+ <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
+ <Install>false</Install>
+ </BootstrapperPackage>
+ <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+ <Visible>False</Visible>
+ <ProductName>.NET Framework 3.5 SP1</ProductName>
+ <Install>false</Install>
+ </BootstrapperPackage>
+ <BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
+ <Visible>False</Visible>
+ <ProductName>Windows Installer 3.1</ProductName>
+ <Install>true</Install>
+ </BootstrapperPackage>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
View
14 Application/MoonApns/NLog.config
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+ <!-- make sure to set 'Copy To Output Directory' option for this file -->
+ <!-- go to http://nlog-project.org/wiki/Configuration_file for more information -->
+
+ <targets>
+ <target name="logfile" xsi:type="File" fileName="D:\MoonAPNS\MoonAPNS\MoonAPNS\Logs\Push Notification\${date:format=yyyyMMdd}.log" archiveEvery="Day" layout="${date:format=yyyyMMdd HHmm} - ${level} - ${message}" createDirs="true"/>
+ </targets>
+
+ <rules>
+ <logger name="*" minlevel="Info" writeTo="logfile"/>
+ </rules>
+</nlog>
View
94 Application/MoonApns/NotificationAlert.cs
@@ -0,0 +1,94 @@
+/*Copyright 2011 Arash Norouzi
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+using System.Collections.Generic;
+
+namespace MoonAPNS
+{
+ /// <summary>
+ /// Alert Portion of the Notification Payload
+ /// </summary>
+ public class NotificationAlert
+ {
+ /// <summary>
+ /// Constructor
+ /// </summary>
+ public NotificationAlert()
+ {
+ Body = null;
+ ActionLocalizedKey = null;
+ LocalizedKey = null;
+ LocalizedArgs = new List<object>();
+ }
+
+ /// <summary>
+ /// Body Text of the Notification's Alert
+ /// </summary>
+ public string Body
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// Action Button's Localized Key
+ /// </summary>
+ public string ActionLocalizedKey
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// Localized Key
+ /// </summary>
+ public string LocalizedKey
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// Localized Argument List
+ /// </summary>
+ public List<object> LocalizedArgs
+ {
+ get;
+ set;
+ }
+
+ public void AddLocalizedArgs(params object[] values)
+ {
+ this.LocalizedArgs.AddRange(values);
+ }
+
+ /// <summary>
+ /// Determines if the Alert is empty and should be excluded from the Notification Payload
+ /// </summary>
+ public bool IsEmpty
+ {
+ get
+ {
+ if (!string.IsNullOrEmpty(Body)
+ || !string.IsNullOrEmpty(ActionLocalizedKey)
+ || !string.IsNullOrEmpty(LocalizedKey)
+ || (LocalizedArgs != null && LocalizedArgs.Count > 0))
+ return false;
+ else
+ return true;
+ }
+ }
+ }
+}
View
148 Application/MoonApns/NotificationPayload.cs
@@ -0,0 +1,148 @@
+/*Copyright 2011 Arash Norouzi
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Newtonsoft.Json.Linq;
+
+namespace MoonAPNS
+{
+ public class NotificationPayload
+ {
+ public NotificationAlert Alert { get; set; }
+
+ public string DeviceToken { get; set; }
+
+ public int? Badge { get; set; }
+
+ public string Sound { get; set; }
+
+ internal int PayloadId { get; set; }
+
+ public Dictionary<string, object[]> CustomItems
+ {
+ get;
+ private set;
+ }
+
+ public NotificationPayload(string deviceToken)
+ {
+ DeviceToken = deviceToken;
+ Alert = new NotificationAlert();
+ CustomItems = new Dictionary<string, object[]>();
+ }
+
+ public NotificationPayload(string deviceToken, string alert)
+ {
+ DeviceToken = deviceToken;
+ Alert = new NotificationAlert() { Body = alert };
+ CustomItems = new Dictionary<string, object[]>();
+ }
+
+ public NotificationPayload(string deviceToken, string alert, int badge)
+ {
+ DeviceToken = deviceToken;
+ Alert = new NotificationAlert() { Body = alert };
+ Badge = badge;
+ CustomItems = new Dictionary<string, object[]>();
+ }
+
+ public NotificationPayload(string deviceToken, string alert, int badge, string sound)
+ {
+ DeviceToken = deviceToken;
+ Alert = new NotificationAlert() { Body = alert };
+ Badge = badge;
+ Sound = sound;
+ CustomItems = new Dictionary<string, object[]>();
+ }
+
+ public void AddCustom(string key, params object[] values)
+ {
+ if (values != null)
+ this.CustomItems.Add(key, values);
+ }
+
+ public string ToJson()
+ {
+ JObject json = new JObject();
+
+ JObject aps = new JObject();
+
+ if (!this.Alert.IsEmpty)
+ {
+ if (!string.IsNullOrEmpty(this.Alert.Body)
+ && string.IsNullOrEmpty(this.Alert.LocalizedKey)
+ && string.IsNullOrEmpty(this.Alert.ActionLocalizedKey)
+ && (this.Alert.LocalizedArgs == null || this.Alert.LocalizedArgs.Count <= 0))
+ {
+ aps["alert"] = new JValue(this.Alert.Body);
+ }
+ else
+ {
+ JObject jsonAlert = new JObject();
+
+ if (!string.IsNullOrEmpty(this.Alert.LocalizedKey))
+ jsonAlert["loc-key"] = new JValue(this.Alert.LocalizedKey);
+
+ if (this.Alert.LocalizedArgs != null && this.Alert.LocalizedArgs.Count > 0)
+ jsonAlert["loc-args"] = new JArray(this.Alert.LocalizedArgs.ToArray());
+
+ if (!string.IsNullOrEmpty(this.Alert.Body))
+ jsonAlert["body"] = new JValue(this.Alert.Body);
+
+ if (!string.IsNullOrEmpty(this.Alert.ActionLocalizedKey))
+ jsonAlert["action-loc-key"] = new JValue(this.Alert.ActionLocalizedKey);
+
+ aps["alert"] = jsonAlert;
+ }
+ }
+
+ if (this.Badge.HasValue)
+ aps["badge"] = new JValue(this.Badge.Value);
+
+ if (!string.IsNullOrEmpty(this.Sound))
+ aps["sound"] = new JValue(this.Sound);
+
+
+ json["aps"] = aps;
+
+ foreach (string key in this.CustomItems.Keys)
+ {
+ if (this.CustomItems[key].Length == 1)
+ json[key] = new JValue(this.CustomItems[key][0]);
+ else if (this.CustomItems[key].Length > 1)
+ json[key] = new JArray(this.CustomItems[key]);
+ }
+
+ string rawString = json.ToString(Newtonsoft.Json.Formatting.None, null);
+
+ StringBuilder encodedString = new StringBuilder();
+ foreach (char c in rawString)
+ {
+ if ((int)c < 32 || (int)c > 127)
+ encodedString.Append("\\u" + String.Format("{0:x4}", Convert.ToUInt32(c)));
+ else
+ encodedString.Append(c);
+ }
+ return rawString;// encodedString.ToString();
+ }
+
+ public override string ToString()
+ {
+ return ToJson();
+ }
+ }
+}
View
46 Application/MoonApns/Program.cs
@@ -0,0 +1,46 @@
+/*Copyright 2011 Arash Norouzi
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+
+
+namespace MoonAPNS
+{
+ internal class Program
+ {
+ private static void Main(string[] args)
+ {
+ // var payload1 = new NotificationPayload("Device token","Message",Badge,"Sound");
+ var payload1 = new NotificationPayload("Device Token", "Message", 1, "default");
+ payload1.AddCustom("RegionID", "IDQ10150");
+
+ var p = new List<NotificationPayload> {payload1};
+
+ var push = new PushNotification(false, "p12 file location","password");
+ var rejected = push.SendToApple(p);
+ foreach (var item in rejected)
+ {
+ Console.WriteLine(item);
+ }
+ Console.ReadLine();
+ }
+
+ }
+}
View
38 Application/MoonApns/Properties/AssemblyInfo.cs
@@ -0,0 +1,38 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Resources;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("MoonAPNS")]
+[assembly: AssemblyDescription("A c# library for sending Apple Push Notifications")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Arash Norouzi")]
+[assembly: AssemblyProduct("MoonAPNS")]
+[assembly: AssemblyCopyright("Copyright © 2011")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("a2ed0a2d-5b9f-466c-9e80-1efc59a4ec99")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: NeutralResourcesLanguageAttribute("en-AU")]
View
409 Application/MoonApns/PushNotification.cs
@@ -0,0 +1,409 @@
+/*Copyright 2011 Arash Norouzi
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading;
+using NLog;
+
+namespace MoonAPNS
+{
+ public class PushNotification
+ {
+ private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+ private TcpClient _apnsClient;
+ private SslStream _apnsStream;
+ private X509Certificate _certificate;
+ private X509CertificateCollection _certificates;
+
+ public string P12File { get; set; }
+ public string P12FilePassword { get; set; }
+
+
+ // Default configurations for APNS
+ private const string ProductionHost = "gateway.push.apple.com";
+ private const string SandboxHost = "gateway.sandbox.push.apple.com";
+ private const int NotificationPort = 2195;
+
+ // Default configurations for Feedback Service
+ private const string ProductionFeedbackHost = "feedback.push.apple.com";
+ private const string SandboxFeedbackHost = "feedback.sandbox.push.apple.com";
+ private const int FeedbackPort = 2196;
+
+
+ private bool _conected = false;
+
+ private readonly string _host;
+ private readonly string _feedbackHost;
+
+ private List<NotificationPayload> _notifications = new List<NotificationPayload>();
+ private List<string> _rejected = new List<string>();
+
+ private Dictionary<int, string> _errorList = new Dictionary<int, string>();
+
+
+ public PushNotification(bool useSandbox, string p12File, string p12FilePassword)
+ {
+ if (useSandbox)
+ {
+ _host = SandboxHost;
+ _feedbackHost = SandboxFeedbackHost;
+ }
+ else
+ {
+ _host = ProductionHost;
+ _feedbackHost = ProductionFeedbackHost;
+ }
+
+ //Load Certificates in to collection.
+ _certificate = string.IsNullOrEmpty(p12FilePassword)? new X509Certificate2(File.ReadAllBytes(p12File)): new X509Certificate2(File.ReadAllBytes(p12File), p12FilePassword);
+ _certificates = new X509CertificateCollection {_certificate};
+
+ // Loading Apple error response list.
+ _errorList.Add(0, "No errors encountered");
+ _errorList.Add(1, "Processing error");
+ _errorList.Add(2, "Missing device token");
+ _errorList.Add(3, "Missing topic");
+ _errorList.Add(4, "Missing payload");
+ _errorList.Add(5, "Invalid token size");
+ _errorList.Add(6, "Invalid topic size");
+ _errorList.Add(7, "Invalid payload size");
+ _errorList.Add(8, "Invalid token");
+ _errorList.Add(255, "None (unknown)");
+ }
+
+ public List<string> SendToApple(List<NotificationPayload> queue)
+ {
+ Logger.Info("Payload queue received.");
+ _notifications = queue;
+ if (queue.Count < 8999)
+ {
+ SendQueueToapple(_notifications);
+ }
+ else
+ {
+ const int pageSize = 8999;
+ int numberOfPages = (queue.Count / pageSize) + (queue.Count % pageSize == 0 ? 0 : 1);
+ int currentPage = 0;
+
+ while(currentPage < numberOfPages)
+ {
+ _notifications = (queue.Skip(currentPage * pageSize).Take(pageSize)).ToList();
+ SendQueueToapple(_notifications);
+ currentPage++;
+ }
+ }
+ //Close the connection
+ Disconnect();
+ return _rejected;
+ }
+
+ private void SendQueueToapple(IEnumerable<NotificationPayload> queue)
+ {
+ int i = 1000;
+ foreach (var item in queue)
+ {
+ if (!_conected)
+ {
+ Connect(_host, NotificationPort, _certificates);
+ var response = new byte[6];
+ _apnsStream.BeginRead(response, 0, 6, ReadResponse, new MyAsyncInfo(response, _apnsStream));
+ }
+ try
+ {
+ if (item.DeviceToken.Length == 64) //check lenght of device token, if its shorter or longer stop generating Payload.
+ {
+ item.PayloadId = i;
+ byte[] payload = GeneratePayload(item);
+ _apnsStream.Write(payload);
+ Logger.Info("Notification successfully sent to APNS server for Device Toekn : " + item.DeviceToken);
+ Thread.Sleep(1000); //Wait to get the response from apple.
+ }
+ else
+ Logger.Error("Invalid device token length, possible simulator entry: " + item.DeviceToken);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("An error occurred on sending payload for device token {0} - {1}", item.DeviceToken, ex.Message);
+ _conected = false;
+ }
+ i++;
+ }
+ }
+
+ private void ReadResponse(IAsyncResult ar)
+ {
+ if (!_conected)
+ return;
+ string payLoadId = "";
+ int payLoadIndex = 0;
+ try
+ {
+ var info = ar.AsyncState as MyAsyncInfo;
+ info.MyStream.ReadTimeout = 100;
+ if (_apnsStream.CanRead)
+ {
+ var command = Convert.ToInt16(info.ByteArray[0]);
+ var status = Convert.ToInt16(info.ByteArray[1]);
+ var ID = new byte[4];
+ Array.Copy(info.ByteArray, 2, ID, 0, 4);
+
+ payLoadId = Encoding.Default.GetString(ID);
+ payLoadIndex = ((int.Parse(payLoadId)) - 1000);
+ Logger.Error("Apple rejected palyload for device token : " + _notifications[payLoadIndex].DeviceToken);
+ Logger.Error("Apple Error code : " + _errorList[status]);
+ Logger.Error("Connection terminated by Apple.");
+ _rejected.Add(_notifications[payLoadIndex].DeviceToken);
+ _conected = false;
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("An error occurred while reading Apple response for token {0} - {1}", _notifications[payLoadIndex].DeviceToken, ex.Message);
+ }
+ }
+
+ private void Connect(string host, int port, X509CertificateCollection certificates)
+ {
+ Logger.Info("Connecting to apple server.");
+ try
+ {
+ _apnsClient = new TcpClient();
+ _apnsClient.Connect(host, port);
+ }
+ catch (SocketException ex)
+ {
+ Logger.Error("An error occurred while connecting to APNS servers - " + ex.Message);
+ }
+
+ var sslOpened = OpenSslStream(host, certificates);
+
+ if (sslOpened)
+ {
+ _conected = true;
+ Logger.Info("Conected.");
+ }
+
+ }
+
+ private void Disconnect()
+ {
+ try
+ {
+ Thread.Sleep(500);
+ _apnsClient.Close();
+ _apnsStream.Close();
+ _apnsStream.Dispose();
+ _apnsStream = null;
+ _conected = false;
+ Logger.Info("Disconnected.");
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("An error occurred while disconnecting. - " + ex.Message);
+ }
+ }
+
+ private bool OpenSslStream(string host, X509CertificateCollection certificates)
+ {
+ Logger.Info("Creating SSL connection.");
+ _apnsStream = new SslStream(_apnsClient.GetStream(), false, validateServerCertificate, SelectLocalCertificate);
+
+ try
+ {
+ _apnsStream.AuthenticateAsClient(host, certificates, System.Security.Authentication.SslProtocols.Ssl3, false);
+ }
+ catch (System.Security.Authentication.AuthenticationException ex)
+ {
+ Logger.Error(ex.Message);
+ return false;
+ }
+
+ if (!_apnsStream.IsMutuallyAuthenticated)
+ {
+ Logger.Error("SSL Stream Failed to Authenticate");
+ return false;
+ }
+
+ if (!_apnsStream.CanWrite)
+ {
+ Logger.Error("SSL Stream is not Writable");
+ return false;
+ }
+ return true;
+ }
+
+ private bool validateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
+ {
+ return true; // Dont care about server's cert
+ }
+
+ private X509Certificate SelectLocalCertificate(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
+ {
+ return _certificate;
+ }
+
+ private static byte[] GeneratePayload(NotificationPayload payload)
+ {
+ try
+ {
+ //convert Devide token to HEX value.
+ byte[] deviceToken = new byte[payload.DeviceToken.Length / 2];
+ for (int i = 0; i < deviceToken.Length; i++)
+ deviceToken[i] = byte.Parse(payload.DeviceToken.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber);
+
+ var memoryStream = new MemoryStream();
+
+ // Command
+ memoryStream.WriteByte(1); // Changed command Type
+
+ //Adding ID to Payload
+ memoryStream.Write(Encoding.ASCII.GetBytes(payload.PayloadId.ToString()), 0, payload.PayloadId.ToString().Length);
+
+ //Adding ExpiryDate to Payload
+ int epoch = (int) (DateTime.UtcNow.AddMinutes(300) - new DateTime(1970, 1, 1)).TotalSeconds;
+ byte[] timeStamp = BitConverter.GetBytes(epoch);
+ memoryStream.Write(timeStamp, 0, timeStamp.Length);
+
+ byte[] tokenLength = BitConverter.GetBytes((Int16) 32);
+ Array.Reverse(tokenLength);
+ // device token length
+ memoryStream.Write(tokenLength, 0, 2);
+
+ // Token
+ memoryStream.Write(deviceToken, 0, 32);
+
+ // String length
+ string apnMessage = payload.ToJson();
+ Logger.Info("Payload generated for " + payload.DeviceToken + " : " + apnMessage);
+
+ byte[] apnMessageLength = BitConverter.GetBytes((Int16) apnMessage.Length);
+ Array.Reverse(apnMessageLength);
+
+ // message length
+ memoryStream.Write(apnMessageLength, 0, 2);
+
+ // Write the message
+ memoryStream.Write(Encoding.ASCII.GetBytes(apnMessage), 0, apnMessage.Length);
+ return memoryStream.ToArray();
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("Unable to generate payload - " + ex.Message);
+ return null;
+ }
+ }
+
+ public List<Feedback> GetFeedBack()
+ {
+ try
+ {
+ var feedbacks = new List<Feedback>();
+ Logger.Info("Connecting to feedback service.");
+
+ if (!_conected)
+ Connect(_feedbackHost, FeedbackPort, _certificates);
+
+ if (_conected)
+ {
+ //Set up
+ byte[] buffer = new byte[38];
+ int recd = 0;
+ DateTime minTimestamp = DateTime.Now.AddYears(-1);
+
+ //Get the first feedback
+ recd = _apnsStream.Read(buffer, 0, buffer.Length);
+ Logger.Info("Feedback response received.");
+
+ if (recd == 0)
+ Logger.Info("Feedback response is empty.");
+
+ //Continue while we have results and are not disposing
+ while (recd > 0)
+ {
+ Logger.Info("processing feedback response");
+ var fb = new Feedback();
+
+ //Get our seconds since 1970 ?
+ byte[] bSeconds = new byte[4];
+ byte[] bDeviceToken = new byte[32];
+
+ Array.Copy(buffer, 0, bSeconds, 0, 4);
+
+ //Check endianness
+ if (BitConverter.IsLittleEndian)
+ Array.Reverse(bSeconds);
+
+ int tSeconds = BitConverter.ToInt32(bSeconds, 0);
+
+ //Add seconds since 1970 to that date, in UTC and then get it locally
+ fb.Timestamp = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(tSeconds).ToLocalTime();
+
+
+ //Now copy out the device token
+ Array.Copy(buffer, 6, bDeviceToken, 0, 32);
+
+ fb.DeviceToken = BitConverter.ToString(bDeviceToken).Replace("-", "").ToLower().Trim();
+
+ //Make sure we have a good feedback tuple
+ if (fb.DeviceToken.Length == 64 && fb.Timestamp > minTimestamp)
+ {
+ //Raise event
+ //this.Feedback(this, fb);
+ feedbacks.Add(fb);
+ }
+
+ //Clear our array to reuse it
+ Array.Clear(buffer, 0, buffer.Length);
+
+ //Read the next feedback
+ recd = _apnsStream.Read(buffer, 0, buffer.Length);
+ }
+ //clode the connection here !
+ Disconnect();
+ if (feedbacks.Count > 0)
+ Logger.Info("Total {0} feedbacks received.", feedbacks.Count);
+ return feedbacks;
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("Error occurred on receiving feed back. - " + ex.Message);
+ return null;
+ }
+ return null;
+ }
+ }
+
+ public class MyAsyncInfo
+ {
+ public Byte[] ByteArray { get; set; }
+ public SslStream MyStream { get; set; }
+
+ public MyAsyncInfo(Byte[] array, SslStream stream)
+ {
+ ByteArray = array;
+ MyStream = stream;
+ }
+ }
+}
+
+
View
5 Application/MoonApns/packages.config
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="NLog" version="2.0.0.2000" />
+ <package id="Newtonsoft.Json" version="4.0.2" />
+</packages>
View
2  MoonAPNS.sln
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoonAPNS", "MoonAPNS.csproj", "{A91E03EF-6532-4896-89C4-9FCEC1D8D4DB}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoonAPNS", "Application\MoonApns\MoonAPNS.csproj", "{A91E03EF-6532-4896-89C4-9FCEC1D8D4DB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
View
BIN  MoonAPNS.suo
Binary file not shown
View
4 README
@@ -0,0 +1,4 @@
+*Moon Apple Push Notification Service Library*
+A free open source C# library for sending Apple Push Notifications in any .NET application.
+
+Read more: http://arashnorouzi.wordpress.com/
Please sign in to comment.
Something went wrong with that request. Please try again.