Start writing the web app
All checks were successful
ci/woodpecker/push/build Pipeline was successful

Signed-off-by: Nikolai Rodionov <allanger@posteo.com>
This commit was merged in pull request #5.
This commit is contained in:
2026-05-19 13:37:41 +02:00
parent 0c5b657a2f
commit 09df205fdb
76 changed files with 1961 additions and 117 deletions

View File

@@ -0,0 +1,71 @@
import 'dart:developer';
import 'package:grpc/grpc.dart';
class AuthInterceptor extends ClientInterceptor {
final String Function() getAccessToken;
final String Function() getRefreshToken;
AuthInterceptor({
required this.getAccessToken,
required this.getRefreshToken,
});
bool _isPublicEndpoint(String path) {
return path.contains('/Public');
}
bool _isRefreshEndpoint(String path) {
return path.contains('/RefreshToken');
}
Map<String, String> _buildMetadata(String path) {
final metadata = <String, String>{};
// Public endpoints → no token
if (_isPublicEndpoint(path)) {
log("Public endpoint, no tokens needed");
return metadata;
}
String? token;
// Refresh endpoint → use refresh token
if (_isRefreshEndpoint(path)) {
log("Adding a refresh token to the metadata");
token = getRefreshToken();
} else {
log("Adding an acces token to the metadata");
token = getAccessToken();
}
if (token.isNotEmpty) {
metadata['authorization'] = 'Bearer $token';
}
return metadata;
}
@override
ResponseStream<R> interceptStreaming<Q, R>(
ClientMethod<Q, R> method,
Stream<Q> requests,
CallOptions options,
ClientStreamingInvoker<Q, R> invoker,
) {
// TODO: implement interceptStreaming
throw UnimplementedError();
}
@override
ResponseFuture<R> interceptUnary<Q, R>(
ClientMethod<Q, R> method,
Q request,
CallOptions options,
ClientUnaryInvoker<Q, R> invoker,
) {
final modifiedOptions = options.mergedWith(
CallOptions(metadata: _buildMetadata(method.path)),
);
return super.interceptUnary(method, request, modifiedOptions, invoker);
}
}

View File

@@ -0,0 +1,16 @@
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:grpc/grpc_web.dart';
final grpcChannelProvider = Provider<GrpcWebClientChannel>((ref) {
String backendURL = dotenv.env['SOFTPLAYER_BACKEND_URL']!;
final GrpcWebClientChannel channel = GrpcWebClientChannel.xhr(
Uri.parse(backendURL),
);
ref.onDispose(() async {
await channel.shutdown();
});
return channel;
});

View File

@@ -0,0 +1,54 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:softplayer_dart_proto/accounts/v1/accounts_v1.pbgrpc.dart';
import 'package:softplayer_dart_proto/test/v1/test_v1.pbgrpc.dart';
import 'package:softplayer_web/core/grpc/grpc_auth_interceptor.dart';
import 'package:softplayer_web/core/tokens/application/tokens_application.dart';
import 'grpc_channel_provider.dart';
final testServiceClientProvider = Provider<TestServiceClient>((ref) {
final channel = ref.watch(grpcChannelProvider);
final tokenState = ref.read(tokensControllerProvider).value;
if (tokenState == null) {
throw Exception("Token state is not initialized");
}
return TestServiceClient(
channel,
interceptors: [
AuthInterceptor(
getAccessToken: tokenState.getAccessToken,
getRefreshToken: tokenState.getRefreshToken,
),
],
);
});
final accountsServiceClientProvider = Provider<AccountsServiceClient>((ref) {
final channel = ref.watch(grpcChannelProvider);
final tokenState = ref.read(tokensControllerProvider).value;
if (tokenState == null) {
throw Exception("Token state is not initialized");
}
return AccountsServiceClient(
channel,
interceptors: [
AuthInterceptor(
getAccessToken: tokenState.getAccessToken,
getRefreshToken: tokenState.getRefreshToken,
),
],
);
});
final publicAccountsServiceClientProvider =
Provider<PublicAccountsServiceClient>((ref) {
final channel = ref.watch(grpcChannelProvider);
return PublicAccountsServiceClient(channel);
});
final refreshSessionServiceClientProvider =
Provider<RefreshSessionServiceClient>((ref) {
final channel = ref.watch(grpcChannelProvider);
return RefreshSessionServiceClient(channel);
});