Skip to content

Commit d8bc067

Browse files
committed
Manually merged PR. Closes #599
Thanks for this, I tweaked it a bit to fit my code style. Can you please verify this works properly for you?
1 parent 8267c1c commit d8bc067

File tree

5 files changed

+148
-41
lines changed

5 files changed

+148
-41
lines changed

PushSharp.Tests/WnsRealTests.cs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public void WNS_Send_Single ()
3838
<toast>
3939
<visual>
4040
<binding template=""ToastText01"">
41-
<text id=""1"">bodyText</text>
41+
<text id=""1"">WNS_Send_Single</text>
4242
</binding>
4343
</visual>
4444
</toast>
@@ -51,6 +51,51 @@ public void WNS_Send_Single ()
5151
Assert.AreEqual (attempted, succeeded);
5252
Assert.AreEqual (0, failed);
5353
}
54+
55+
[Test]
56+
public void WNS_Send_Mutiple ()
57+
{
58+
var succeeded = 0;
59+
var failed = 0;
60+
var attempted = 0;
61+
62+
var config = new WnsConfiguration (Settings.Instance.WnsPackageName,
63+
Settings.Instance.WnsPackageSid,
64+
Settings.Instance.WnsClientSecret);
65+
66+
var broker = new WnsServiceBroker (config);
67+
broker.OnNotificationFailed += (notification, exception) => {
68+
failed++;
69+
};
70+
broker.OnNotificationSucceeded += (notification) => {
71+
succeeded++;
72+
};
73+
74+
broker.Start ();
75+
76+
foreach (var uri in Settings.Instance.WnsChannelUris) {
77+
for (var i = 1; i <= 3; i++) {
78+
attempted++;
79+
broker.QueueNotification (new WnsToastNotification {
80+
ChannelUri = uri,
81+
Payload = XElement.Parse(@"
82+
<toast>
83+
<visual>
84+
<binding template=""ToastText01"">
85+
<text id=""1"">WNS_Send_Multiple " + i.ToString() + @"</text>
86+
</binding>
87+
</visual>
88+
</toast>
89+
")
90+
});
91+
}
92+
}
93+
94+
broker.Stop ();
95+
96+
Assert.AreEqual (attempted, succeeded);
97+
Assert.AreEqual (0, failed);
98+
}
5499
}
55100
}
56101

PushSharp.Windows/Exceptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ public WnsNotificationException (WnsNotificationStatus status) : base (status.Er
1212

1313
public new WnsNotification Notification { get; set; }
1414
public WnsNotificationStatus Status { get; private set; }
15+
16+
public override string ToString ()
17+
{
18+
return base.ToString() + " Status = " + Status.HttpStatus;
19+
}
1520
}
1621
}
1722

PushSharp.Windows/PushSharp.Windows.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<Compile Include="WnsNotificationStatus.cs" />
4444
<Compile Include="WnsConnection.cs" />
4545
<Compile Include="Exceptions.cs" />
46+
<Compile Include="WnsTokenAccessManager.cs" />
4647
</ItemGroup>
4748
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
4849
<ItemGroup>

PushSharp.Windows/WnsConnection.cs

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,19 @@ namespace PushSharp.Windows
1515
{
1616
public class WnsServiceConnectionFactory : IServiceConnectionFactory<WnsNotification>
1717
{
18+
WnsAccessTokenManager wnsAccessTokenManager;
19+
1820
public WnsServiceConnectionFactory (WnsConfiguration configuration)
1921
{
22+
wnsAccessTokenManager = new WnsAccessTokenManager (configuration);
2023
Configuration = configuration;
2124
}
2225

2326
public WnsConfiguration Configuration { get; private set; }
2427

2528
public IServiceConnection<WnsNotification> Create()
2629
{
27-
return new WnsServiceConnection (Configuration);
30+
return new WnsServiceConnection (Configuration, wnsAccessTokenManager);
2831
}
2932
}
3033

@@ -39,32 +42,30 @@ public class WnsServiceConnection : IServiceConnection<WnsNotification>
3942
{
4043
HttpClient http;
4144

42-
public WnsServiceConnection (WnsConfiguration configuration)
45+
public WnsServiceConnection (WnsConfiguration configuration, WnsAccessTokenManager accessTokenManager)
4346
{
47+
AccessTokenManager = accessTokenManager;
4448
Configuration = configuration;
4549

4650
//TODO: Microsoft recommends we disable expect-100 to improve latency
4751
// Not sure how to do this in httpclient
4852
http = new HttpClient ();
4953
}
5054

55+
public WnsAccessTokenManager AccessTokenManager { get; private set; }
5156
public WnsConfiguration Configuration { get; private set; }
52-
public string AccessToken { get; private set; }
53-
public string TokenType { get; private set; }
5457

5558
public async Task Send (WnsNotification notification)
5659
{
57-
//See if we need an access token
58-
if (string.IsNullOrEmpty(AccessToken))
59-
await RenewAccessToken();
60-
60+
// Get or renew our access token
61+
var accessToken = await AccessTokenManager.GetAccessToken ();
62+
6163
//https://cloud.notify.windows.com/?token=.....
6264
//Authorization: Bearer {AccessToken}
6365
//
6466

6567
http.DefaultRequestHeaders.TryAddWithoutValidation ("X-WNS-Type", string.Format ("wns/{0}", notification.Type.ToString ().ToLower ()));
66-
//http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue ("Bearer", AccessToken);
67-
http.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "Bearer " + AccessToken);
68+
http.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "Bearer " + accessToken);
6869

6970
if (notification.RequestForStatus.HasValue)
7071
http.DefaultRequestHeaders.TryAddWithoutValidation ("X-WNS-RequestForStatus", notification.RequestForStatus.Value.ToString().ToLower());
@@ -131,6 +132,12 @@ public async Task Send (WnsNotification notification)
131132
return;
132133
}
133134

135+
//401
136+
if (status.HttpStatus == HttpStatusCode.Unauthorized) {
137+
AccessTokenManager.InvalidateAccessToken (accessToken);
138+
throw new RetryAfterException ("Access token expired", DateTime.UtcNow.AddSeconds (5));
139+
}
140+
134141
//404 or 410
135142
if (status.HttpStatus == HttpStatusCode.NotFound || status.HttpStatus == HttpStatusCode.Gone) {
136143
throw new DeviceSubscriptonExpiredException {
@@ -139,40 +146,11 @@ public async Task Send (WnsNotification notification)
139146
};
140147
}
141148

149+
142150
// Any other error
143151
throw new WnsNotificationException (status);
144152
}
145153

146-
async Task RenewAccessToken()
147-
{
148-
var p = new Dictionary<string, string> {
149-
{ "grant_type", "client_credentials" },
150-
{ "client_id", Configuration.PackageSecurityIdentifier },
151-
{ "client_secret", Configuration.ClientSecret },
152-
{ "scope", "notify.windows.com" }
153-
};
154-
155-
var result = await http.PostAsync ("https://login.live.com/accesstoken.srf", new FormUrlEncodedContent (p));
156-
157-
var data = await result.Content.ReadAsStringAsync ();
158-
159-
var json = new JObject();
160-
161-
try { json = JObject.Parse (data); }
162-
catch { }
163-
164-
var accessToken = json.Value<string>("access_token");
165-
var tokenType = json.Value<string>("token_type");
166-
167-
if (!string.IsNullOrEmpty(accessToken) && !string.IsNullOrEmpty(tokenType)) {
168-
AccessToken = accessToken;
169-
TokenType = tokenType;
170-
} else {
171-
throw new UnauthorizedAccessException("Could not retrieve access token for the supplied Package Security Identifier (SID) and client secret");
172-
}
173-
}
174-
175-
176154
WnsNotificationStatus ParseStatus(HttpResponseMessage resp, WnsNotification notification)
177155
{
178156
var result = new WnsNotificationStatus();
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using System.Net.Http;
4+
using PushSharp.Core;
5+
using System.Collections.Generic;
6+
using Newtonsoft.Json.Linq;
7+
8+
namespace PushSharp.Windows
9+
{
10+
public class WnsAccessTokenManager
11+
{
12+
Task renewAccessTokenTask = null;
13+
string accessToken = null;
14+
HttpClient http;
15+
16+
public WnsAccessTokenManager (WnsConfiguration configuration)
17+
{
18+
http = new HttpClient ();
19+
Configuration = configuration;
20+
}
21+
22+
public WnsConfiguration Configuration { get; private set; }
23+
24+
public async Task<string> GetAccessToken ()
25+
{
26+
if (accessToken == null) {
27+
if (renewAccessTokenTask == null) {
28+
Log.Info ("Renewing Access Token");
29+
renewAccessTokenTask = RenewAccessToken ();
30+
await renewAccessTokenTask;
31+
} else {
32+
Log.Info ("Waiting for access token");
33+
await renewAccessTokenTask;
34+
}
35+
}
36+
37+
return accessToken;
38+
}
39+
40+
public void InvalidateAccessToken (string currentAccessToken)
41+
{
42+
if (accessToken == currentAccessToken)
43+
accessToken = null;
44+
}
45+
46+
async Task RenewAccessToken ()
47+
{
48+
var p = new Dictionary<string, string> {
49+
{ "grant_type", "client_credentials" },
50+
{ "client_id", Configuration.PackageSecurityIdentifier },
51+
{ "client_secret", Configuration.ClientSecret },
52+
{ "scope", "notify.windows.com" }
53+
};
54+
55+
var result = await http.PostAsync ("https://login.live.com/accesstoken.srf", new FormUrlEncodedContent (p));
56+
57+
var data = await result.Content.ReadAsStringAsync ();
58+
59+
var token = string.Empty;
60+
var tokenType = string.Empty;
61+
62+
try {
63+
var json = JObject.Parse (data);
64+
token = json.Value<string> ("access_token");
65+
tokenType = json.Value<string> ("token_type");
66+
} catch {
67+
}
68+
69+
if (!string.IsNullOrEmpty (token) && !string.IsNullOrEmpty (tokenType)) {
70+
accessToken = token;
71+
} else {
72+
accessToken = null;
73+
throw new UnauthorizedAccessException ("Could not retrieve access token for the supplied Package Security Identifier (SID) and client secret");
74+
}
75+
}
76+
}
77+
}
78+

0 commit comments

Comments
 (0)