oauth support is almost done

This commit is contained in:
Henry Hiles 2026-07-19 22:46:39 -04:00
commit 52769238bd
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
19 changed files with 146 additions and 9 deletions

View file

@ -22,6 +22,14 @@
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data android:name="flutter_deeplinking_enabled" android:value="false" />
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="nexus.federated.nexus" />
</intent-filter>
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"

View file

@ -13,7 +13,7 @@ class AuthUrlController extends AsyncNotifier<OAuthAuthCodeResponse> {
.getAuthUrl(
.new(
homeserverUrl: homeserver,
redirectUri: .new(scheme: "nexus.federated.nexus"),
redirectUri: .new(scheme: "nexus.federated.nexus", path: "/"),
responseMode: .query,
scopes: .new([.clientApi]),
clientId: await ref.watch(

View file

@ -22,6 +22,7 @@ import "package:nexus/models/requests/get_related_events.dart";
import "package:nexus/models/requests/get_room_state.dart";
import "package:nexus/models/requests/join_room.dart";
import "package:nexus/models/profile.dart";
import "package:nexus/models/requests/oauth/exchange_token.dart";
import "package:nexus/models/requests/oauth/get_auth_url.dart";
import "package:nexus/models/requests/oauth/register_client.dart";
import "package:nexus/models/requests/paginate.dart";
@ -288,6 +289,9 @@ class ClientController extends AsyncNotifier<int> {
await _sendCommand("oauth_get_authorization_url", request.toJson()),
);
Future<void> exchangeToken(OAuthExchangeTokenRequest request) async =>
await _sendCommand("oauth_exchange_token", request.toJson());
Future<Uri?> discoverHomeserver(Uri homeserver) async {
try {
final response = await _sendCommand("discover_homeserver", {

View file

@ -20,7 +20,9 @@ class ClientIdController extends AsyncNotifier<String> {
),
homeserverUrl: homeserver,
clientUri: Uri.https("nexus.federated.nexus"),
redirectUris: .new([.new(scheme: "nexus.federated.nexus")]),
redirectUris: .new([
.new(scheme: "nexus.federated.nexus", path: "/"),
]),
),
);

View file

@ -79,6 +79,7 @@ void main() async {
runApp(
ProviderScope(
retry: null,
observers: [
// Change false to true if you want debug information on provider reloads
// ignore: dead_code

View file

@ -0,0 +1,17 @@
import "package:freezed_annotation/freezed_annotation.dart";
part "exchange_token.freezed.dart";
part "exchange_token.g.dart";
@freezed
abstract class OAuthExchangeTokenRequest with _$OAuthExchangeTokenRequest {
const factory OAuthExchangeTokenRequest({
required Uri homeserverUrl,
required String codeVerifier,
required Uri redirectUri,
required String code,
required String clientId,
}) = _OAuthExchangeTokenRequest;
factory OAuthExchangeTokenRequest.fromJson(Map<String, Object?> json) =>
_$OAuthExchangeTokenRequestFromJson(json);
}

View file

@ -18,6 +18,7 @@ class ChatPage extends ConsumerWidget {
final showMembersByDefault = constraints.maxWidth > 1000;
final initComplete = ref.watch(InitCompleteController.provider);
final roomId = ref.watch(KeyController.provider(KeyController.roomKey));
ref.read(EmojiController.provider);
return Scaffold(

View file

@ -1,9 +1,11 @@
import "package:app_links/app_links.dart";
import "package:flutter/material.dart";
import "package:flutter_hooks/flutter_hooks.dart";
import "package:flutter_svg/flutter_svg.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/auth_url.dart";
import "package:nexus/controllers/client.dart";
import "package:nexus/controllers/client_id.dart";
import "package:nexus/helpers/launch_helper.dart";
import "package:nexus/main.dart";
import "package:nexus/models/homeserver.dart";
@ -48,11 +50,35 @@ class SelectServerPage extends HookConsumerWidget {
),
);
} else {
final authUrl = await ref.read(
final codeResponse = await ref.watch(
AuthUrlController.provider(newUrl).future,
);
await ref.watch(LaunchHelper.provider).launchUrl(authUrl.url);
await ref.watch(LaunchHelper.provider).launchUrl(codeResponse.url);
AppLinks().uriLinkStream.listen((encodedUri) async {
final code = encodedUri.queryParameters["code"];
if (code != null) {
await ref
.watch(ClientController.provider.notifier)
.exchangeToken(
.new(
homeserverUrl: newUrl,
codeVerifier: codeResponse.codeVerifier,
redirectUri: .new(
scheme: "nexus.federated.nexus",
path: "/",
),
code: code,
clientId: await ref.watch(
ClientIdController.provider(newUrl).future,
),
),
)
.onError(showError);
}
});
// await Navigator.of(context).push(
// MaterialPageRoute(builder: (_) => LoginPage(homeserver: newUrl)),
// );

View file

@ -8,6 +8,7 @@
#include <dynamic_color/dynamic_color_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <gtk/gtk_plugin.h>
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
#include <media_kit_video/media_kit_video_plugin.h>
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
@ -21,6 +22,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) gtk_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
gtk_plugin_register_with_registrar(gtk_registrar);
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);

View file

@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
dynamic_color
file_selector_linux
gtk
media_kit_libs_linux
media_kit_video
screen_retriever_linux

View file

@ -2,8 +2,9 @@
Name=Nexus
GenericName=Matrix Client
Comment=A simple and user-friendly Matrix client
Exec=nexus
Exec=nexus %u
Icon=nexus
Terminal=false
Type=Application
Categories=Chat;Network;InstantMessaging;
MimeType=x-scheme-handler/nexus.federated.nexus;

View file

@ -23,6 +23,13 @@ static void first_frame_cb(MyApplication* self, FlView *view)
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GList* windows = gtk_application_get_windows(GTK_APPLICATION(application));
if (windows) {
gtk_window_present(GTK_WINDOW(windows->data));
return;
}
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
@ -93,7 +100,7 @@ static gboolean my_application_local_command_line(GApplication* application, gch
g_application_activate(application);
*exit_status = 0;
return TRUE;
return FALSE;
}
// Implements GApplication::startup.
@ -140,6 +147,6 @@ MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
"flags", G_APPLICATION_NON_UNIQUE,
"flags", G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_HANDLES_OPEN,
nullptr));
}

View file

@ -5,6 +5,7 @@
import FlutterMacOS
import Foundation
import app_links
import dynamic_color
import file_picker
import file_selector_macos
@ -18,6 +19,7 @@ import wakelock_plus
import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))

View file

@ -28,5 +28,18 @@
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<!-- abstract name for this URL type (you can leave it blank) -->
<string>auth_callback</string>
<key>CFBundleURLSchemes</key>
<array>
<!-- your schemes -->
<string>nexus.federated.nexus</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -41,6 +41,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.13.11"
app_links:
dependency: "direct main"
description:
name: app_links
sha256: "3462d9defc61565fde4944858b59bec5be2b9d5b05f20aed190adb3ad08a7abc"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
app_links_linux:
dependency: transitive
description:
name: app_links_linux
sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
url: "https://pub.dev"
source: hosted
version: "1.0.3"
app_links_platform_interface:
dependency: transitive
description:
name: app_links_platform_interface
sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
app_links_web:
dependency: transitive
description:
name: app_links_web
sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
url: "https://pub.dev"
source: hosted
version: "1.0.4"
archive:
dependency: transitive
description:
@ -558,6 +590,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.2"
gtk:
dependency: transitive
description:
name: gtk
sha256: "4ff85b2a16724029dd9e5bbb5a94b6918f9973f74ba571c949d2002801879cf5"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
hooks:
dependency: "direct main"
description:

View file

@ -76,6 +76,7 @@ dependencies:
m3e_card_list: ^0.1.0
xdg_directories: ^1.1.0
package_info_plus: ^9.0.1
app_links: ^7.0.0
dev_dependencies:
build_runner: 2.15.0

View file

@ -6,6 +6,7 @@
#include "generated_plugin_registrant.h"
#include <app_links/app_links_plugin_c_api.h>
#include <dynamic_color/dynamic_color_plugin_c_api.h>
#include <file_selector_windows/file_selector_windows.h>
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
@ -15,6 +16,8 @@
#include <window_manager/window_manager_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
AppLinksPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
DynamicColorPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
FileSelectorWindowsRegisterWithRegistrar(

View file

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
app_links
dynamic_color
file_selector_windows
media_kit_libs_windows_video

View file

@ -1,12 +1,17 @@
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
#include "app_links/app_links_plugin_c_api.h"
#include "flutter_window.h"
#include "utils.h"
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) {
// Forward the link to an existing instance, if any, then exit.
// You may ignore the result if you need to create another window.
if (SendAppLinkToInstance()) {
return EXIT_SUCCESS;
}
// Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {