Skip to content

Commit 5344d50

Browse files
Update samples, fix some OAuth methods (#344)
* Bump samples to net10 * Fix OAuth commands
1 parent 49a2953 commit 5344d50

File tree

10 files changed

+219
-23
lines changed

10 files changed

+219
-23
lines changed

samples/BSkyOauth/BSkyOAuth.Android/BSkyOAuth.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFramework>net9.0-android</TargetFramework>
3+
<TargetFramework>net10.0-android</TargetFramework>
44
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
55
<OutputType>Exe</OutputType>
66
<Nullable>enable</Nullable>

samples/BSkyOauth/BSkyOAuth.ClientMetadata/BSkyOAuth.ClientMetadata.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net9.0</TargetFramework>
5+
<TargetFramework>net10.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
88
<PublishAOT>true</PublishAOT>

samples/BSkyOauth/BSkyOAuth.MacCatalyst/BSkyOAuth.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFramework>net9.0-maccatalyst</TargetFramework>
3+
<TargetFramework>net10.0-maccatalyst</TargetFramework>
44
<!-- The default runtime is maccatalyst-x64, except in Release config, in which case the default is maccatalyst-x64;maccatalyst-arm64.
55
When specifying both architectures, use the plural <RuntimeIdentifiers> instead of the singular <RuntimeIdentifer>.
66
The App Store will NOT accept apps with ONLY maccatalyst-arm64 indicated;

samples/BSkyOauth/BSkyOAuth.MacCatalyst/LoginViewController.cs

Lines changed: 191 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ public sealed class LoginViewController : UIViewController
2121

2222
private UIButton authButton;
2323

24+
private UIButton refreshTokenButton;
25+
26+
private UIButton getTimelineButton;
27+
28+
private UIButton saveSessionButton;
29+
30+
private UIButton loadSessionButton;
31+
2432
private UITextField handleField;
2533

2634
/// <summary>
@@ -31,34 +39,77 @@ public LoginViewController()
3139
this.oauthManager = new OAuthManager(this, "vip.drasticactions", this.OnSuccess, this.OnError);
3240
var atProtocolBuilder = new ATProtocolBuilder();
3341
this.atProtocol = atProtocolBuilder.Build();
42+
this.atProtocol.SessionUpdated += (sender, args) =>
43+
{
44+
Console.WriteLine($"Session updated: {args.Session.ToString()}");
45+
};
3446

3547
this.View!.BackgroundColor = UIColor.SystemBackground;
48+
49+
this.handleField = new UITextField();
50+
this.handleField.Placeholder = "Handle";
51+
this.handleField.TranslatesAutoresizingMaskIntoConstraints = false;
52+
this.View!.AddSubview(this.handleField);
53+
3654
this.authButton = new UIButton(UIButtonType.System);
3755
this.authButton.SetTitle("Authenticate", UIControlState.Normal);
56+
this.authButton.TranslatesAutoresizingMaskIntoConstraints = false;
3857
this.authButton.TouchUpInside += this.AuthButton_TouchUpInside;
3958
this.View!.AddSubview(this.authButton);
4059

41-
this.handleField = new UITextField();
42-
this.handleField.Placeholder = "Handle";
60+
this.refreshTokenButton = new UIButton(UIButtonType.System);
61+
this.refreshTokenButton.SetTitle("Refresh Token", UIControlState.Normal);
62+
this.refreshTokenButton.TranslatesAutoresizingMaskIntoConstraints = false;
63+
this.refreshTokenButton.Enabled = false;
64+
this.refreshTokenButton.TouchUpInside += this.RefreshTokenButton_TouchUpInside;
65+
this.View!.AddSubview(this.refreshTokenButton);
4366

44-
this.View!.AddSubview(this.handleField);
45-
this.handleField.TranslatesAutoresizingMaskIntoConstraints = false;
67+
this.getTimelineButton = new UIButton(UIButtonType.System);
68+
this.getTimelineButton.SetTitle("Get Timeline", UIControlState.Normal);
69+
this.getTimelineButton.TranslatesAutoresizingMaskIntoConstraints = false;
70+
this.getTimelineButton.Enabled = false;
71+
this.getTimelineButton.TouchUpInside += this.GetTimelineButton_TouchUpInside;
72+
this.View!.AddSubview(this.getTimelineButton);
73+
74+
this.saveSessionButton = new UIButton(UIButtonType.System);
75+
this.saveSessionButton.SetTitle("Save Session", UIControlState.Normal);
76+
this.saveSessionButton.TranslatesAutoresizingMaskIntoConstraints = false;
77+
this.saveSessionButton.Enabled = false;
78+
this.saveSessionButton.TouchUpInside += this.SaveSessionButton_TouchUpInside;
79+
this.View!.AddSubview(this.saveSessionButton);
80+
81+
this.loadSessionButton = new UIButton(UIButtonType.System);
82+
this.loadSessionButton.SetTitle("Load Session", UIControlState.Normal);
83+
this.loadSessionButton.TranslatesAutoresizingMaskIntoConstraints = false;
84+
this.loadSessionButton.TouchUpInside += this.LoadSessionButton_TouchUpInside;
85+
this.View!.AddSubview(this.loadSessionButton);
4686

4787
this.View!.AddConstraints(new[]
4888
{
4989
NSLayoutConstraint.Create(this.handleField, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.View!, NSLayoutAttribute.Top, 1, 100),
5090
NSLayoutConstraint.Create(this.handleField, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, this.View!, NSLayoutAttribute.Leading, 1, 20),
5191
NSLayoutConstraint.Create(this.handleField, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, this.View!, NSLayoutAttribute.Trailing, 1, -20),
5292
NSLayoutConstraint.Create(this.handleField, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1, 40),
53-
});
54-
55-
this.authButton.TranslatesAutoresizingMaskIntoConstraints = false;
56-
this.View!.AddConstraints(new[]
57-
{
5893
NSLayoutConstraint.Create(this.authButton, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.handleField, NSLayoutAttribute.Bottom, 1, 20),
5994
NSLayoutConstraint.Create(this.authButton, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, this.View!, NSLayoutAttribute.Leading, 1, 20),
6095
NSLayoutConstraint.Create(this.authButton, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, this.View!, NSLayoutAttribute.Trailing, 1, -20),
6196
NSLayoutConstraint.Create(this.authButton, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1, 40),
97+
NSLayoutConstraint.Create(this.refreshTokenButton, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.authButton, NSLayoutAttribute.Bottom, 1, 20),
98+
NSLayoutConstraint.Create(this.refreshTokenButton, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, this.View!, NSLayoutAttribute.Leading, 1, 20),
99+
NSLayoutConstraint.Create(this.refreshTokenButton, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, this.View!, NSLayoutAttribute.Trailing, 1, -20),
100+
NSLayoutConstraint.Create(this.refreshTokenButton, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1, 40),
101+
NSLayoutConstraint.Create(this.getTimelineButton, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.refreshTokenButton, NSLayoutAttribute.Bottom, 1, 20),
102+
NSLayoutConstraint.Create(this.getTimelineButton, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, this.View!, NSLayoutAttribute.Leading, 1, 20),
103+
NSLayoutConstraint.Create(this.getTimelineButton, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, this.View!, NSLayoutAttribute.Trailing, 1, -20),
104+
NSLayoutConstraint.Create(this.getTimelineButton, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1, 40),
105+
NSLayoutConstraint.Create(this.saveSessionButton, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.getTimelineButton, NSLayoutAttribute.Bottom, 1, 20),
106+
NSLayoutConstraint.Create(this.saveSessionButton, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, this.View!, NSLayoutAttribute.Leading, 1, 20),
107+
NSLayoutConstraint.Create(this.saveSessionButton, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, this.View!, NSLayoutAttribute.Trailing, 1, -20),
108+
NSLayoutConstraint.Create(this.saveSessionButton, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1, 40),
109+
NSLayoutConstraint.Create(this.loadSessionButton, NSLayoutAttribute.Top, NSLayoutRelation.Equal, this.saveSessionButton, NSLayoutAttribute.Bottom, 1, 20),
110+
NSLayoutConstraint.Create(this.loadSessionButton, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, this.View!, NSLayoutAttribute.Leading, 1, 20),
111+
NSLayoutConstraint.Create(this.loadSessionButton, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, this.View!, NSLayoutAttribute.Trailing, 1, -20),
112+
NSLayoutConstraint.Create(this.loadSessionButton, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1, 40),
62113
});
63114
}
64115

@@ -79,7 +130,7 @@ private async void AuthButton_TouchUpInside(object? sender, EventArgs e)
79130
var (uri, error) = await this.atProtocol.GenerateOAuth2AuthenticationUrlResultAsync(
80131
ClientMetadataUrl,
81132
RedirectUri,
82-
new[] { "atproto" },
133+
new[] { "atproto", "transition:generic" },
83134
atIdentifier!);
84135

85136
if (error != null)
@@ -114,9 +165,11 @@ private async void OnSuccess(NSUrl? callbackUrl)
114165
var (session, error) = await this.atProtocol.AuthenticateWithOAuth2CallbackResultAsync(callbackUrl.ToString());
115166
if (session != null)
116167
{
117-
// We have a session!
118168
this.InvokeOnMainThread(() =>
119169
{
170+
this.refreshTokenButton.Enabled = true;
171+
this.getTimelineButton.Enabled = true;
172+
this.saveSessionButton.Enabled = true;
120173
var alert = UIAlertController.Create("Success", $"Authenticated as {session.Handle}", UIAlertControllerStyle.Alert);
121174
alert.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
122175
this.PresentViewController(alert, true, null);
@@ -144,6 +197,133 @@ private async void OnSuccess(NSUrl? callbackUrl)
144197
}
145198
}
146199

200+
private async void RefreshTokenButton_TouchUpInside(object? sender, EventArgs e)
201+
{
202+
var (refreshedSession, refreshError) = await this.atProtocol.RefreshAuthSessionResultAsync();
203+
if (refreshError != null)
204+
{
205+
this.InvokeOnMainThread(() =>
206+
{
207+
var alert = UIAlertController.Create("Error", $"Token refresh failed: {refreshError}", UIAlertControllerStyle.Alert);
208+
alert.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
209+
this.PresentViewController(alert, true, null);
210+
});
211+
212+
return;
213+
}
214+
215+
this.InvokeOnMainThread(() =>
216+
{
217+
var alert = UIAlertController.Create("Success", "OAuth token refreshed.", UIAlertControllerStyle.Alert);
218+
alert.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
219+
this.PresentViewController(alert, true, null);
220+
});
221+
}
222+
223+
private async void GetTimelineButton_TouchUpInside(object? sender, EventArgs e)
224+
{
225+
var (timeline, timelineError) = await this.atProtocol.Feed.GetTimelineAsync(limit: 1);
226+
if (timelineError != null)
227+
{
228+
this.InvokeOnMainThread(() =>
229+
{
230+
var alert = UIAlertController.Create("Error", $"GetTimeline failed: {timelineError}", UIAlertControllerStyle.Alert);
231+
alert.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
232+
this.PresentViewController(alert, true, null);
233+
});
234+
235+
return;
236+
}
237+
238+
var postCount = timeline?.Feed?.Count ?? 0;
239+
this.InvokeOnMainThread(() =>
240+
{
241+
var alert = UIAlertController.Create("Success", $"Timeline returned {postCount} post(s).", UIAlertControllerStyle.Alert);
242+
alert.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
243+
this.PresentViewController(alert, true, null);
244+
});
245+
}
246+
247+
private void SaveSessionButton_TouchUpInside(object? sender, EventArgs e)
248+
{
249+
var oauthSession = this.atProtocol.OAuthSession;
250+
if (oauthSession == null)
251+
{
252+
this.InvokeOnMainThread(() =>
253+
{
254+
var alert = UIAlertController.Create("Error", "No active OAuth session to save.", UIAlertControllerStyle.Alert);
255+
alert.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
256+
this.PresentViewController(alert, true, null);
257+
});
258+
259+
return;
260+
}
261+
262+
var json = oauthSession.ToString();
263+
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "session.json");
264+
File.WriteAllText(path, json);
265+
266+
this.InvokeOnMainThread(() =>
267+
{
268+
var alert = UIAlertController.Create("Success", $"Session saved to {path}", UIAlertControllerStyle.Alert);
269+
alert.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
270+
this.PresentViewController(alert, true, null);
271+
});
272+
}
273+
274+
private async void LoadSessionButton_TouchUpInside(object? sender, EventArgs e)
275+
{
276+
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "session.json");
277+
if (!File.Exists(path))
278+
{
279+
this.InvokeOnMainThread(() =>
280+
{
281+
var alert = UIAlertController.Create("Error", "No saved session file found.", UIAlertControllerStyle.Alert);
282+
alert.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
283+
this.PresentViewController(alert, true, null);
284+
});
285+
286+
return;
287+
}
288+
289+
var json = File.ReadAllText(path);
290+
var authSession = AuthSession.FromString(json);
291+
if (authSession == null)
292+
{
293+
this.InvokeOnMainThread(() =>
294+
{
295+
var alert = UIAlertController.Create("Error", "Failed to deserialize session.", UIAlertControllerStyle.Alert);
296+
alert.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
297+
this.PresentViewController(alert, true, null);
298+
});
299+
300+
return;
301+
}
302+
303+
var (session, error) = await this.atProtocol.AuthenticateWithOAuth2SessionResultAsync(authSession, ClientMetadataUrl);
304+
if (error != null)
305+
{
306+
this.InvokeOnMainThread(() =>
307+
{
308+
var alert = UIAlertController.Create("Error", $"Failed to restore session: {error}", UIAlertControllerStyle.Alert);
309+
alert.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
310+
this.PresentViewController(alert, true, null);
311+
});
312+
313+
return;
314+
}
315+
316+
this.InvokeOnMainThread(() =>
317+
{
318+
this.refreshTokenButton.Enabled = true;
319+
this.getTimelineButton.Enabled = true;
320+
this.saveSessionButton.Enabled = true;
321+
var alert = UIAlertController.Create("Success", $"Session loaded. Authenticated as {session?.Handle}", UIAlertControllerStyle.Alert);
322+
alert.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
323+
this.PresentViewController(alert, true, null);
324+
});
325+
}
326+
147327
private void OnError(NSError? error)
148328
{
149329
this.InvokeOnMainThread(() =>

samples/BSkyOauth/BSkyOAuth.MacOS/BSkyOAuth.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFramework>net9.0-macos</TargetFramework>
3+
<TargetFramework>net10.0-macos</TargetFramework>
44
<OutputType>Exe</OutputType>
55
<Nullable>enable</Nullable>
66
<ImplicitUsings>true</ImplicitUsings>

samples/BSkyOauth/BSkyOAuth.iOS/BSkyOAuth.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFramework>net9.0-ios</TargetFramework>
3+
<TargetFramework>net10.0-ios</TargetFramework>
44
<OutputType>Exe</OutputType>
55
<Nullable>enable</Nullable>
66
<ImplicitUsings>true</ImplicitUsings>

samples/BSkyOauth/BSkyOauth.Windows/BSkyOAuth.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<OutputType>WinExe</OutputType>
4-
<TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
4+
<TargetFramework>net10.0-windows10.0.19041.0</TargetFramework>
55
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
66
<RootNamespace>BSkyOAuth</RootNamespace>
77
<ApplicationManifest>app.manifest</ApplicationManifest>

samples/OAuth/OAuth.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net9.0</TargetFramework>
5+
<TargetFramework>net10.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
88
<PublishAot>true</PublishAot>

src/FishyFlip/ATProtocol.cs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -265,17 +265,31 @@ public async Task<string> GenerateOAuth2AuthenticationUrlAsync(string clientId,
265265
/// <param name="session">The OAuth session.</param>
266266
/// <param name="clientId">The client ID.</param>
267267
/// <param name="instanceUrl">Optional. The instance URL. If null, uses https://bsky.social.</param>
268+
/// <param name="cancellationToken">Optional. A CancellationToken that can be used to cancel the operation.</param>
268269
/// <returns>A task that represents the asynchronous operation. The task result contains a Result object with the session details, or null if the session could not be created.</returns>
269-
public async Task<Result<Session?>> AuthenticateWithOAuth2SessionResultAsync(AuthSession session, string clientId, string? instanceUrl = default)
270+
public async Task<Result<Session?>> AuthenticateWithOAuth2SessionResultAsync(AuthSession session, string clientId, string? instanceUrl = default, CancellationToken cancellationToken = default)
270271
{
272+
if (string.IsNullOrEmpty(instanceUrl))
273+
{
274+
var identifier = session.Session.Did;
275+
var (hostUrl, error) = await this.ResolveATIdentifierToHostAddressAsync(identifier, cancellationToken);
276+
277+
if (error is not null)
278+
{
279+
return error;
280+
}
281+
282+
instanceUrl = hostUrl ?? throw new OAuth2Exception("Failed to resolve instance URL from ATIdentifier.");
283+
}
284+
271285
var oAuth2SessionManager = new OAuth2SessionManager(this);
272286
this.SessionManager = oAuth2SessionManager;
273287
if (string.IsNullOrEmpty(session.ProofKey))
274288
{
275289
return new ATError(new OAuth2Exception("Proof key is required for OAuth2 sessions."));
276290
}
277291

278-
var (session2, error2) = await oAuth2SessionManager.StartSessionAsync(session, clientId, instanceUrl);
292+
var (session2, error2) = await oAuth2SessionManager.StartSessionAsync(session, clientId, instanceUrl, cancellationToken);
279293
if (error2 is not null)
280294
{
281295
return error2;
@@ -308,14 +322,15 @@ public async Task<string> GenerateOAuth2AuthenticationUrlAsync(string clientId,
308322
/// <summary>
309323
/// Refreshes the current session asynchronously.
310324
/// </summary>
325+
/// <param name="token">Cancellation Token.</param>
311326
/// <returns><see cref="AuthSession"/>.</returns>
312-
public async Task<Result<AuthSession?>> RefreshAuthSessionResultAsync()
327+
public async Task<Result<AuthSession?>> RefreshAuthSessionResultAsync(CancellationToken? token = default)
313328
{
314329
switch (this.sessionManager)
315330
{
316331
case OAuth2SessionManager oAuth2SessionManager:
317332
// Refresh the token to make sure it's the most up to date.
318-
var (resultOauth, errorOauth) = await oAuth2SessionManager.RefreshSessionAsync();
333+
var (resultOauth, errorOauth) = await oAuth2SessionManager.RefreshSessionAsync(token ?? CancellationToken.None);
319334
if (errorOauth is not null)
320335
{
321336
return errorOauth;
@@ -326,7 +341,7 @@ public async Task<string> GenerateOAuth2AuthenticationUrlAsync(string clientId,
326341
// The information from RefreshSessionOutput is set in passwordManager.Session,
327342
// so we can return the session from password manager, and only worry about
328343
// checking for the error.
329-
var (_, error) = await passwordManager.RefreshSessionAsync();
344+
var (_, error) = await passwordManager.RefreshSessionAsync(token ?? CancellationToken.None);
330345
if (error is not null)
331346
{
332347
return error;

src/FishyFlip/OAuth2SessionManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ public OAuth2SessionManager(ATProtocol protocol)
247247
refreshSessionOutput.RefreshJwt = result.RefreshToken;
248248
refreshSessionOutput.Did = this.session.Did;
249249
refreshSessionOutput.DidDoc = this.session.DidDoc;
250+
250251
return refreshSessionOutput;
251252
}
252253

0 commit comments

Comments
 (0)