👽 Update API to microservices

♻️ Refactor router pushes
This commit is contained in:
2025-07-17 14:35:09 +08:00
parent a7454edec0
commit e6c58b7b63
109 changed files with 9156 additions and 344 deletions

View File

@@ -0,0 +1,17 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/pods/network.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'captcha.config.g.dart';
@riverpod
Future<String> captchaUrl(Ref ref) async {
final apiClient = ref.watch(apiClientProvider);
final resp = await apiClient.get('/.well-known/services');
final serviceMapping = await resp.data;
var baseUrl = serviceMapping['DysonNetwork.Pass'] as String;
// The backend using self-signed certicates on development
// Which mobile simulator might not accept, use this to avoid errors
if (baseUrl.contains('https://localhost')) baseUrl = 'http://localhost:5216';
return '$baseUrl/captcha';
}

View File

@@ -0,0 +1,26 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'captcha.config.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$captchaUrlHash() => r'627caa2f2eb020a28a4b138122fe8e99915185f9';
/// See also [captchaUrl].
@ProviderFor(captchaUrl)
final captchaUrlProvider = AutoDisposeFutureProvider<String>.internal(
captchaUrl,
name: r'captchaUrlProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$captchaUrlHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef CaptchaUrlRef = AutoDisposeFutureProviderRef<String>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:island/pods/config.dart';
import 'package:island/screens/auth/captcha.config.dart';
import 'package:island/widgets/app_scaffold.dart';
class CaptchaScreen extends ConsumerWidget {
@@ -9,13 +9,15 @@ class CaptchaScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final serverUrl = ref.watch(serverUrlProvider);
final captchaUrl = ref.watch(captchaUrlProvider);
if (!captchaUrl.hasValue) return Center(child: CircularProgressIndicator());
return AppScaffold(
appBar: AppBar(title: Text("Anti-Robot")),
body: InAppWebView(
initialUrlRequest: URLRequest(
url: WebUri('$serverUrl/auth/captcha?redirect_uri=solink://captcha'),
url: WebUri('${captchaUrl.value}?redirect_uri=solian://captcha'),
),
shouldOverrideUrlLoading: (controller, navigationAction) async {
Uri? url = navigationAction.request.url;

View File

@@ -3,6 +3,7 @@
import 'dart:ui_web' as ui;
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/pods/config.dart';
import 'package:island/screens/auth/captcha.config.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:web/web.dart' as web;
import 'package:flutter/material.dart';
@@ -17,7 +18,7 @@ class CaptchaScreen extends ConsumerStatefulWidget {
class _CaptchaScreenState extends ConsumerState<CaptchaScreen> {
bool _isInitialized = false;
void _setupWebListener(String serverUrl) {
void _setupWebListener(String serverUrl) async {
web.window.onMessage.listen((event) {
if (event.data != null && event.data is String) {
final message = event.data as String;
@@ -29,9 +30,11 @@ class _CaptchaScreenState extends ConsumerState<CaptchaScreen> {
}
});
final captchaUrl = await ref.watch(captchaUrlProvider.future);
final iframe =
web.HTMLIFrameElement()
..src = '$serverUrl/auth/captcha'
..src = captchaUrl
..style.border = 'none'
..width = '100%'
..height = '100%';

View File

@@ -49,7 +49,7 @@ class CreateAccountScreen extends HookConsumerWidget {
showLoadingModal(context);
final client = ref.watch(apiClientProvider);
await client.post(
'/accounts',
'/id/accounts',
data: {
'name': usernameController.text,
'nick': nicknameController.text,
@@ -305,7 +305,7 @@ class _PostCreateModal extends HookConsumerWidget {
TextButton(
onPressed: () {
Navigator.pop(context);
context.pushReplacement('/auth/login');
context.pushReplacementNamed('login');
},
child: Text('login'.tr()),
),

View File

@@ -178,7 +178,7 @@ class _LoginCheckScreen extends HookConsumerWidget {
// Get token if challenge is completed
final client = ref.watch(apiClientProvider);
final tokenResp = await client.post(
'/auth/token',
'/id/auth/token',
data: {
'grant_type': 'authorization_code',
'code': code ?? challenge!.id,
@@ -215,7 +215,7 @@ class _LoginCheckScreen extends HookConsumerWidget {
if (name != null) {
final client = ref.watch(apiClientProvider);
await client.patch(
'/accounts/me/sessions/current/label',
'/id/accounts/me/sessions/current/label',
data: jsonEncode(name),
);
}
@@ -225,6 +225,7 @@ class _LoginCheckScreen extends HookConsumerWidget {
useEffect(() {
if (challenge != null && challenge?.stepRemain == 0) {
Future(() {
if (isBusy.value) return;
isBusy.value = true;
getToken().catchError((err) {
showErrorAlert(err);
@@ -265,7 +266,7 @@ class _LoginCheckScreen extends HookConsumerWidget {
// Pass challenge
final client = ref.watch(apiClientProvider);
final resp = await client.patch(
'/auth/challenge/${challenge!.id}',
'/id/auth/challenge/${challenge!.id}',
data: {'factor_id': factor!.id, 'password': pwd},
);
final result = SnAuthChallenge.fromJson(resp.data);
@@ -412,7 +413,7 @@ class _LoginPickerScreen extends HookConsumerWidget {
try {
await client.post(
'/auth/challenge/${challenge!.id}/factors/${factorPicked.value!.id}',
'/id/auth/challenge/${challenge!.id}/factors/${factorPicked.value!.id}',
data:
hintController.text.isNotEmpty
? jsonEncode(hintController.text)
@@ -555,7 +556,7 @@ class _LoginLookupScreen extends HookConsumerWidget {
try {
final client = ref.watch(apiClientProvider);
await client.post(
'/accounts/recovery/password',
'/id/accounts/recovery/password',
data: {'account': uname, 'captcha_token': captchaTk},
);
showInfoAlert('loginResetPasswordSent'.tr(), 'done'.tr());
@@ -573,7 +574,7 @@ class _LoginLookupScreen extends HookConsumerWidget {
try {
final client = ref.watch(apiClientProvider);
final resp = await client.post(
'/auth/challenge',
'/id/auth/challenge',
data: {
'account': uname,
'device_id': await getUdid(),
@@ -593,7 +594,7 @@ class _LoginLookupScreen extends HookConsumerWidget {
final result = SnAuthChallenge.fromJson(resp.data);
onChallenge(result);
final factorResp = await client.get(
'/auth/challenge/${result.id}/factors',
'/id/auth/challenge/${result.id}/factors',
);
onFactor(
List<SnAuthFactor>.from(
@@ -622,7 +623,7 @@ class _LoginLookupScreen extends HookConsumerWidget {
if (context.mounted) showLoadingModal(context);
final resp = await client.post(
'/auth/login/apple/mobile',
'/id/auth/login/apple/mobile',
data: {
'identity_token': credential.identityToken!,
'authorization_code': credential.authorizationCode,
@@ -633,7 +634,7 @@ class _LoginLookupScreen extends HookConsumerWidget {
final challenge = SnAuthChallenge.fromJson(resp.data);
onChallenge(challenge);
final factorResp = await client.get(
'/auth/challenge/${challenge.id}/factors',
'/id/auth/challenge/${challenge.id}/factors',
);
onFactor(
List<SnAuthFactor>.from(
@@ -658,11 +659,11 @@ class _LoginLookupScreen extends HookConsumerWidget {
final client = ref.watch(apiClientProvider);
try {
final resp = await client.get('/auth/challenge/$challengeId');
final resp = await client.get('/id/auth/challenge/$challengeId');
final challenge = SnAuthChallenge.fromJson(resp.data);
onChallenge(challenge);
final factorResp = await client.get(
'/auth/challenge/${challenge.id}/factors',
'/id/auth/challenge/${challenge.id}/factors',
);
onFactor(
List<SnAuthFactor>.from(

View File

@@ -120,7 +120,7 @@ class _OidcScreenState extends ConsumerState<OidcScreen> {
final queryParams = url.queryParameters;
// Check if we're on the token page
if (path.endsWith('/auth/callback')) {
if (path.endsWith('/id/auth/callback')) {
// Extract token from URL
final challenge = queryParams['challenge'];
// Return the token and close the webview