using System.Security.Claims;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using static System.Net.Mime.MediaTypeNames;
using Microsoft.Net.Http.Headers;
using Microsoft.Extensions.Configuration;
using NPOI.SS.Formula.Functions;
using System.Net.Http;
using Flurl.Http.Configuration;
using System.Net;
using QuzrtzJob.Factory;
using Org.BouncyCastle.Crypto.Parameters;
using System.IO;
using CurlThin;
using CurlThin.Enums;
using CurlThin.Helpers;
using CurlThin.Native;
using CurlThin.SafeHandles;
namespace OASystem.API.OAMethodLib.APNs
{
public enum NotificationType : int
{
Alert = 0,
Sound = 1,
Badge = 2,
Silent = 3
}
///
/// APNs 生成 JWT token,添加服务的时候,使用单利
///
public class APNsService : IAPNsService
{
static string token = null;
static string baseUrl = null;
private readonly ILogger _logger;
//private static readonly HttpClient _httpClientFactory = new HttpClient { BaseAddress = new Uri("https://api.push.apple.com:443/3/device/") };
private readonly IConfiguration _configuration;
//private readonly IHttpClientFactory _httpClientFactory;
public APNsService(ILogger logger, IConfiguration configuration)
{
this._configuration = configuration;
//this._httpClientFactory = httpClientFactory;
//APNsService.baseUrl = this._configuration["apple:pushNotificationServer"];
//APNsService.baseUrl = this._configuration["apple:pushNotificationServer_Production"];
//
//APNsService.baseUrl = string.Format("https://api.push.apple.com:443/3/device/");
_logger = logger;
}
///
/// 生成 APNs JWT token
///
///
public string GetnerateAPNsJWTToken()
{
return this.GetnerateAPNsJWTToken(APNsService.token);
}
//private static CngKey GetPrivateKey()
//{
// using (var reader = System.IO.File.OpenText("File/AuthKey_RMV7Y4KM9V.p8"))
// {
// var ecPrivateKeyParameters = (ECPrivateKeyParameters)new PemReader(reader).ReadObject();
// var x = ecPrivateKeyParameters.Parameters.G.AffineXCoord.GetEncoded();
// var y = ecPrivateKeyParameters.Parameters.G.AffineYCoord.GetEncoded();
// var d = ecPrivateKeyParameters.D.ToByteArrayUnsigned();
// return EccKey.New(x, y, d);
// }
//}
///
/// 生成 APNs JWT token
///
///
private string GetnerateAPNsJWTToken(string oldToken)
{
var tokenHandler = new JwtSecurityTokenHandler();
var iat = ((DateTime.UtcNow.Ticks - new DateTime(1970, 1, 1).Ticks) / TimeSpan.TicksPerSecond);
// 判断原 token 是否超过 50 分钟,如果未超过,直接返回
if (string.IsNullOrWhiteSpace(oldToken) == false)
{
JwtPayload oldPayload = tokenHandler.ReadJwtToken(oldToken).Payload;
var oldIat = oldPayload.Claims.FirstOrDefault(c => c.Type == "iat");
if (oldIat != null)
{
if (long.TryParse(oldIat.Value, out long oldIatValue) == true)
{
// 两次间隔小于 50 分钟,使用原 token
if ((iat - oldIatValue) < (50 * 60))
{
return oldToken;
}
}
}
}
var kid = _configuration["apple:kid"];
//var securityKey = _configuration["apple:securityKey"].Replace("\n", "");
var securityKey = string.Format("MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQglyUl7hjI75YJUVMbZLN6TpkiFzuTXUN+UIjuJA7+y8ugCgYIKoZIzj0DAQehRANCAAS8GR7lKNst4KENCp45OXCMyiytvzK0qdRBrx0l+bMaHjiU+Upfox82G+Xy4wd8hI+0wMDh341aNelqEdYUUx3O");
var iss = _configuration["apple:iss"];
var claims = new Claim[]
{
new Claim("iss", iss),
new Claim("iat", iat.ToString())
};
string msg = string.Empty;
msg += $"[GetnerateAPNsJWTToken] 0";
try
{
msg += $"[GetnerateAPNsJWTToken] 1";
var eCDsa = ECDsa.Create();
msg += $"[GetnerateAPNsJWTToken] 2";
eCDsa.ImportPkcs8PrivateKey(Convert.FromBase64String(securityKey), out _);
msg += $"[GetnerateAPNsJWTToken] 3";
var key = new ECDsaSecurityKey(eCDsa);
msg += $"[GetnerateAPNsJWTToken] 4";
key.KeyId = kid;
msg += $"[GetnerateAPNsJWTToken] 5";
var signingCredentials = new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256);
msg += $"[GetnerateAPNsJWTToken] 6";
var jwtHeader = new JwtHeader(signingCredentials);
msg += $"[GetnerateAPNsJWTToken] 7";
var jwtPayload = new JwtPayload(claims);
msg += $"[GetnerateAPNsJWTToken] 8";
var jwtSecurityToken = new JwtSecurityToken(jwtHeader, jwtPayload);
msg += $"[GetnerateAPNsJWTToken] 9";
APNsService.token = tokenHandler.WriteToken(jwtSecurityToken);
}
catch (Exception ex)
{
return msg += $"[GetnerateAPNsJWTToken] --> ExMsg:[{ex.Message}]";
}
return APNsService.token;
}
public async Task PushNotification1(string apnsTopic, string deviceToken, NotificationType type, string title, string subtitle, string body, bool isTarget, string viewCode, PageParam_PriceAuditH5 pageParam)
{
var res = new Result() { Code = 0, Msg = "", Data = "" };
//This string is for extracting libcurl and ssl libs to the bin directory.
CurlResources.Init();
var global = CurlNative.Init();
var easy = CurlNative.Easy.Init();
string content = string.Empty;
try
{
var token = GetnerateAPNsJWTToken();
var dataCopier = new DataCallbackCopier();
CurlNative.Easy.SetOpt(easy, CURLoption.URL, $"https://api.push.apple.com:443/3/device/{deviceToken}");
CurlNative.Easy.SetOpt(easy, CURLoption.WRITEFUNCTION, dataCopier.DataHandler);
//This string is needed when you call a https endpoint.
CurlNative.Easy.SetOpt(easy, CURLoption.CAINFO, CurlResources.CaBundlePath);
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, APNsService.baseUrl + deviceToken)
{
Headers =
{
{ HeaderNames.Authorization, "bearer " + token },
{ "apns-topic", apnsTopic },
{ "apns-expiration", "0" }
},
Version = new Version(2, 0)
};
if (isTarget)
{
var notContent = new
{
aps = new
{
alert = new
{
title = title,
subtitle = subtitle,
body = body
}
},
ViewCode = viewCode,
PageParam = pageParam
};
var contentJson = new StringContent(System.Text.Json.JsonSerializer.Serialize(notContent), System.Text.Encoding.UTF8, Application.Json);
CurlNative.Easy.SetOpt(easy, CURLoption.POSTFIELDS, System.Text.Json.JsonSerializer.Serialize(notContent));
}
else
{
var notContent = new
{
aps = new
{
alert = new
{
title = title,
subtitle = subtitle,
body = body
}
}
};
var contentJson = new StringContent(System.Text.Json.JsonSerializer.Serialize(notContent), System.Text.Encoding.UTF8, Application.Json);
CurlNative.Easy.SetOpt(easy, CURLoption.POSTFIELDS, System.Text.Json.JsonSerializer.Serialize(notContent));
}
var headers = CurlNative.Slist.Append(SafeSlistHandle.Null, $"Authorization: Bearer {token}");
CurlNative.Slist.Append(headers, $"apns-topic: {apnsTopic}");
CurlNative.Slist.Append(headers, $"apns-expiration: 0");
CurlNative.Easy.SetOpt(easy, CURLoption.HTTPHEADER, headers.DangerousGetHandle());
//Your set of ciphers, full list is here https://curl.se/docs/ssl-ciphers.html
CurlNative.Easy.SetOpt(easy, CURLoption.SSL_CIPHER_LIST, "ECDHE-RSA-AES256-GCM-SHA384");
res.Msg += $"[PushNotification1] --> 发送请求";
CurlNative.Easy.Perform(easy);
content = Encoding.UTF8.GetString(dataCopier.Stream.ToArray());
}
catch (Exception ex)
{
res.Msg += $"[PushNotification1] ExMsg:{ex.Message}";
if (ex.InnerException != null)
{
res.Msg += $"[PushNotification1] InnerException:{ex.InnerException.Message}";
}
}
finally
{
easy.Dispose();
if (global == CURLcode.OK)
CurlNative.Cleanup();
}
res.Data = content;
return res;
}
///
/// 发送推送通知
///
/// APP Id
/// 设备标识[文件:AuthKey_RMV7Y4KM9V.p8]
/// 通知类型
/// 标题
/// 子标题
/// 通知内容
///
public async Task PushNotification(string apnsTopic, string deviceToken, NotificationType type, string title, string subtitle, string body)
{
Result result = new Result() { Code = -1, Msg = "[PushNotification] Start" };
result.Msg += "\r\n[PushNotification] Start";
_logger.LogInformation($"[PushNotification] Start");
var responseData = FailedAPNsReponseData();
var token = this.GetnerateAPNsJWTToken();
try
{
var _httpClientFactory = new HttpClient { BaseAddress = new Uri("https://api.push.apple.com:443/3/device/") };
result.Msg += $"\r\n[PushNotification] --> [HttpClient] --> Init --> jsonStr:{JsonConvert.SerializeObject(_httpClientFactory)}";
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, APNsService.baseUrl + deviceToken)
{
Headers =
{
{ HeaderNames.Authorization, "bearer " + token },
{ "apns-topic", apnsTopic },
{ "apns-expiration", "0" }
},
Version = new Version(2, 0)
};
result.Msg += $"\r\n[PushNotification] --> [httpRequestMessage] --> Init --> jsonStr:{JsonConvert.SerializeObject(httpRequestMessage)}";
var notContent = new
{
aps = new
{
alert = new
{
title = title,
subtitle = subtitle,
body = body
}
}
};
//var content = new StringContent(JsonSerializerTool.SerializeDefault(notContent), System.Text.Encoding.UTF8, Application.Json);
var content = new StringContent(System.Text.Json.JsonSerializer.Serialize(notContent), System.Text.Encoding.UTF8, Application.Json);
httpRequestMessage.Content = content;
result.Msg += $"\r\n[PushNotification] --> [httpRequestMessage] --> Content --> jsonStr:{JsonConvert.SerializeObject(httpRequestMessage)}";
try
{
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;
//HttpClientHandler handler = new HttpClientHandler()
//{
// Proxy = new WebProxy("https://api.push.apple.com:443/3/device/"),
// SslProtocols = System.Security.Authentication.SslProtocols.Tls12, // Enforce TLS 1.2
// UseProxy = true
//};
//HttpClient client = new HttpClient(handler);
//var httpResponseMessage = await client.SendAsync(httpRequestMessage);
var httpResponseMessage = await _httpClientFactory.SendAsync(httpRequestMessage);
if (httpResponseMessage.IsSuccessStatusCode)
{
responseData.Code = 200;
result.Code = 0;
result.Msg = "";
result.Data = responseData;
result.Msg += $"\r\n[PushNotification] End jsonStr[{JsonConvert.SerializeObject(result)}]";
return result;
}
else
{
responseData.Data = httpResponseMessage.StatusCode;
result.Code = -2;
result.Msg = "";
result.Data = responseData;
result.Msg += $"\r\n[PushNotification] End jsonStr[{JsonConvert.SerializeObject(result)}]";
return result;
}
}
catch (Exception e)
{
responseData.Data = e.Message;
result.Code = -3;
result.Msg = "";
result.Data = responseData;
result.Msg += $"\r\n[PushNotification] --> [httpClientFactory] ExceptionMsg:{e.Message}";
result.Msg += $"\r\n[PushNotification] --> [httpClientFactory] InnerExceptionData:{e.InnerException}";
result.Msg += $"\r\n[PushNotification] --> [httpClientFactory] InnerExceptionMsg:{e.InnerException.Message}";
return result;
}
}
catch (Exception ex)
{
result.Msg += $"\r\n[PushNotification] InnerExceptionData:{JsonConvert.SerializeObject(ex.InnerException)}";
result.Msg += $"\r\n[PushNotification] InnerExceptionMsg:{ex.InnerException.Message}";
}
return result;
}
public APNsReponseData FailedAPNsReponseData()
{
return new APNsReponseData() { Code = 400, Data = "" };
}
}
public class APNsReponseData
{
public int Code { get; set; } = 0;
public object Data { get; set; } = "";
}
public class PageParam_PriceAuditH5
{
public string diid { get; set; }
public string uid { get; set; }
}
}