mirror of
https://github.com/wuzf/2fa.git
synced 2026-09-03 07:17:09 +08:00
fix(ui): hide clock warning until first sync settles
读取到校准缓存时 status 会先变成 cached,start() 随即渲染出警告条, 几百毫秒后校准成功又隐藏,表现为每次刷新闪现一次。 新增 hasSettledSync 标记,首次校准有结果前不渲染任何警告。原先只有 local 分支有 syncPromise 守卫,cached 和 stale 分支漏了同样的处理。
This commit is contained in:
@@ -37,6 +37,7 @@ export function getTimeCode() {
|
||||
this.syncAttemptId = 0;
|
||||
this.activeSampleControllers = new Set();
|
||||
this.started = false;
|
||||
this.hasSettledSync = false;
|
||||
this.loadCachedOffset();
|
||||
}
|
||||
|
||||
@@ -248,6 +249,7 @@ export function getTimeCode() {
|
||||
this.localWallAtSyncMs = Date.now();
|
||||
this.monotonicEpochAtSyncMs = this.monotonicEpochNow();
|
||||
this.status = 'synced';
|
||||
this.hasSettledSync = true;
|
||||
this.generation += 1;
|
||||
this.persistOffset();
|
||||
this.renderStatus();
|
||||
@@ -256,6 +258,7 @@ export function getTimeCode() {
|
||||
}
|
||||
|
||||
markSyncFailure() {
|
||||
this.hasSettledSync = true;
|
||||
this.status = Number.isFinite(this.offsetMs) ? 'cached' : 'local';
|
||||
this.renderStatus();
|
||||
}
|
||||
@@ -352,7 +355,11 @@ export function getTimeCode() {
|
||||
const isStale = Number.isFinite(ageMs) && ageMs > CLOCK_SYNC_STALE_MS;
|
||||
let message = '';
|
||||
|
||||
if (this.status === 'local' && !this.syncPromise) {
|
||||
// 首次校准落地前不提示:页面刚加载时缓存偏移已生效但校准仍在进行,
|
||||
// 此时提示会在校准成功后立即消失,表现为刷新时的一次闪烁。
|
||||
if (!this.hasSettledSync) {
|
||||
message = '';
|
||||
} else if (this.status === 'local') {
|
||||
message = '无法校准服务器时间,OTP 正在使用设备时间,可能不正确。';
|
||||
} else if (this.status === 'cached') {
|
||||
const ageText = this.formatAge(ageMs);
|
||||
|
||||
@@ -548,6 +548,61 @@ describe('trusted browser clock', () => {
|
||||
await expect(backgroundSync).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('keeps the warning hidden on load while the first sync is still in flight', async () => {
|
||||
const elements = createWarningElements();
|
||||
const harness = createHarness({
|
||||
elements,
|
||||
storageValues: {
|
||||
[STORAGE_KEY]: createCachedClock({
|
||||
offsetMs: 12_000,
|
||||
localWallAtSyncMs: SERVER_BASE_MS - 60 * 60 * 1000,
|
||||
}),
|
||||
},
|
||||
});
|
||||
expect(harness.api.trustedClock.status).toBe('cached');
|
||||
|
||||
harness.queueSamples([20, 40, 60].map((rttMs) => ({ rttMs, offsetMs: 15_000 })));
|
||||
harness.api.trustedClock.start();
|
||||
const initialSync = harness.api.trustedClock.syncPromise;
|
||||
|
||||
expect(elements.clockWarning.hidden).toBe(true);
|
||||
expect(elements.clockWarningText.textContent).toBe('');
|
||||
expect(elements.clockWarning.classList.toggle).not.toHaveBeenCalledWith('show', true);
|
||||
|
||||
await harness.completePendingRequests();
|
||||
await expect(initialSync).resolves.toBe(true);
|
||||
|
||||
expect(harness.api.trustedClock.status).toBe('synced');
|
||||
expect(elements.clockWarning.hidden).toBe(true);
|
||||
expect(elements.clockWarning.classList.toggle).not.toHaveBeenCalledWith('show', true);
|
||||
});
|
||||
|
||||
it('shows the cached warning once the first sync fails instead of before it settles', async () => {
|
||||
const elements = createWarningElements();
|
||||
const harness = createHarness({
|
||||
elements,
|
||||
storageValues: {
|
||||
[STORAGE_KEY]: createCachedClock({
|
||||
offsetMs: 12_000,
|
||||
localWallAtSyncMs: SERVER_BASE_MS - 60 * 60 * 1000,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
harness.queueSamples([20, 40, 60].map((rttMs) => ({ reject: true, rttMs })));
|
||||
harness.api.trustedClock.start();
|
||||
const initialSync = harness.api.trustedClock.syncPromise;
|
||||
expect(elements.clockWarning.hidden).toBe(true);
|
||||
|
||||
await harness.completePendingRequests();
|
||||
await expect(initialSync).resolves.toBe(false);
|
||||
|
||||
expect(harness.api.trustedClock.status).toBe('cached');
|
||||
expect(elements.clockWarning.hidden).toBe(false);
|
||||
expect(elements.clockWarning.classList.toggle).toHaveBeenLastCalledWith('show', true);
|
||||
expect(elements.clockWarningText.textContent).not.toBe('');
|
||||
});
|
||||
|
||||
it('shows a stale warning after 24 hours during periodic and offline status refreshes', async () => {
|
||||
const elements = createWarningElements();
|
||||
const harness = createHarness({ elements });
|
||||
|
||||
Reference in New Issue
Block a user