/// <summary> /// Send a alert notification to all iPhone devices subscribed to a given subscription. /// </summary> /// <param name="subId">Id of the subscription to send the message to.</param> /// <param name="message">The message text.</param> /// <param name="count">Number to show on the badge.</param> /// <param name="sound">Sound file for the iPhone notification.</param> /// <returns>Returns "success" if successful otherwise an error.</returns> public string SendIphoneNotification(string subId, string message, string count, string sound) { // Make sure user is authenticated. if (!AuthManager.AuthenticateUser()) { AuthManager.ConstructAuthResponse(); return null; } // Make sure subscription name is created. bool subExists = this.subscriptionInfoMgr.IsSubscriptionRegistered(subId); if (!subExists) { throw new WebFaultException<string>(Utils.GetErrorString(PushMessageError.ErrorSubscriptionNameNotFound), System.Net.HttpStatusCode.BadRequest); } // Make sure that the count is an int. int countVal; bool success = int.TryParse(count, out countVal); if (!success) { throw new WebFaultException<string>(Utils.GetErrorString(PushMessageError.ErrorIllegalCount), System.Net.HttpStatusCode.BadRequest); } try { iPhoneMessage iPhoneMsg = new iPhoneMessage(subId, message, count, sound); this.msgQueue.Enque(iPhoneMsg); } catch (Exception e) { Trace.TraceError(string.Format(CultureInfo.InvariantCulture, "Internal Error: SendiOSAlert subscription: {0} title: {1}, Error: {2}", subId, message, e.Message)); throw new WebFaultException<string>(e.Message, System.Net.HttpStatusCode.InternalServerError); } return "success"; }
/// <summary> /// Enque message for a particular device. For iPhone, we have to pack each message separately as the device ID is put in the message /// </summary> /// <param name="device"></param> /// <param name="msg"></param> private void EnqueiOSMessage(DeviceDataModel device, iPhoneMessage msg) { byte[] apnsNotificationBytes; int numberOfRetries = 0; if (device.DeviceType == "iOS") { if (!CheckDeviceId(device.DeviceId)) { return; } // compose the message apnsNotificationBytes = this.ComposeAPNSMessage(device.DeviceId, msg as iPhoneMessage); while (numberOfRetries < this.SendRetries) { try { // and send the notification this.SendiPhoneDeviceNotification(apnsNotificationBytes); return; } catch (DeviceIdFormatException e) { // device ID was wrong. Send an error to listener if (this.DeviceIdFormatError != null) { this.DeviceIdFormatError(this, new NotificationEventArgs(e)); } } catch (NotificationFormatException e) { // notification format was wrong. send error to listner if (this.NotificationFormatError != null) { this.NotificationFormatError(this, new NotificationEventArgs(e)); } } catch (ObjectDisposedException e) { if (this.NotificationFailed != null) { this.NotificationFailed(this, new NotificationEventArgs(e)); } this.DisconnectTCPClient(); } catch (System.IO.IOException e) { if (this.NotificationFailed != null) { this.NotificationFailed(this, new NotificationEventArgs(e)); } this.DisconnectTCPClient(); } numberOfRetries++; } } }
// uses .NET serializer to serialize the message. // here is how it should look like {"aps": {"badge":3, "alert":"This is my alert", "sound":"default"}} private static string ToJson(iPhoneMessage message) { JavaScriptSerializer js = new JavaScriptSerializer(); AppleNotification an = new AppleNotification(); if (!string.IsNullOrEmpty(message.Alert)) { an.Aps.Alert = message.Alert; } else { an.Aps.Alert = string.Empty; } if (!string.IsNullOrEmpty(message.Badge)) { an.Aps.Badge = int.Parse(message.Badge, CultureInfo.InvariantCulture); } if (!string.IsNullOrEmpty(message.Sound)) { an.Aps.Sound = message.Sound; } else { an.Aps.Sound = string.Empty; } string str = js.Serialize(an); return str; }
/// <summary> /// Compose the APNS message per format . Look up Apple's docs. This packs the message in a binary format. /// </summary> /// <param name="deviceID"></param> /// <param name="message"></param> /// <returns></returns> private byte[] ComposeAPNSMessage(string deviceID, iPhoneMessage message) { // get byte size of deviceID byte[] deviceIDBytes = new byte[deviceID.Length / 2]; // get deviceID bytes for (int i = 0; i < deviceIDBytes.Length; i++) { deviceIDBytes[i] = byte.Parse(deviceID.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber, CultureInfo.InvariantCulture); } if (deviceIDBytes.Length != DeviceTokenBinarySize) { throw new DeviceIdFormatException(deviceID); } // size is always 32 byte[] deviceIDSize = new byte[2] { 0, 32 }; string messageInJson = ToJson(message).Replace("Aps", "aps").Replace("Alert", "alert").Replace("Badge", "badge").Replace("Sound", "sound"); byte[] messageBytes = Encoding.UTF8.GetBytes(messageInJson); // if the message is longer, trim the alert if (messageBytes.Length > MaxPayloadSize) { int newSize = message.Alert.Length - (messageBytes.Length - MaxPayloadSize); if (newSize > 0) { message.Alert = message.Alert.Substring(0, newSize); messageBytes = Encoding.UTF8.GetBytes(message.ToString()); } } // convert the message to binary byte[] messageBytesSize = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Convert.ToInt16(messageBytes.Length))); // find the titak buffer size int bufferSize = sizeof(byte) + deviceIDSize.Length + deviceIDBytes.Length + messageBytesSize.Length + messageBytes.Length; byte[] apnsMessage = new byte[bufferSize]; // pack the message apnsMessage[0] = 0x00; Buffer.BlockCopy(deviceIDSize, 0, apnsMessage, sizeof(byte), deviceIDSize.Length); Buffer.BlockCopy(deviceIDBytes, 0, apnsMessage, sizeof(byte) + deviceIDSize.Length, deviceIDBytes.Length); Buffer.BlockCopy(messageBytesSize, 0, apnsMessage, sizeof(byte) + deviceIDSize.Length + deviceIDBytes.Length, messageBytesSize.Length); Buffer.BlockCopy(messageBytes, 0, apnsMessage, sizeof(byte) + deviceIDSize.Length + deviceIDBytes.Length + messageBytesSize.Length, messageBytes.Length); return apnsMessage; }
/// <summary> /// Initializes a new instance of the NotificationFormatException class. /// </summary> /// This is needed for ISerializable interface /// <param name="serializationInfo">SerializationInfo provides the class where the class is serialized.</param> /// <param name="streamingContext">Additional StreamingContext class.</param> protected NotificationFormatException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { if (serializationInfo != null) { PushMessageType messageType = (PushMessageType)serializationInfo.GetInt16("MessageType"); string subscriptionName = serializationInfo.GetString("SubscriptionName"); if ((PushMessageType)this.NotificationMessage.MessageType == PushMessageType.Raw) { string rawMessageText = serializationInfo.GetString("rawMessageText"); RawMessage rawMessage = new RawMessage(subscriptionName, rawMessageText); this.NotificationMessage = rawMessage; } else if ((PushMessageType)this.NotificationMessage.MessageType == PushMessageType.Toast) { string tileMessageText = serializationInfo.GetString("toastMessageText"); ToastMessage toastMessage = new ToastMessage(subscriptionName, tileMessageText); this.NotificationMessage = toastMessage; } else if ((PushMessageType)this.NotificationMessage.MessageType == PushMessageType.Tile) { string tileMessageTitle = serializationInfo.GetString("tileMessageTitle"); string tileMessageCount = serializationInfo.GetString("tileMessageCount"); string tileMessageUrl = serializationInfo.GetString("tileMessageUrl"); TileMessage tileMessage = new TileMessage(subscriptionName, tileMessageTitle, tileMessageCount, tileMessageUrl); this.NotificationMessage = tileMessage; } else if ((PushMessageType)this.NotificationMessage.MessageType == PushMessageType.Iphone) { string messageAlert = serializationInfo.GetString("iPhoneMessageAlert"); string messageBadge = serializationInfo.GetString("iPhoneMessageBadge"); string messageSound = serializationInfo.GetString("iPhoneMessageSound"); iPhoneMessage iphoneMessage = new iPhoneMessage(subscriptionName, messageAlert, messageBadge, messageSound); this.NotificationMessage = iphoneMessage; } else if ((PushMessageType)this.NotificationMessage.MessageType == PushMessageType.Common) { string messageTitle = serializationInfo.GetString("commmonMessageTitle"); int messageCount = serializationInfo.GetInt32("commonMessageCount"); string messageImage = serializationInfo.GetString("commonMessageImage"); string messageSound = serializationInfo.GetString("commonMessageSound"); CommonMessage commonMessage = new CommonMessage(subscriptionName, messageTitle, messageCount, messageImage, messageSound); this.NotificationMessage = commonMessage; } } }