1- using CorePush . Interfaces ;
2- using CorePush . Utils ;
3- using System ;
1+ using System ;
42using System . Collections . Concurrent ;
53using System . Collections . Generic ;
64using System . Linq ;
119using System . Threading ;
1210using System . Threading . Tasks ;
1311
12+ using CorePush . Interfaces ;
13+ using CorePush . Utils ;
14+ using CorePush . Serialization ;
15+
1416namespace CorePush . Apple
1517{
1618 /// <summary>
1719 /// HTTP2 Apple Push Notification sender
1820 /// </summary>
1921 public class ApnSender : IApnSender
2022 {
21- private static readonly ConcurrentDictionary < string , Tuple < string , DateTime > > tokens = new ConcurrentDictionary < string , Tuple < string , DateTime > > ( ) ;
22- private static readonly Dictionary < ApnServerType , string > servers = new Dictionary < ApnServerType , string >
23+ private static readonly ConcurrentDictionary < string , Tuple < string , DateTime > > tokens = new ( ) ;
24+ private static readonly Dictionary < ApnServerType , string > servers = new ( )
2325 {
2426 { ApnServerType . Development , "https://api.development.push.apple.com:443" } ,
2527 { ApnServerType . Production , "https://api.push.apple.com:443" }
@@ -30,17 +32,24 @@ public class ApnSender : IApnSender
3032
3133 private readonly ApnSettings settings ;
3234 private readonly HttpClient http ;
35+ private readonly IJsonSerializer serializer ;
3336
37+ public ApnSender ( ApnSettings settings , HttpClient http ) : this ( settings , http , new DefaultJsonSerializer ( ) )
38+ {
39+ }
40+
3441 /// <summary>
3542 /// Apple push notification sender constructor
3643 /// </summary>
3744 /// <param name="settings">Apple Push Notification settings</param>
3845 /// <param name="http">HTTP client instance</param>
39- public ApnSender ( ApnSettings settings , HttpClient http )
46+ /// <param name="serializer">JSON serializer</param>
47+ public ApnSender ( ApnSettings settings , HttpClient http , IJsonSerializer serializer )
4048 {
4149 this . settings = settings ?? throw new ArgumentNullException ( nameof ( settings ) ) ;
4250 this . http = http ?? throw new ArgumentNullException ( nameof ( http ) ) ;
43-
51+ this . serializer = serializer ?? throw new ArgumentNullException ( nameof ( serializer ) ) ;
52+
4453 if ( http . BaseAddress == null )
4554 {
4655 http . BaseAddress = new Uri ( servers [ settings . ServerType ] ) ;
@@ -65,39 +74,37 @@ public async Task<ApnsResponse> SendAsync(
6574 CancellationToken cancellationToken = default )
6675 {
6776 var path = $ "/3/device/{ deviceToken } ";
68- var json = JsonHelper . Serialize ( notification ) ;
77+ var json = serializer . Serialize ( notification ) ;
6978
70- using ( var message = new HttpRequestMessage ( HttpMethod . Post , path ) )
71- {
72- message . Version = new Version ( 2 , 0 ) ;
73- message . Content = new StringContent ( json ) ;
79+ using var message = new HttpRequestMessage ( HttpMethod . Post , path ) ;
80+
81+ message . Version = new Version ( 2 , 0 ) ;
82+ message . Content = new StringContent ( json ) ;
7483
75- message . Headers . Authorization = new AuthenticationHeaderValue ( "bearer" , GetJwtToken ( ) ) ;
76- message . Headers . TryAddWithoutValidation ( ":method" , "POST" ) ;
77- message . Headers . TryAddWithoutValidation ( ":path" , path ) ;
78- message . Headers . Add ( "apns-topic" , settings . AppBundleIdentifier ) ;
79- message . Headers . Add ( "apns-expiration" , apnsExpiration . ToString ( ) ) ;
80- message . Headers . Add ( "apns-priority" , apnsPriority . ToString ( ) ) ;
81- message . Headers . Add ( "apns-push-type" , apnPushType . ToString ( ) . ToLowerInvariant ( ) ) ; // required for iOS 13+
82-
83- if ( ! string . IsNullOrWhiteSpace ( apnsId ) )
84- {
85- message . Headers . Add ( apnIdHeader , apnsId ) ;
86- }
87-
88- using ( var response = await http . SendAsync ( message , cancellationToken ) )
89- {
90- var succeed = response . IsSuccessStatusCode ;
91- var content = await response . Content . ReadAsStringAsync ( ) ;
92- var error = JsonHelper . Deserialize < ApnsError > ( content ) ;
93-
94- return new ApnsResponse
95- {
96- IsSuccess = succeed ,
97- Error = error
98- } ;
99- }
84+ message . Headers . Authorization = new AuthenticationHeaderValue ( "bearer" , GetJwtToken ( ) ) ;
85+ message . Headers . TryAddWithoutValidation ( ":method" , "POST" ) ;
86+ message . Headers . TryAddWithoutValidation ( ":path" , path ) ;
87+ message . Headers . Add ( "apns-topic" , settings . AppBundleIdentifier ) ;
88+ message . Headers . Add ( "apns-expiration" , apnsExpiration . ToString ( ) ) ;
89+ message . Headers . Add ( "apns-priority" , apnsPriority . ToString ( ) ) ;
90+ message . Headers . Add ( "apns-push-type" , apnPushType . ToString ( ) . ToLowerInvariant ( ) ) ; // required for iOS 13+
91+
92+ if ( ! string . IsNullOrWhiteSpace ( apnsId ) )
93+ {
94+ message . Headers . Add ( apnIdHeader , apnsId ) ;
10095 }
96+
97+ using var response = await http . SendAsync ( message , cancellationToken ) ;
98+
99+ var succeed = response . IsSuccessStatusCode ;
100+ var content = await response . Content . ReadAsStringAsync ( cancellationToken ) ;
101+ var error = serializer . Deserialize < ApnsError > ( content ) ;
102+
103+ return new ApnsResponse
104+ {
105+ IsSuccess = succeed ,
106+ Error = error
107+ } ;
101108 }
102109
103110 private string GetJwtToken ( )
@@ -114,18 +121,17 @@ private string GetJwtToken()
114121
115122 private string CreateJwtToken ( )
116123 {
117- var header = JsonHelper . Serialize ( new { alg = "ES256" , kid = CleanP8Key ( settings . P8PrivateKeyId ) } ) ;
118- var payload = JsonHelper . Serialize ( new { iss = settings . TeamId , iat = EpochTime ( ) } ) ;
124+ var header = serializer . Serialize ( new { alg = "ES256" , kid = CleanP8Key ( settings . P8PrivateKeyId ) } ) ;
125+ var payload = serializer . Serialize ( new { iss = settings . TeamId , iat = EpochTime ( ) } ) ;
119126 var headerBase64 = Convert . ToBase64String ( Encoding . UTF8 . GetBytes ( header ) ) ;
120127 var payloadBase64 = Convert . ToBase64String ( Encoding . UTF8 . GetBytes ( payload ) ) ;
121128 var unsignedJwtData = $ "{ headerBase64 } .{ payloadBase64 } ";
122129 var unsignedJwtBytes = Encoding . UTF8 . GetBytes ( unsignedJwtData ) ;
123130
124- using ( var dsa = AppleCryptoHelper . GetEllipticCurveAlgorithm ( CleanP8Key ( settings . P8PrivateKey ) ) )
125- {
126- var signature = dsa . SignData ( unsignedJwtBytes , 0 , unsignedJwtBytes . Length , HashAlgorithmName . SHA256 ) ;
127- return $ "{ unsignedJwtData } .{ Convert . ToBase64String ( signature ) } ";
128- }
131+ using var dsa = AppleCryptoHelper . GetEllipticCurveAlgorithm ( CleanP8Key ( settings . P8PrivateKey ) ) ;
132+
133+ var signature = dsa . SignData ( unsignedJwtBytes , 0 , unsignedJwtBytes . Length , HashAlgorithmName . SHA256 ) ;
134+ return $ "{ unsignedJwtData } .{ Convert . ToBase64String ( signature ) } ";
129135 }
130136
131137 private static int EpochTime ( )
@@ -149,7 +155,7 @@ private static string CleanP8Key(string p8Key)
149155 lines . RemoveAt ( 0 ) ;
150156 }
151157
152- if ( 0 != lines . Count && lines [ lines . Count - 1 ] . StartsWith ( "-----END PRIVATE KEY-----" ) )
158+ if ( 0 != lines . Count && lines [ ^ 1 ] . StartsWith ( "-----END PRIVATE KEY-----" ) )
153159 {
154160 lines . RemoveAt ( lines . Count - 1 ) ;
155161 }
0 commit comments