전역 구조 리팩터링 및 테스트 확장

This commit is contained in:
JiWoong Sul
2025-09-29 01:51:47 +09:00
parent c00c0c9ab2
commit fef7108479
70 changed files with 7709 additions and 3185 deletions

View File

@@ -0,0 +1,19 @@
import 'token_storage_stub.dart'
if (dart.library.html) 'token_storage_web.dart'
if (dart.library.io) 'token_storage_native.dart';
/// 액세스/리프레시 토큰을 안전하게 보관하는 스토리지 인터페이스.
abstract class TokenStorage {
Future<void> writeAccessToken(String? token);
Future<String?> readAccessToken();
Future<void> writeRefreshToken(String? token);
Future<String?> readRefreshToken();
Future<void> clear();
}
/// 플랫폼에 맞는 스토리지 구현체를 생성한다.
TokenStorage createTokenStorage() => buildTokenStorage();

View File

@@ -0,0 +1,53 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'token_storage.dart';
const _kAccessTokenKey = 'access_token';
const _kRefreshTokenKey = 'refresh_token';
TokenStorage buildTokenStorage() {
const storage = FlutterSecureStorage(
aOptions: AndroidOptions(encryptedSharedPreferences: true),
iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
mOptions: MacOsOptions(),
wOptions: WindowsOptions(),
lOptions: LinuxOptions(),
);
return _SecureTokenStorage(storage);
}
class _SecureTokenStorage implements TokenStorage {
const _SecureTokenStorage(this._storage);
final FlutterSecureStorage _storage;
@override
Future<void> clear() async {
await _storage.delete(key: _kAccessTokenKey);
await _storage.delete(key: _kRefreshTokenKey);
}
@override
Future<String?> readAccessToken() => _storage.read(key: _kAccessTokenKey);
@override
Future<String?> readRefreshToken() => _storage.read(key: _kRefreshTokenKey);
@override
Future<void> writeAccessToken(String? token) async {
if (token == null || token.isEmpty) {
await _storage.delete(key: _kAccessTokenKey);
return;
}
await _storage.write(key: _kAccessTokenKey, value: token);
}
@override
Future<void> writeRefreshToken(String? token) async {
if (token == null || token.isEmpty) {
await _storage.delete(key: _kRefreshTokenKey);
return;
}
await _storage.write(key: _kRefreshTokenKey, value: token);
}
}

View File

@@ -0,0 +1,24 @@
import 'token_storage.dart';
TokenStorage buildTokenStorage() => _UnsupportedTokenStorage();
class _UnsupportedTokenStorage implements TokenStorage {
Never _unsupported() {
throw UnsupportedError('TokenStorage is not supported on this platform.');
}
@override
Future<void> clear() async => _unsupported();
@override
Future<String?> readAccessToken() async => _unsupported();
@override
Future<String?> readRefreshToken() async => _unsupported();
@override
Future<void> writeAccessToken(String? token) async => _unsupported();
@override
Future<void> writeRefreshToken(String? token) async => _unsupported();
}

View File

@@ -0,0 +1,45 @@
// ignore: deprecated_member_use, avoid_web_libraries_in_flutter
import 'dart:html' as html;
import 'token_storage.dart';
const _kAccessTokenKey = 'access_token';
const _kRefreshTokenKey = 'refresh_token';
TokenStorage buildTokenStorage() => _WebTokenStorage(html.window.localStorage);
class _WebTokenStorage implements TokenStorage {
const _WebTokenStorage(this._storage);
final Map<String, String> _storage;
@override
Future<void> clear() async {
_storage.remove(_kAccessTokenKey);
_storage.remove(_kRefreshTokenKey);
}
@override
Future<String?> readAccessToken() async => _storage[_kAccessTokenKey];
@override
Future<String?> readRefreshToken() async => _storage[_kRefreshTokenKey];
@override
Future<void> writeAccessToken(String? token) async {
if (token == null || token.isEmpty) {
_storage.remove(_kAccessTokenKey);
} else {
_storage[_kAccessTokenKey] = token;
}
}
@override
Future<void> writeRefreshToken(String? token) async {
if (token == null || token.isEmpty) {
_storage.remove(_kRefreshTokenKey);
} else {
_storage[_kRefreshTokenKey] = token;
}
}
}