PageRenderTime 49ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/PushSharp.Windows/WindowsPushChannel.cs

https://github.com/mustafagenc/PushSharp
C# | 255 lines | 179 code | 50 blank | 26 comment | 40 complexity | da732ebae308432cf65f93a94b36a94a MD5 | raw file
Possible License(s): Apache-2.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Net;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using System.Web;
  8. using Newtonsoft.Json.Linq;
  9. using PushSharp.Common;
  10. namespace PushSharp.Windows
  11. {
  12. public class WindowsPushChannel : Common.PushChannelBase
  13. {
  14. public string AccessToken { get; private set; }
  15. public string TokenType { get; private set; }
  16. WindowsPushChannelSettings channelSettings;
  17. public WindowsPushChannel(WindowsPushChannelSettings channelSettings, PushServiceSettings serviceSettings = null) : base(channelSettings, serviceSettings)
  18. {
  19. this.channelSettings = channelSettings;
  20. }
  21. public override PlatformType PlatformType
  22. {
  23. get { return Common.PlatformType.Windows; }
  24. }
  25. void RenewAccessToken()
  26. {
  27. var postData = new StringBuilder();
  28. postData.AppendFormat("{0}={1}&", "grant_type", "client_credentials");
  29. postData.AppendFormat("{0}={1}&", "client_id", HttpUtility.UrlEncode(this.channelSettings.PackageSecurityIdentifier));
  30. postData.AppendFormat("{0}={1}&", "client_secret", HttpUtility.UrlEncode(this.channelSettings.ClientSecret));
  31. postData.AppendFormat("{0}={1}", "scope", "notify.windows.com");
  32. var wc = new WebClient();
  33. wc.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
  34. var response = string.Empty;
  35. try { response = wc.UploadString("https://login.live.com/accesstoken.srf", "POST", postData.ToString()); }
  36. catch (Exception ex) { this.Events.RaiseChannelException(ex, PlatformType.Windows); }
  37. var json = new JObject();
  38. try { json = JObject.Parse(response); }
  39. catch { }
  40. var accessToken = json.Value<string>("access_token");
  41. var tokenType = json.Value<string>("token_type");
  42. if (!string.IsNullOrEmpty(accessToken) && !string.IsNullOrEmpty(tokenType))
  43. {
  44. this.AccessToken = accessToken;
  45. this.TokenType = tokenType;
  46. }
  47. else
  48. {
  49. this.Events.RaiseChannelException(new UnauthorizedAccessException("Could not retrieve access token for the supplied Package Security Identifier (SID) and client secret"), PlatformType.Windows);
  50. }
  51. }
  52. protected override void SendNotification(Common.Notification notification)
  53. {
  54. try { sendNotification(notification); }
  55. catch (Exception ex)
  56. {
  57. this.Events.RaiseChannelException(ex, PlatformType.Windows, notification);
  58. }
  59. }
  60. void sendNotification(Common.Notification notification)
  61. {
  62. //See if we need an access token
  63. if (string.IsNullOrEmpty(AccessToken))
  64. RenewAccessToken();
  65. var winNotification = notification as WindowsNotification;
  66. //https://cloud.notify.windows.com/?token=.....
  67. //Authorization: Bearer {AccessToken}
  68. //
  69. var wnsType = string.Empty;
  70. switch (winNotification.Type)
  71. {
  72. case WindowsNotificationType.Tile:
  73. wnsType = "wns/tile";
  74. break;
  75. case WindowsNotificationType.Badge:
  76. wnsType = "wns/badge";
  77. break;
  78. case WindowsNotificationType.Toast:
  79. wnsType = "wns/toast";
  80. break;
  81. default:
  82. wnsType = "wns/raw";
  83. break;
  84. }
  85. var request = (HttpWebRequest)HttpWebRequest.Create(winNotification.ChannelUri); // "https://notify.windows.com");
  86. request.Method = "POST";
  87. request.Headers.Add("X-WNS-Type", wnsType);
  88. request.Headers.Add("Authorization", string.Format("Bearer {0}", this.AccessToken));
  89. request.ContentType = "text/xml";
  90. if (winNotification.Type == WindowsNotificationType.Tile)
  91. {
  92. var winTileNot = winNotification as WindowsTileNotification;
  93. if (winTileNot != null && winTileNot.CachePolicy.HasValue)
  94. request.Headers.Add("X-WNS-Cache-Policy", winTileNot.CachePolicy == WindowsNotificationCachePolicyType.Cache ? "cache" : "no-cache");
  95. }
  96. else if (winNotification.Type == WindowsNotificationType.Badge)
  97. {
  98. var winTileBadge = winNotification as WindowsBadgeNotification;
  99. if (winTileBadge != null && winTileBadge.CachePolicy.HasValue)
  100. request.Headers.Add("X-WNS-Cache-Policy", winTileBadge.CachePolicy == WindowsNotificationCachePolicyType.Cache ? "cache" : "no-cache");
  101. }
  102. if (winNotification.RequestForStatus.HasValue)
  103. request.Headers.Add("X-WNS-RequestForStatus", winNotification.RequestForStatus.Value.ToString().ToLower());
  104. if (winNotification.Type == WindowsNotificationType.Tile)
  105. {
  106. var winTileNot = winNotification as WindowsTileNotification;
  107. if (winTileNot != null && !string.IsNullOrEmpty(winTileNot.NotificationTag))
  108. request.Headers.Add("X-WNS-Tag", winTileNot.NotificationTag); // TILE only
  109. }
  110. if (winNotification.TimeToLive.HasValue)
  111. request.Headers.Add("X-WNS-TTL", winNotification.TimeToLive.Value.ToString()); //Time to live in seconds
  112. //Microsoft recommends we disable expect-100 to improve latency
  113. request.ServicePoint.Expect100Continue = false;
  114. var payload = winNotification.PayloadToString();
  115. var data = Encoding.Default.GetBytes(payload);
  116. request.ContentLength = data.Length;
  117. using (var rs = request.GetRequestStream())
  118. rs.Write(data, 0, data.Length);
  119. try
  120. {
  121. request.BeginGetResponse(new AsyncCallback(getResponseCallback), new object[] { request, winNotification });
  122. }
  123. catch (WebException wex)
  124. {
  125. //Handle different httpstatuses
  126. var status = ParseStatus(wex.Response as HttpWebResponse, winNotification);
  127. HandleStatus(status);
  128. }
  129. }
  130. void getResponseCallback(IAsyncResult asyncResult)
  131. {
  132. //Good list of statuses:
  133. //http://msdn.microsoft.com/en-us/library/ff941100(v=vs.92).aspx
  134. var objs = (object[])asyncResult.AsyncState;
  135. var wr = (HttpWebRequest)objs[0];
  136. var winNotification = (WindowsNotification)objs[1];
  137. var resp = wr.EndGetResponse(asyncResult) as HttpWebResponse;
  138. var status = ParseStatus(resp, winNotification);
  139. HandleStatus(status);
  140. }
  141. WindowsNotificationStatus ParseStatus(HttpWebResponse resp, WindowsNotification notification)
  142. {
  143. var result = new WindowsNotificationStatus();
  144. result.Notification = notification;
  145. result.HttpStatus = resp.StatusCode;
  146. var wnsDebugTrace = resp.Headers["X-WNS-Debug-Trace"];
  147. var wnsDeviceConnectionStatus = resp.Headers["X-WNS-DeviceConnectionStatus"] ?? "connected";
  148. var wnsErrorDescription = resp.Headers["X-WNS-Error-Description"];
  149. var wnsMsgId = resp.Headers["X-WNS-Msg-ID"];
  150. var wnsNotificationStatus = resp.Headers["X-WNS-NotificationStatus"];
  151. result.DebugTrace = wnsDebugTrace;
  152. result.ErrorDescription = wnsErrorDescription;
  153. result.MessageID = wnsMsgId;
  154. if (wnsNotificationStatus.Equals("received", StringComparison.InvariantCultureIgnoreCase))
  155. result.NotificationStatus = WindowsNotificationSendStatus.Received;
  156. else if (wnsNotificationStatus.Equals("dropped", StringComparison.InvariantCultureIgnoreCase))
  157. result.NotificationStatus = WindowsNotificationSendStatus.Dropped;
  158. else
  159. result.NotificationStatus = WindowsNotificationSendStatus.ChannelThrottled;
  160. if (wnsDeviceConnectionStatus.Equals("connected", StringComparison.InvariantCultureIgnoreCase))
  161. result.DeviceConnectionStatus = WindowsDeviceConnectionStatus.Connected;
  162. else if (wnsDeviceConnectionStatus.Equals("tempdisconnected", StringComparison.InvariantCultureIgnoreCase))
  163. result.DeviceConnectionStatus = WindowsDeviceConnectionStatus.TempDisconnected;
  164. else
  165. result.DeviceConnectionStatus = WindowsDeviceConnectionStatus.Disconnected;
  166. return result;
  167. }
  168. void HandleStatus(WindowsNotificationStatus status)
  169. {
  170. //RESPONSE HEADERS
  171. // X-WNS-Debug-Trace string
  172. // X-WNS-DeviceConnectionStatus connected | disconnected | tempdisconnected (if RequestForStatus was set to true)
  173. // X-WNS-Error-Description string
  174. // X-WNS-Msg-ID string (max 16 char)
  175. // X-WNS-NotificationStatus received | dropped | channelthrottled
  176. //
  177. // 200 OK
  178. // 400 One or more headers were specified incorrectly or conflict with another header.
  179. // 401 The cloud service did not present a valid authentication ticket. The OAuth ticket may be invalid.
  180. // 403 The cloud service is not authorized to send a notification to this URI even though they are authenticated.
  181. // 404 The channel URI is not valid or is not recognized by WNS. - Raise Expiry
  182. // 405 Invalid Method - never will get
  183. // 406 The cloud service exceeded its throttle limit.
  184. // 410 The channel expired. - Raise Expiry
  185. // 413 The notification payload exceeds the 5000 byte size limit.
  186. // 500 An internal failure caused notification delivery to fail.
  187. // 503 The server is currently unavailable.
  188. if (status.HttpStatus == HttpStatusCode.OK
  189. && status.NotificationStatus == WindowsNotificationSendStatus.Received)
  190. {
  191. Events.RaiseNotificationSent(status.Notification);
  192. return;
  193. }
  194. else if (status.HttpStatus == HttpStatusCode.NotFound) //404
  195. {
  196. Events.RaiseDeviceSubscriptionExpired(PlatformType.Windows, status.Notification.ChannelUri, status.Notification);
  197. }
  198. else if (status.HttpStatus == HttpStatusCode.Gone) //410
  199. {
  200. Events.RaiseDeviceSubscriptionExpired(PlatformType.Windows, status.Notification.ChannelUri, status.Notification);
  201. }
  202. Events.RaiseNotificationSendFailure(status.Notification, new WindowsNotificationSendFailureException(status));
  203. }
  204. }
  205. }