Я также работал над нашим API push-уведомлений, который отправляет уведомления в Google Firebase. и APN Apple в зависимости от типа устройства, которое использует пользователь. Мне пришлось переписать код push-уведомлений Apple, поскольку наш код использовал конечную точку, которую предполагалось отключить в 2021 году, но каким-то образом ей удалось продолжать работать в рабочей среде.
Я написал класс ниже для отправки push-уведомлений в точки доступа Apple. Разработка не финальная, так как остались задачи типа хранения токена и запроса его каждые 20-60 минут. Мое решение во многом основано на различных ответах в более старой теме переполнения стека. Я перенес его в .NET 8.0, почистил и применил наши рекомендации по кодированию.
Код: Выделить всё
namespace MyNamespace
{
public class NewApplePushNotificationService : IPushNotificationService
{
private readonly string _url;
private readonly int _port;
private readonly string _certificatePath;
private readonly string _keyId;
private readonly string _bundleId;
private readonly string _teamId;
public NewApplePushNotificationService(bool isProduction, string certificatePath, string keyId, string bundleId, string teamId)
{
_url = isProduction ? "api.push.apple.com" : "api.sandbox.push.apple.com";
_port = 443; // 2197 is also a valid port according to Apple
_certificatePath = certificatePath;
_keyId = keyId;
_bundleId = bundleId;
_teamId = teamId;
}
public async Task Send(string title, string? subtitle, string message, string deviceToken)
{
var notification = new ApplePushNotification()
{
Aps = new ApplePushService()
{
Alert = new Alert(title, subtitle ?? string.Empty, message)
}
};
try
{
var json = JsonConvert.SerializeObject(notification);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
Debug.WriteLine($"{this}.Send > {nameof(json)} = {json}");
Debug.WriteLine($"{this}.Send > {nameof(ServicePointManager.SecurityProtocol)} = {ServicePointManager.SecurityProtocol}");
// Get absolute path of the private certificate.
var directory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (!Directory.Exists(directory))
{
Debug.WriteLine($"{this}.Send > Directory \"{directory}\" does not exist!");
return;
}
var certificatePath = $"{directory}\\{_certificatePath}";
if (!File.Exists(certificatePath))
{
Debug.WriteLine($"{this}.Send > File \"{certificatePath}\" does not exist!");
return;
}
// Get the private key.
ECDsa privateKey;
using (var reader = File.OpenText(certificatePath))
{
var ecPrivateKeyParameters = (ECPrivateKeyParameters)new Org.BouncyCastle.OpenSsl.PemReader(reader).ReadObject();
var q = ecPrivateKeyParameters.Parameters.G.Multiply(ecPrivateKeyParameters.D).Normalize();
var qx = q.AffineXCoord.GetEncoded();
var qy = q.AffineYCoord.GetEncoded();
var d = ecPrivateKeyParameters.D.ToByteArrayUnsigned();
var msEcp = new ECParameters { Curve = ECCurve.NamedCurves.nistP256, Q = { X = qx, Y = qy }, D = d };
privateKey = ECDsa.Create(msEcp);
}
// Create the authorization token.
var securityKey = new ECDsaSecurityKey(privateKey)
{
KeyId = _keyId
};
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.EcdsaSha256);
var descriptor = new SecurityTokenDescriptor
{
IssuedAt = DateTime.Now,
// NotBefore = DateTime.UtcNow.AddSeconds(-5),
Issuer = _teamId,
SigningCredentials = credentials
};
var handler = new JwtSecurityTokenHandler();
var encodedToken = handler.CreateEncodedJwt(descriptor);
if (string.IsNullOrEmpty(encodedToken))
{
Debug.WriteLine($"{this}.Send > The creation of the authorization token \"{encodedToken}\" failed!");
return;
}
Debug.WriteLine($"{this}.Send > {nameof(encodedToken)} = {encodedToken}");
// Send notification to Apple.
var httpClient = new HttpClient
{
DefaultRequestVersion = HttpVersion.Version20,
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower
};
var apnsId = Guid.NewGuid().ToString("D");
Debug.WriteLine($"{this}.Send > {nameof(apnsId)} = {apnsId}");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", encodedToken);
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("apns-topic", _bundleId);
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("apns-push-type", "alert");
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("apns-expiration", Convert.ToString(0));
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("apns-priority", Convert.ToString(10));
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("apns-id", apnsId);
// httpClient.DefaultRequestHeaders.TryAddWithoutValidation("apns-collapse-id", "test");
var url = $"https://{_url}:{_port}/3/device/{deviceToken}";
Debug.WriteLine($"{this}.Send > {nameof(url)} = {url}");
var response = await httpClient.PostAsync(new Uri(url), new StringContent(json, Encoding.UTF8, "application/json"));
if (response == null)
{
Debug.WriteLine($"{this}.Send > Invalid response object.");
return;
}
Debug.WriteLine($"{this}.Send > {nameof(response)} = {response} (Code {response.StatusCode})");
Debug.WriteLine($"{this}.Send > {nameof(response.IsSuccessStatusCode)} = {response.IsSuccessStatusCode}");
if (response.IsSuccessStatusCode)
{
try
{
var responseContent = await response.Content.ReadAsStringAsync();
Debug.WriteLine($"{this}.Send > {nameof(responseContent)} = {responseContent}");
}
catch (Exception exception)
{
Debug.WriteLine($"{this}.Send > Reading content failed. {nameof(exception)} = {exception}");
}
}
}
catch (Exception exception)
{
Debug.WriteLine(exception);
}
}
}
}
Но я не получаю push-уведомления на свой телефон. Опять же, если я отправлю его с веб-сайта, он сработает. Использование одной и той же информации (например, токена устройства, полезной нагрузки и т. д.).
Код: Выделить всё
MyNamespace.NewApplePushNotificationService.Send > json = {"Aps":{"Alert":{"Title":"My title","Subtitle":"My subtitle","Body":"My body"}}}
MyNamespace.NewApplePushNotificationService.Send > SecurityProtocol = Tls12
MyNamespace.NewApplePushNotificationService.Send > encodedToken = REDACTED
MyNamespace.NewApplePushNotificationService.Send > apnsId = 4fbf3ef3-6120-4f34-a3ea-0cd648e2cc69
MyNamespace.NewApplePushNotificationService.Send > url = https://api.sandbox.push.apple.com:443/3/device/REDACTED
MyNamespace.NewApplePushNotificationService.Send > response = StatusCode: 200, ReasonPhrase: 'OK', Version: 2.0, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
apns-id: 4fbf3ef3-6120-4f34-a3ea-0cd648e2cc69
apns-unique-id: 92f6386c-e17a-1add-a09e-aeae5d047ade
} (Code OK)
MyNamespace.NewApplePushNotificationService.Send > IsSuccessStatusCode = True
MyNamespace.NewApplePushNotificationService.Send > responseContent =
Если кто-то заметит ошибку или догадается, что я мог сделать не так, дайте мне знать. Тот факт, что я могу получать и обрабатывать push-уведомления на своем телефоне, когда они отправляются с веб-сайта Apple, означает, что телефон хорошо их обрабатывает (разрешения, события и т. д.). Тот факт, что Apple подтверждает мой вызов push-уведомления из API с помощью 200, означает, что Apple говорит, что все в порядке. Но что-то явно не так, и я в замешательстве.
Подробнее здесь: https://stackoverflow.com/questions/790 ... h-notifica