feat: Ref 호환 접속 제한과 벌점 초기화 구현

실행 중인 프로필에서만 접속 벌점을 누적하고 제한 임계값과 대상 경로를 Ref 순서에 맞춘다. 자기 턴 명령 성공 시 순간 점수를 같은 flush에서 초기화하며 월간 누적 감쇠는 유지한다. 제한 중 메인 자동 갱신과 실시간 구독을 중지하고 수동 갱신 성공 시 복구한다.
This commit is contained in:
2026-08-15 18:49:41 +00:00
parent a99f8166eb
commit 48d94e5ff2
29 changed files with 982 additions and 232 deletions
+29 -17
View File
@@ -82,6 +82,16 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
const realtimeEnabled = ref(true);
const realtimeStatus = ref<'idle' | 'connected' | 'paused'>('idle');
const realtimeActive = ref(false);
const accessLimited = ref(false);
const handleDashboardError = (value: unknown) => {
const message = resolveErrorMessage(value);
error.value = message;
if (message.startsWith('접속 제한중입니다.')) {
accessLimited.value = true;
realtimeStatus.value = 'paused';
}
};
const general = ref<PresentGeneralContext['general'] | null>(null);
const city = ref<PresentGeneralContext['city'] | null>(null);
@@ -508,6 +518,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
{ context: true, commandTable: true, boardAccess: true },
true
);
accessLimited.value = false;
applyDashboardPatch(contextPatch);
const context = contextSnapshot;
@@ -573,7 +584,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
}
initialized = true;
} catch (err) {
error.value = resolveErrorMessage(err);
handleDashboardError(err);
} finally {
if (isInitialLoad) {
loading.value = false;
@@ -626,14 +637,14 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
if (plan.records) recordsError.value = null;
if (plan.frontStatus) frontStatusError.value = null;
try {
const contextBundlePromise =
plan.context || plan.commands || plan.boardAccess
? fetchContextBundlePatch({
context: plan.context,
commandTable: plan.commands,
boardAccess: plan.boardAccess,
})
: Promise.resolve(undefined);
const contextPatch = await fetchContextBundlePatch({
// Every automatic refresh crosses this access-limit gate. The
// context delta is usually unchanged and therefore stays small.
context: true,
commandTable: plan.commands,
boardAccess: plan.boardAccess,
});
accessLimited.value = false;
const lobbyPromise = plan.lobby ? trpc.lobby.info.query() : Promise.resolve(undefined);
const mapPromise = plan.map
? trpc.world.getMap.query({ generalId: id, showMe: true, useCache: true })
@@ -659,8 +670,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
})
: Promise.resolve(undefined);
const [contextPatch, lobby, map, contacts, generalTurns, records, nextFrontStatus] = await Promise.all([
contextBundlePromise,
const [lobby, map, contacts, generalTurns, records, nextFrontStatus] = await Promise.all([
lobbyPromise,
mapPromise,
contactsPromise,
@@ -669,7 +679,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
frontPromise,
]);
const patch: DashboardReadModelPatch = contextPatch ? { ...contextPatch } : {};
const patch: DashboardReadModelPatch = { ...contextPatch };
if (lobby !== undefined) patch.lobbyInfo = lobby;
if (map !== undefined) patch.worldMap = map;
if (contacts !== undefined) patch.messageContacts = contacts;
@@ -690,7 +700,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
applyDashboardPatch(patch);
publishDashboardPatch(patch);
} catch (err) {
error.value = resolveErrorMessage(err);
handleDashboardError(err);
} finally {
refreshing.value = false;
}
@@ -709,7 +719,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
applyDashboardPatch(patch);
publishDashboardPatch(patch);
} catch (err) {
error.value = resolveErrorMessage(err);
handleDashboardError(err);
}
};
@@ -970,6 +980,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
realtimeActive.value &&
document.visibilityState !== 'hidden' &&
realtimeEnabled.value &&
!accessLimited.value &&
session.isReady &&
session.hasGeneral &&
generalId.value !== null;
@@ -1158,11 +1169,12 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
session.profile,
session.user?.id,
generalId.value,
accessLimited.value,
],
([active, enabled, ready, hasGeneral]) => {
realtimeStatus.value = !enabled ? 'paused' : realtimeStatus.value;
([active, enabled, ready, hasGeneral, , , , , limited]) => {
realtimeStatus.value = !enabled || limited ? 'paused' : realtimeStatus.value;
if (!active || !ready || !hasGeneral) {
realtimeStatus.value = enabled ? 'idle' : 'paused';
realtimeStatus.value = enabled && !limited ? 'idle' : 'paused';
}
reconcileRealtimeCoordinator();
}