Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom PDSs #45

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Bluesky.NET/ApiClients/BlueskyApiClient.Actor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ partial class BlueskyApiClient
{
public async Task<Author?> GetAuthorAsync(string accessToken, string identifier)
{
var timelineUrl = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.ProfilePath}?actor={identifier}";
var timelineUrl = $"{_baseUrl}/{UrlConstants.ProfilePath}?actor={identifier}";
HttpRequestMessage message = new(HttpMethod.Get, timelineUrl);
message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

Expand Down Expand Up @@ -56,7 +56,7 @@ public async Task<Result<FeedResponse>> GetSuggestedPeopleAsync(
count = 100;
}

var url = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.SuggestedPeoplePath}?limit={count}";
var url = $"{_baseUrl}/{UrlConstants.SuggestedPeoplePath}?limit={count}";
if (cursor is { Length: > 0 })
{
url += $"&cursor={cursor}";
Expand All @@ -78,7 +78,7 @@ public async Task<bool> FollowActorAsync(
string subjectDid,
CancellationToken ct)
{
var url = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.CreateRecordPath}";
var url = $"{_baseUrl}/{UrlConstants.CreateRecordPath}";

FollowRecordBody body = new()
{
Expand Down
2 changes: 1 addition & 1 deletion src/Bluesky.NET/ApiClients/BlueskyApiClient.Blob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ partial class BlueskyApiClient
{
public async Task<Blob?> UploadBlobAsync(string accessToken, byte[] blob, string mimeType)
{
var url = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.UploadBlobPath}";
var url = $"{_baseUrl}/{UrlConstants.UploadBlobPath}";

HttpRequestMessage message = new(HttpMethod.Post, url)
{
Expand Down
4 changes: 2 additions & 2 deletions src/Bluesky.NET/ApiClients/BlueskyApiClient.FeedGenerators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public async Task<Result<IReadOnlyList<FeedGenerator>>> GetFeedGeneratorsAsync(

string combined = string.Join("&", feedParameters);

var url = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.FeedGeneratorsPath}?{combined}";
var url = $"{_baseUrl}/{UrlConstants.FeedGeneratorsPath}?{combined}";
HttpRequestMessage message = new(HttpMethod.Get, url);
message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

Expand All @@ -46,7 +46,7 @@ public async Task<Result<FeedResponse>> GetSuggestedFeedGeneratorsAsync(
CancellationToken ct,
string? cursor = null)
{
var url = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.SuggestedFeedsPath}";
var url = $"{_baseUrl}/{UrlConstants.SuggestedFeedsPath}";
if (cursor is { Length: > 0 })
{
url += $"?cursor={cursor}";
Expand Down
6 changes: 3 additions & 3 deletions src/Bluesky.NET/ApiClients/BlueskyApiClient.Feeds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public async Task<Result<FeedResponse>> GetFeedAsync(
{
ct.ThrowIfCancellationRequested();

var url = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.FeedPath}?feed={atUri}";
var url = $"{_baseUrl}/{UrlConstants.FeedPath}?feed={atUri}";
if (cursor is { Length: > 0 } cursorParameter)
{
url += $"&cursor={cursorParameter}";
Expand All @@ -45,7 +45,7 @@ public async Task<Result<FeedResponse>> GetFeedAsync(

public async Task<FeedResponse> GetTimelineAsync(string accesstoken, string? cursor = null)
{
var timelineUrl = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.TimelinePath}";
var timelineUrl = $"{_baseUrl}/{UrlConstants.TimelinePath}";
if (cursor is { Length: > 0 } cursorParameter)
{
timelineUrl += $"?cursor={cursorParameter}";
Expand Down Expand Up @@ -74,7 +74,7 @@ public async Task<FeedResponse> GetTimelineAsync(string accesstoken, string? cur

public async Task<IReadOnlyList<FeedItem>> GetAuthorFeedAsync(string accesstoken, string handle)
{
var feedUrl = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.AuthorFeedPath}?actor={handle}";
var feedUrl = $"{_baseUrl}/{UrlConstants.AuthorFeedPath}?actor={handle}";
HttpRequestMessage message = new(HttpMethod.Get, feedUrl);
message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accesstoken);

Expand Down
6 changes: 3 additions & 3 deletions src/Bluesky.NET/ApiClients/BlueskyApiClient.Notifications.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ partial class BlueskyApiClient
{
public async Task<IReadOnlyList<Notification>> GetNotificationsAsync(string accessToken)
{
var url = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.NotificationsPath}";
var url = $"{_baseUrl}/{UrlConstants.NotificationsPath}";
HttpRequestMessage message = new(HttpMethod.Get, url);
message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

Expand All @@ -35,7 +35,7 @@ public async Task<IReadOnlyList<Notification>> GetNotificationsAsync(string acce
/// <inheritdoc/>
public async Task<Result> UpdateSeenAsync(string accessToken, CancellationToken ct)
{
var url = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.UpdateSeenPath}";
var url = $"{_baseUrl}/{UrlConstants.UpdateSeenPath}";
UpdateSeenBody body = new()
{
SeenAt = DateTime.Now.ToUniversalTime().ToString("O")
Expand All @@ -52,7 +52,7 @@ public async Task<Result> UpdateSeenAsync(string accessToken, CancellationToken
/// <inheritdoc/>
public async Task<Result<int>> GetUnreadCountAsync(string accessToken, CancellationToken ct)
{
var url = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.UnreadCountPath}";
var url = $"{_baseUrl}/{UrlConstants.UnreadCountPath}";
HttpRequestMessage message = new(HttpMethod.Get, url);
message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

Expand Down
6 changes: 3 additions & 3 deletions src/Bluesky.NET/ApiClients/BlueskyApiClient.Posts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public async Task<IReadOnlyList<FeedPost>> GetPostsAsync(string accessToken, IRe
return [];
}

var timelineUrl = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.PostsPath}?uris={string.Join(",", atUriList)}";
var timelineUrl = $"{_baseUrl}/{UrlConstants.PostsPath}?uris={string.Join(",", atUriList)}";
HttpRequestMessage message = new(HttpMethod.Get, timelineUrl);
message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

Expand All @@ -48,7 +48,7 @@ public async Task<IReadOnlyList<FeedPost>> GetPostsAsync(string accessToken, IRe
/// <inheritdoc/>
public async Task<CreateRecordResponse?> SubmitPostAsync(string accessToken, string handle, SubmissionRecord record, RecordType recordType)
{
var url = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.CreateRecordPath}";
var url = $"{_baseUrl}/{UrlConstants.CreateRecordPath}";

CreateRecordBody body = new()
{
Expand Down Expand Up @@ -91,7 +91,7 @@ public async Task<IReadOnlyList<FeedPost>> GetPostsAsync(string accessToken, IRe
/// <inheritdoc/>
public async Task SubmitPostUndoAsync(string accessToken, string handle, string rkey, RecordType recordType, CancellationToken cancellationToken)
{
var url = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.DeleteRecordPath}";
var url = $"{_baseUrl}/{UrlConstants.DeleteRecordPath}";

DeleteRecordBody body = new()
{
Expand Down
2 changes: 1 addition & 1 deletion src/Bluesky.NET/ApiClients/BlueskyApiClient.Preferences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public async Task<Result<IReadOnlyList<PreferenceItem>>> GetPreferencesAsync(str
{
ct.ThrowIfCancellationRequested();

var url = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.PreferencesPath}";
var url = $"{_baseUrl}/{UrlConstants.PreferencesPath}";
HttpRequestMessage message = new(HttpMethod.Get, url);
message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

Expand Down
6 changes: 3 additions & 3 deletions src/Bluesky.NET/ApiClients/BlueskyApiClient.Search.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public async Task<Result<FeedResponse>> SearchFeedsAsync(
CancellationToken ct,
string? cursor = null)
{
var url = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.SearchFeedsPath}?query={HttpUtility.UrlEncode(query)}";
var url = $"{_baseUrl}/{UrlConstants.SearchFeedsPath}?query={HttpUtility.UrlEncode(query)}";

if (cursor is not null)
{
Expand All @@ -44,7 +44,7 @@ public async Task<Result<FeedResponse>> SearchActorsAsync(
string? cursor = null)
{
// Ref: https://docs.bsky.app/docs/api/app-bsky-actor-search-actors
var url = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.SearchActorsPath}?q={HttpUtility.UrlEncode(query)}";
var url = $"{_baseUrl}/{UrlConstants.SearchActorsPath}?q={HttpUtility.UrlEncode(query)}";

if (cursor is not null)
{
Expand All @@ -70,7 +70,7 @@ public async Task<Result<FeedResponse>> SearchActorsAsync(
// Ref: https://docs.bsky.app/docs/api/app-bsky-feed-search-posts
options ??= new();

var url = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.SearchPostsPath}?q={HttpUtility.UrlEncode(query)}";
var url = $"{_baseUrl}/{UrlConstants.SearchPostsPath}?q={HttpUtility.UrlEncode(query)}";

if (cursor is not null)
{
Expand Down
11 changes: 7 additions & 4 deletions src/Bluesky.NET/ApiClients/BlueskyApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@ namespace Bluesky.NET.ApiClients;
public partial class BlueskyApiClient : IBlueskyApiClient
{
private readonly HttpClient _httpClient = new();
private string _baseUrl = "https://bsky.social";

public async Task<Result<AuthResponse>> RefreshAsync(string refreshToken)
public async Task<Result<AuthResponse>> RefreshAsync(string refreshToken, string baseUrl = UrlConstants.BlueskyBaseUrl)
{
var refreshUrl = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.RefreshAuthPath}";
_baseUrl = baseUrl;
var refreshUrl = $"{baseUrl}/{UrlConstants.RefreshAuthPath}";
HttpRequestMessage message = new(HttpMethod.Post, refreshUrl);
message.Headers.Authorization = new AuthenticationHeaderValue("Bearer", refreshToken);
return await PostAuthMessageAsync(message);
}

/// <inheritdoc/>
public async Task<Result<AuthResponse>> AuthenticateAsync(string identifer, string appPassword)
public async Task<Result<AuthResponse>> AuthenticateAsync(string identifer, string appPassword, string baseUrl = UrlConstants.BlueskyBaseUrl)
{
var authUrl = $"{UrlConstants.BlueskyBaseUrl}/{UrlConstants.AuthPath}";
_baseUrl = baseUrl;
var authUrl = $"{baseUrl}/{UrlConstants.AuthPath}";

var requestBody = new AuthRequestBody
{
Expand Down
4 changes: 2 additions & 2 deletions src/Bluesky.NET/ApiClients/IBlueskyApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ public interface IBlueskyApiClient
/// <param name="userHandle">The user's handle or email address or DID.</param>
/// <param name="appPassword">An app password provided by the user.</param>
/// <returns>An <see cref="AuthResponse"/>.</returns>
Task<Result<AuthResponse>> AuthenticateAsync(string identifier, string appPassword);
Task<Result<AuthResponse>> AuthenticateAsync(string identifier, string appPassword, string baseUrl = UrlConstants.BlueskyBaseUrl);
Task<FeedResponse> GetTimelineAsync(string accesstoken, string? cursor = null);
Task<Result<AuthResponse>> RefreshAsync(string refreshToken);
Task<Result<AuthResponse>> RefreshAsync(string refreshToken, string baseUrl = UrlConstants.BlueskyBaseUrl);

Task<Author?> GetAuthorAsync(string accessToken, string identifier);

Expand Down
6 changes: 6 additions & 0 deletions src/BlueskyClient.Uwp/Strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AdvancedText" xml:space="preserve">
<value>Advanced</value>
</data>
<data name="AppPasswordHelpText" xml:space="preserve">
<value>What's an App Password?</value>
</data>
Expand Down Expand Up @@ -192,6 +195,9 @@
<data name="PasswordBoxPlaceholder" xml:space="preserve">
<value>Your special App Password</value>
</data>
<data name="PDSBoxPlaceholder" xml:space="preserve">
<value>Custom PDS URL</value>
</data>
<data name="PeopleText" xml:space="preserve">
<value>People</value>
</data>
Expand Down
21 changes: 21 additions & 0 deletions src/BlueskyClient.Uwp/Views/SignInPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,27 @@
Command="{x:Bind ViewModel.SignInCommand}"
Content="{x:Bind strings:Resources.SignInText}"
IsEnabled="{x:Bind ex:UIExtensions.Not(ViewModel.SigningIn), Mode=OneWay}" />

<winui:Expander Header="{x:Bind strings:Resources.AdvancedText}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Margin="0,8,0,0">
<StackPanel Orientation="Vertical">
<TextBlock
x:Name="PDSHeader"
FontSize="12"
Foreground="{ThemeResource TextFillColorTertiaryBrush}"
Text="{x:Bind strings:Resources.PDSBoxPlaceholder}"
TextWrapping="WrapWholeWords" />
<TextBox
Margin="0,4,0,0"
AutomationProperties.LabeledBy="{x:Bind IdentifierHeader}"
IsEnabled="{x:Bind ex:UIExtensions.Not(ViewModel.SigningIn), Mode=OneWay}"
IsSpellCheckEnabled="False"
PlaceholderText="https://bsky.social"
Text="{x:Bind ViewModel.PdsInput, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</winui:Expander>
</StackPanel>

<winui:InfoBar
Expand Down
16 changes: 11 additions & 5 deletions src/BlueskyClient/Services/AuthenticationService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Bluesky.NET.ApiClients;
using Bluesky.NET.Constants;
using Bluesky.NET.Models;
using BlueskyClient.Constants;
using FluentResults;
Expand Down Expand Up @@ -53,7 +54,7 @@ public async Task<Result<AuthResponse>> TrySilentSignInAsync()
string? storedAppPassword = _secureCredentialStorage.GetCredential(AppPasswordCredentialKey(storedDid));
if (storedAppPassword is { Length: > 0 })
{
result = await SignInWithValidatedCredentialsAsync(storedDid, storedAppPassword);
result = await SignInWithValidatedCredentialsAsync(storedDid, storedAppPassword, _secureCredentialStorage.GetCredential(BaseURLCredentialKey(storedDid)));
}

return result;
Expand All @@ -67,6 +68,7 @@ public void SignOut()
{
_secureCredentialStorage.SetCredential(storedDid, string.Empty);
_secureCredentialStorage.SetCredential(AppPasswordCredentialKey(storedDid), string.Empty);
_secureCredentialStorage.SetCredential(BaseURLCredentialKey(storedDid), string.Empty);
}

_userSettings.Set(UserSettingsConstants.LocalUserIdKey, string.Empty);
Expand All @@ -77,7 +79,7 @@ public void SignOut()
}

/// <inheritdoc/>
public async Task<Result<AuthResponse>> SignInAsync(string rawUserHandleOrEmail, string rawPassword)
public async Task<Result<AuthResponse>> SignInAsync(string rawUserHandleOrEmail, string rawPassword, string? baseUrl)
{
var userHandleOrEmail = rawUserHandleOrEmail.Trim().TrimStart('@');
var password = rawPassword.Trim();
Expand All @@ -87,7 +89,7 @@ public async Task<Result<AuthResponse>> SignInAsync(string rawUserHandleOrEmail,
return Result.Fail<AuthResponse>("Empty identifier or password");
}

return await SignInWithValidatedCredentialsAsync(userHandleOrEmail, password);
return await SignInWithValidatedCredentialsAsync(userHandleOrEmail, password, baseUrl ?? "bsky.social");
}

public async Task<Result<string>> TryGetFreshTokenAsync()
Expand Down Expand Up @@ -142,9 +144,9 @@ private void UpdateStoredToken(AuthResponse response)
}
}

private async Task<Result<AuthResponse>> SignInWithValidatedCredentialsAsync(string identifier, string password)
private async Task<Result<AuthResponse>> SignInWithValidatedCredentialsAsync(string identifier, string password, string? baseUrl)
{
Result<AuthResponse> result = await _apiClient.AuthenticateAsync(identifier, password);
Result<AuthResponse> result = await _apiClient.AuthenticateAsync(identifier, password, baseUrl ?? "bsky.social");

if (result.IsSuccess)
{
Expand All @@ -154,6 +156,9 @@ private async Task<Result<AuthResponse>> SignInWithValidatedCredentialsAsync(str
{
_userSettings.Set(UserSettingsConstants.SignedInDIDKey, did);
_secureCredentialStorage.SetCredential(AppPasswordCredentialKey(did), password);
_secureCredentialStorage.SetCredential(
BaseURLCredentialKey(did),
baseUrl ?? UrlConstants.BlueskyBaseUrl);
}
}

Expand All @@ -162,4 +167,5 @@ private async Task<Result<AuthResponse>> SignInWithValidatedCredentialsAsync(str


private static string AppPasswordCredentialKey(string did) => $"{did}-appPassword";
private static string BaseURLCredentialKey(string did) => $"{did}-baseUrl";
}
2 changes: 1 addition & 1 deletion src/BlueskyClient/Services/IAuthenticationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace BlueskyClient.Services;

public interface IAuthenticationService
{
Task<Result<AuthResponse>> SignInAsync(string rawUserHandle, string rawPassword);
Task<Result<AuthResponse>> SignInAsync(string rawUserHandle, string rawPassword, string? baseUrl);
void SignOut();
Task<Result<string>> TryGetFreshTokenAsync();
Task<Result<AuthResponse>> TrySilentSignInAsync();
Expand Down
5 changes: 4 additions & 1 deletion src/BlueskyClient/ViewModels/SignInPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public SignInPageViewModel(
[ObservableProperty]
private string _appPasswordInput = string.Empty;

[ObservableProperty]
private string _pdsInput = "bsky.social";

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ErrorBannerVisible))]
private string _signInErrorMessage = string.Empty;
Expand All @@ -56,7 +59,7 @@ private async Task SignInAsync()

_telemetry.TrackEvent(TelemetryConstants.SignInClicked);

Result<AuthResponse> result = await _authService.SignInAsync(UserHandleInput, AppPasswordInput);
Result<AuthResponse> result = await _authService.SignInAsync(UserHandleInput, AppPasswordInput, PdsInput.Contains("://") ? PdsInput : $"https://{PdsInput}");

SignInErrorMessage = result.IsSuccess
? string.Empty
Expand Down