로그아웃 즉시 처리 · 푸시 토큰 정리 순서
This commit is contained in:
@@ -65,6 +65,7 @@ class PushMessaging {
|
||||
///
|
||||
/// iOS 는 APNs 키 전까지 토큰이 null 이다 — 예외가 아니라 정상이다.
|
||||
Future<String?> syncToken() async {
|
||||
await _awaitPendingClear();
|
||||
final token = await _transport.getToken();
|
||||
if (token == null || token.isEmpty) return null;
|
||||
await _saveToken(token);
|
||||
@@ -111,6 +112,7 @@ class PushMessaging {
|
||||
/// 팬덤 ID 는 사용자 문서에서 스스로 읽는다. 호출자가 넘기게 하면 설정 화면이
|
||||
/// 그 값을 알아야 하고, 화면이 알 이유가 없다.
|
||||
Future<void> syncTopics(Map<PushTopic, bool> prefs) async {
|
||||
await _awaitPendingClear();
|
||||
for (final fandomId in await _resolveFandomIds()) {
|
||||
final plan = topicPlan(fandomId, prefs);
|
||||
for (final topic in plan.subscribe) {
|
||||
@@ -150,25 +152,67 @@ class PushMessaging {
|
||||
///
|
||||
/// 토픽 해지를 먼저 하는 이유는 실패 순서다. 토큰 삭제가 실패해도 구독은 끊겨
|
||||
/// 알림이 멈춘다. 반대로 두면 토큰만 지워진 채 토픽 알림이 계속 온다.
|
||||
///
|
||||
/// **두 단계다.** 토큰 제거는 반환 전에 끝낸다(상한 [tokenRemoveTimeout]) — 이 기기 토큰이
|
||||
/// 이전 계정 문서에 남으면 다음 사용자가 그 계정의 개인 알림을 받는다. 호출자는 이 Future 가
|
||||
/// 끝난 뒤 로그아웃한다. 토픽 해지는 기다리지 않고 [_pendingClear] 에 남겨, 다음 로그인의
|
||||
/// [syncToken] · [syncTopics] 가 끝나기를 기다린 뒤에야 새 구독을 넣는다. 실패는 로그만 남긴다.
|
||||
Future<void> clearOnSignOut() async {
|
||||
// 해제는 한꺼번에 보낸다. 가입 행성 × 구분 수만큼 차례로 기다리면 그것만으로
|
||||
// 로그아웃이 수 초 걸린다.
|
||||
late final Future<void> run;
|
||||
run = _unsubscribeAll()
|
||||
.catchError(
|
||||
(Object e) => debugPrint('[Push] topic cleanup failed: $e'),
|
||||
)
|
||||
.whenComplete(() {
|
||||
if (identical(_pendingClear, run)) _pendingClear = null;
|
||||
});
|
||||
_pendingClear = run;
|
||||
try {
|
||||
await _removeToken().timeout(tokenRemoveTimeout);
|
||||
} on Object catch (e) {
|
||||
debugPrint('[Push] token removal skipped: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
static Duration tokenRemoveTimeout = const Duration(seconds: 2);
|
||||
|
||||
/// 진행 중인 로그아웃 정리. 끝나면 비운다.
|
||||
Future<void>? _pendingClear;
|
||||
|
||||
/// 새 구독 전에 남은 정리를 기다린다. 멈춘 정리가 로그인을 막지 않도록 상한을 둔다.
|
||||
Future<void> _awaitPendingClear() async {
|
||||
final pending = _pendingClear;
|
||||
if (pending == null) return;
|
||||
await pending.timeout(
|
||||
pendingClearTimeout,
|
||||
onTimeout: () => debugPrint('[Push] pending cleanup timed out'),
|
||||
);
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
static Duration pendingClearTimeout = const Duration(seconds: 10);
|
||||
|
||||
Future<void> _unsubscribeAll() async {
|
||||
// 해제는 한꺼번에 보낸다. 가입 행성 × 구분 수만큼 차례로 기다리면 그것만으로 수 초 걸린다.
|
||||
final ids = await _resolveFandomIds();
|
||||
// 다음 로그인은 다른 계정일 수 있다. 팬덤을 들고 있으면 그 계정의 토픽에 구독해 버린다.
|
||||
_fandomIds = null;
|
||||
await Future.wait([
|
||||
for (final fandomId in await _resolveFandomIds())
|
||||
for (final fandomId in ids)
|
||||
for (final topic in PushTopic.values)
|
||||
_transport.unsubscribeFromTopic(topic.topicFor(fandomId)),
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> _removeToken() async {
|
||||
final uid = _backend.uid;
|
||||
final token = await _transport.getToken();
|
||||
if (uid != null && token != null && token.isNotEmpty) {
|
||||
await _backend.db.doc('users/$uid').set({
|
||||
'fcmTokens': FieldValue.arrayRemove([token]),
|
||||
'updatedAt': FieldValue.serverTimestamp(),
|
||||
}, SetOptions(merge: true));
|
||||
}
|
||||
// 다음 로그인은 다른 계정일 수 있다. 팬덤을 들고 있으면 그 계정의 토픽에
|
||||
// 구독해 버린다.
|
||||
_fandomIds = null;
|
||||
if (uid == null || token == null || token.isEmpty) return;
|
||||
await _backend.db.doc('users/$uid').set({
|
||||
'fcmTokens': FieldValue.arrayRemove([token]),
|
||||
'updatedAt': FieldValue.serverTimestamp(),
|
||||
}, SetOptions(merge: true));
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
|
||||
Reference in New Issue
Block a user