From fde7e95fb4ef0e82188d575c54a22b127722a7f6 Mon Sep 17 00:00:00 2001 From: qiin <414382190@qq.com> Date: Sun, 14 Jun 2026 14:31:11 +0800 Subject: [PATCH] Refactor GitHub unlock resume flow --- entry/src/main/ets/pages/SettingsPageV2.ets | 239 ++++++++---------- .../ets/service/DeveloperUnlockService.ets | 215 ++++++++++++++++ 2 files changed, 315 insertions(+), 139 deletions(-) create mode 100644 entry/src/main/ets/service/DeveloperUnlockService.ets diff --git a/entry/src/main/ets/pages/SettingsPageV2.ets b/entry/src/main/ets/pages/SettingsPageV2.ets index ef4dd3a..4778fcf 100644 --- a/entry/src/main/ets/pages/SettingsPageV2.ets +++ b/entry/src/main/ets/pages/SettingsPageV2.ets @@ -29,13 +29,8 @@ import { loadChangelog, ChangelogEntry, ChangeItem, getChangeTypeConfig } from ' import { InlineSegmentPicker } from '../components/InlineSegmentPicker'; import { isAihdrSupported } from '../components/AihdrModifier'; import { DevKeyVerifier } from '../utils/DevKeyVerifier'; -import { - GitHubDeviceCode, - GitHubStarCheck, - GitHubStarVerifier, - GitHubTokenPollResult, - GitHubTokenPollStatus -} from '../utils/GitHubStarVerifier'; +import { DeveloperUnlockResult, DeveloperUnlockService, DeveloperUnlockStatus } from '../service/DeveloperUnlockService'; +import { GitHubDeviceCode } from '../utils/GitHubStarVerifier'; import { ToastQueue } from '../utils/ToastQueue'; /** @@ -265,8 +260,8 @@ struct SettingsPageV2 { @State developerMode: boolean = false; // 开发者模式 @State developerUnlockBusy: boolean = false; private devKeyActivated: boolean = false; // 激活码已验证过 - private developerPendingDeviceCode: GitHubDeviceCode | null = null; - private developerLastPollMs: number = 0; + private developerPendingDialogVisible: boolean = false; + private developerPendingResumeBusy: boolean = false; // 应用版本 @State appVersion: string = ''; @@ -351,6 +346,12 @@ struct SettingsPageV2 { this.loadDecoderCapabilities(); } + onPageShow(): void { + setTimeout(() => { + this.resumeDeveloperPendingUnlock(); + }, 250); + } + onBackPress(): boolean { router.back(); return true; @@ -605,7 +606,7 @@ struct SettingsPageV2 { this.resumeStream = await this.loadBoolean(SettingsKeys.RESUME_STREAM, false); this.developerMode = await this.loadBoolean(SettingsKeys.DEVELOPER_MODE, false); this.devKeyActivated = await this.loadBoolean(SettingsKeys.DEV_KEY_ACTIVATED, false); - await this.restoreDeveloperPendingDeviceCode(); + await DeveloperUnlockService.restorePendingDeviceCode(); // 备份 this.autoBackupEnabled = await SettingsBackupService.isAutoBackupEnabled(); @@ -613,6 +614,9 @@ struct SettingsPageV2 { console.error('Failed to load settings:', error); } finally { this.isLoading = false; + setTimeout(() => { + this.resumeDeveloperPendingUnlock(); + }, 250); } } @@ -3715,10 +3719,10 @@ struct SettingsPageV2 { return '已开启'; } if (this.devKeyActivated) { - return GitHubStarVerifier.isConfigured() ? '已验证 Star,点击开启' : '已授权,点击开启'; + return DeveloperUnlockService.isGitHubConfigured() ? '已验证 Star,点击开启' : '已授权,点击开启'; } - if (GitHubStarVerifier.isConfigured()) { - return `验证 ${GitHubStarVerifier.projectFullName()} Star 后开启`; + if (DeveloperUnlockService.isGitHubConfigured()) { + return `验证 ${DeveloperUnlockService.projectFullName()} Star 后开启`; } return '需要激活码'; } @@ -3741,7 +3745,7 @@ struct SettingsPageV2 { return; } - if (GitHubStarVerifier.isConfigured()) { + if (DeveloperUnlockService.isGitHubConfigured()) { this.showDeveloperUnlockDialog(); } else { this.developerMode = true; @@ -3750,17 +3754,19 @@ struct SettingsPageV2 { } private async showDeveloperUnlockDialog(): Promise { - const pending = this.developerPendingDeviceCode ?? await this.loadDeveloperPendingDeviceCode(); + const pending = await DeveloperUnlockService.restorePendingDeviceCode(); if (pending) { - this.developerPendingDeviceCode = pending; - this.showDeveloperDeviceCodeDialog(pending); - this.pollDeveloperPendingDeviceCode(false, true); + const result = await this.pollDeveloperPendingDeviceCode(false, true); + if (result.status === DeveloperUnlockStatus.PENDING && result.deviceCode && + !this.devKeyActivated && !this.developerMode) { + this.showDeveloperDeviceCodeDialog(result.deviceCode); + } return; } promptAction.showDialog({ title: '激活开发者模式', - message: `开发者功能面向项目支持者开放。登录 GitHub 后,应用会验证当前账号是否 Star 了 ${GitHubStarVerifier.projectFullName()}。\n\n说明:该方式依赖 GitHub 第三方服务;若市场审核不允许外部平台互动解锁功能,可继续保留激活码方式用于市场包。`, + message: `开发者功能面向项目支持者开放。登录 GitHub 后,应用会验证当前账号是否 Star 了 ${DeveloperUnlockService.projectFullName()}。\n\n说明:该验证会跳转 GitHub,并仅用于确认 Star 状态。`, buttons: [ { text: '验证 Star', color: AppColors.Primary }, { text: '打开项目', color: AppColors.TextSecondary }, @@ -3770,7 +3776,7 @@ struct SettingsPageV2 { if (result.index === 0) { this.startDeveloperUnlockVerification(); } else if (result.index === 1) { - this.openUrl(GitHubStarVerifier.projectUrl()); + this.openUrl(DeveloperUnlockService.projectUrl()); } else if (result.index === 2) { this.developerMode = true; this.showDevKeyActivation(); @@ -3787,193 +3793,148 @@ struct SettingsPageV2 { await this.requestDeveloperDeviceCode(); } + private async resumeDeveloperPendingUnlock(): Promise { + if (this.isLoading || !DeveloperUnlockService.isGitHubConfigured() || this.developerMode || this.devKeyActivated || + this.developerUnlockBusy || this.developerPendingResumeBusy || this.developerPendingDialogVisible) { + return; + } + + this.developerPendingResumeBusy = true; + try { + const pending = await DeveloperUnlockService.restorePendingDeviceCode(); + if (!pending) { + return; + } + + const result = await this.pollDeveloperPendingDeviceCode(false, true); + if (result.status === DeveloperUnlockStatus.PENDING && result.deviceCode && + !this.devKeyActivated && !this.developerMode) { + this.showDeveloperDeviceCodeDialog(result.deviceCode); + } + } finally { + this.developerPendingResumeBusy = false; + } + } + private async requestDeveloperDeviceCode(): Promise { this.developerUnlockBusy = true; ToastQueue.show({ message: '正在发起 GitHub 授权...', duration: 2000 }); try { - const deviceCode = await GitHubStarVerifier.requestDeviceCode(); - this.developerPendingDeviceCode = deviceCode; - await this.saveDeveloperPendingDeviceCode(deviceCode); - this.showDeveloperDeviceCodeDialog(deviceCode); - } catch (err) { - const message = (err as Error).message ?? String(err); - ToastQueue.show({ message: `GitHub 授权失败: ${message}`, duration: 3000 }); + const result = await DeveloperUnlockService.requestDeviceCode(); + await this.applyDeveloperUnlockResult(result, true); } finally { this.developerUnlockBusy = false; } } private showDeveloperDeviceCodeDialog(deviceCode: GitHubDeviceCode): void { + if (this.developerPendingDialogVisible) { + return; + } + + this.developerPendingDialogVisible = true; promptAction.showDialog({ title: 'GitHub Star 验证', - message: `1. 打开授权页并输入代码:${deviceCode.userCode}\n2. 确认已 Star ${GitHubStarVerifier.projectFullName()}\n3. 回到这里点击“我已授权”\n\n授权页:${deviceCode.verificationUri}`, + message: `1. 打开授权页并输入代码:${deviceCode.userCode}\n2. 确认已 Star ${DeveloperUnlockService.projectFullName()}\n3. 回到这里点击“我已授权”\n\n授权页:${deviceCode.verificationUri}`, buttons: [ { text: '打开授权页', color: AppColors.Primary }, { text: '我已授权', color: AppColors.Primary }, { text: '打开项目', color: AppColors.TextSecondary } ] }).then((result) => { + this.developerPendingDialogVisible = false; if (result.index === 0) { this.openUrl(deviceCode.verificationUriComplete ?? deviceCode.verificationUri); } else if (result.index === 1) { this.pollDeveloperPendingDeviceCode(true, false); } else if (result.index === 2) { - this.openUrl(GitHubStarVerifier.projectUrl()); + this.openUrl(DeveloperUnlockService.projectUrl()); } + }).catch((err: Error) => { + this.developerPendingDialogVisible = false; + console.warn(`DeveloperUnlockService: 授权弹窗已关闭: ${err}`); }); } - private async pollDeveloperPendingDeviceCode(showPendingToast: boolean, enforceThrottle: boolean): Promise { - const deviceCode = this.developerPendingDeviceCode ?? await this.loadDeveloperPendingDeviceCode(); - if (!deviceCode) { - if (showPendingToast) { - ToastQueue.show({ message: 'GitHub 授权已过期,请重新开始', duration: 2500 }); - } - return; - } - - const nowMs = Date.now(); - if (enforceThrottle && nowMs - this.developerLastPollMs < 1500) { - return; - } - this.developerLastPollMs = nowMs; + private async pollDeveloperPendingDeviceCode( + showPendingToast: boolean, + enforceThrottle: boolean + ): Promise { this.developerUnlockBusy = true; - try { - const poll = await GitHubStarVerifier.pollAccessToken(deviceCode); - await this.handleDeveloperTokenPollResult(poll, deviceCode, showPendingToast); - } catch (err) { - const message = (err as Error).message ?? String(err); - await this.failDeveloperUnlockVerification(message); + const result = await DeveloperUnlockService.pollPendingDeviceCode(enforceThrottle); + await this.applyDeveloperUnlockResult(result, showPendingToast); + return result; } finally { this.developerUnlockBusy = false; } } - private async handleDeveloperTokenPollResult( - poll: GitHubTokenPollResult, - deviceCode: GitHubDeviceCode, + private async applyDeveloperUnlockResult( + result: DeveloperUnlockResult, showPendingToast: boolean ): Promise { - switch (poll.status) { - case GitHubTokenPollStatus.AUTHORIZED: - if (!poll.accessToken) { - await this.failDeveloperUnlockVerification('GitHub 未返回 access token'); - return; + switch (result.status) { + case DeveloperUnlockStatus.REQUESTED: + if (result.deviceCode) { + this.showDeveloperDeviceCodeDialog(result.deviceCode); } - const starCheck = await GitHubStarVerifier.checkStar(poll.accessToken); - await this.completeDeveloperUnlockVerification(starCheck); break; - case GitHubTokenPollStatus.PENDING: - case GitHubTokenPollStatus.SLOW_DOWN: - if (poll.intervalSeconds) { - deviceCode.intervalSeconds = poll.intervalSeconds; - await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_INTERVAL, deviceCode.intervalSeconds); - } + case DeveloperUnlockStatus.PENDING: if (showPendingToast) { ToastQueue.show({ message: '仍在等待 GitHub 授权,请稍后再试', duration: 2500 }); } break; - case GitHubTokenPollStatus.FAILED: - await this.failDeveloperUnlockVerification(poll.message ?? 'GitHub 授权失败'); + case DeveloperUnlockStatus.VERIFIED: + await this.activateDeveloperMode(result.login); + break; + case DeveloperUnlockStatus.NOT_STARRED: + await this.showDeveloperNotStarredDialog(); + break; + case DeveloperUnlockStatus.EXPIRED: + if (showPendingToast) { + ToastQueue.show({ message: result.message ?? 'GitHub 授权已过期,请重新开始', duration: 2500 }); + } + break; + case DeveloperUnlockStatus.FAILED: + await this.failDeveloperUnlockVerification(result.message ?? 'GitHub 授权失败'); break; } } - private async completeDeveloperUnlockVerification(starCheck: GitHubStarCheck): Promise { - await this.clearDeveloperPendingDeviceCode(); - await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_LOGIN, starCheck.login ?? ''); - - if (starCheck.starred) { - this.developerMode = true; - this.devKeyActivated = true; - await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_VERIFIED_AT, Date.now()); - await this.saveSetting(SettingsKeys.DEVELOPER_MODE, true); - await this.saveSetting(SettingsKeys.DEV_KEY_ACTIVATED, true); - const suffix = starCheck.login ? `,${starCheck.login}` : ''; - ToastQueue.show({ message: `开发者模式已激活${suffix}`, duration: 2500 }); - return; - } + private async activateDeveloperMode(login?: string): Promise { + this.developerMode = true; + this.devKeyActivated = true; + await this.saveSetting(SettingsKeys.DEVELOPER_MODE, true); + await this.saveSetting(SettingsKeys.DEV_KEY_ACTIVATED, true); + const suffix = login ? `,${login}` : ''; + ToastQueue.show({ message: `开发者模式已激活${suffix}`, duration: 2500 }); + } + private async showDeveloperNotStarredDialog(): Promise { this.developerMode = false; this.devKeyActivated = false; await this.saveSetting(SettingsKeys.DEVELOPER_MODE, false); await this.saveSetting(SettingsKeys.DEV_KEY_ACTIVATED, false); promptAction.showDialog({ title: '未检测到 Star', - message: `GitHub 授权成功,但当前账号尚未 Star ${GitHubStarVerifier.projectFullName()}。如果刚刚 Star,请稍等片刻后重新验证。`, + message: `GitHub 授权成功,但当前账号尚未 Star ${DeveloperUnlockService.projectFullName()}。如果刚刚 Star,请稍等片刻后重新验证。`, buttons: [ { text: '打开项目', color: AppColors.Primary }, { text: '取消', color: AppColors.TextSecondary } ] }).then((result) => { if (result.index === 0) { - this.openUrl(GitHubStarVerifier.projectUrl()); + this.openUrl(DeveloperUnlockService.projectUrl()); } }); } private async failDeveloperUnlockVerification(message: string): Promise { - await this.clearDeveloperPendingDeviceCode(); this.developerMode = false; ToastQueue.show({ message: `开发者模式验证失败: ${message}`, duration: 3000 }); } - private async saveDeveloperPendingDeviceCode(deviceCode: GitHubDeviceCode): Promise { - const expiresAt = Date.now() + deviceCode.expiresInSeconds * 1000; - await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_DEVICE_CODE, deviceCode.deviceCode); - await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_USER_CODE, deviceCode.userCode); - await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_VERIFICATION_URI, deviceCode.verificationUri); - await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_VERIFICATION_URI_COMPLETE, deviceCode.verificationUriComplete ?? ''); - await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_EXPIRES_AT, expiresAt); - await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_INTERVAL, deviceCode.intervalSeconds); - } - - private async loadDeveloperPendingDeviceCode(): Promise { - const expiresAt = await PreferencesUtil.get(SettingsKeys.DEV_GITHUB_PENDING_EXPIRES_AT, 0); - const remainingSeconds = Math.floor((expiresAt - Date.now()) / 1000); - if (remainingSeconds <= 0) { - await this.clearDeveloperPendingDeviceCode(); - return null; - } - - const deviceCode = await PreferencesUtil.get(SettingsKeys.DEV_GITHUB_PENDING_DEVICE_CODE, ''); - const userCode = await PreferencesUtil.get(SettingsKeys.DEV_GITHUB_PENDING_USER_CODE, ''); - const verificationUri = await PreferencesUtil.get(SettingsKeys.DEV_GITHUB_PENDING_VERIFICATION_URI, ''); - if (!deviceCode || !userCode || !verificationUri) { - await this.clearDeveloperPendingDeviceCode(); - return null; - } - - const verificationUriComplete = await PreferencesUtil.get( - SettingsKeys.DEV_GITHUB_PENDING_VERIFICATION_URI_COMPLETE, - '' - ); - const intervalSeconds = await PreferencesUtil.get(SettingsKeys.DEV_GITHUB_PENDING_INTERVAL, 5); - return new GitHubDeviceCode( - deviceCode, - userCode, - verificationUri, - verificationUriComplete || undefined, - remainingSeconds, - Math.max(1, intervalSeconds) - ); - } - - private async restoreDeveloperPendingDeviceCode(): Promise { - this.developerPendingDeviceCode = await this.loadDeveloperPendingDeviceCode(); - } - - private async clearDeveloperPendingDeviceCode(): Promise { - this.developerPendingDeviceCode = null; - await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_DEVICE_CODE, ''); - await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_USER_CODE, ''); - await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_VERIFICATION_URI, ''); - await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_VERIFICATION_URI_COMPLETE, ''); - await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_EXPIRES_AT, 0); - await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_INTERVAL, 0); - } - /** * 显示开发者模式激活码输入 */ diff --git a/entry/src/main/ets/service/DeveloperUnlockService.ets b/entry/src/main/ets/service/DeveloperUnlockService.ets new file mode 100644 index 0000000..0dc69b2 --- /dev/null +++ b/entry/src/main/ets/service/DeveloperUnlockService.ets @@ -0,0 +1,215 @@ +/* + * Moonlight for HarmonyOS + * Copyright (C) 2024-2025 Moonlight/AlkaidLab + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +import { SettingsKeys } from './SettingsService'; +import { + GitHubDeviceCode, + GitHubStarVerifier, + GitHubTokenPollResult, + GitHubTokenPollStatus +} from '../utils/GitHubStarVerifier'; +import { PreferencesUtil } from '../utils/PreferencesUtil'; + +export enum DeveloperUnlockStatus { + REQUESTED = 'requested', + PENDING = 'pending', + VERIFIED = 'verified', + NOT_STARRED = 'not_starred', + EXPIRED = 'expired', + FAILED = 'failed' +} + +export class DeveloperUnlockResult { + status: DeveloperUnlockStatus; + deviceCode?: GitHubDeviceCode; + login?: string; + message?: string; + + constructor( + status: DeveloperUnlockStatus, + deviceCode?: GitHubDeviceCode, + login?: string, + message?: string + ) { + this.status = status; + this.deviceCode = deviceCode; + this.login = login; + this.message = message; + } +} + +export class DeveloperUnlockService { + private static pendingDeviceCode: GitHubDeviceCode | null = null; + private static lastPollMs: number = 0; + private static readonly MIN_POLL_INTERVAL_MS = 1500; + + static isGitHubConfigured(): boolean { + return GitHubStarVerifier.isConfigured(); + } + + static projectUrl(): string { + return GitHubStarVerifier.projectUrl(); + } + + static projectFullName(): string { + return GitHubStarVerifier.projectFullName(); + } + + static async requestDeviceCode(): Promise { + try { + const deviceCode = await GitHubStarVerifier.requestDeviceCode(); + DeveloperUnlockService.pendingDeviceCode = deviceCode; + await DeveloperUnlockService.savePendingDeviceCode(deviceCode); + return new DeveloperUnlockResult(DeveloperUnlockStatus.REQUESTED, deviceCode); + } catch (err) { + return DeveloperUnlockService.failedResult(err, 'GitHub 授权失败'); + } + } + + static async pollPendingDeviceCode(enforceThrottle: boolean): Promise { + const deviceCode = DeveloperUnlockService.pendingDeviceCode ?? + await DeveloperUnlockService.loadPendingDeviceCode(); + if (!deviceCode) { + return new DeveloperUnlockResult(DeveloperUnlockStatus.EXPIRED, undefined, undefined, 'GitHub 授权已过期,请重新开始'); + } + + const nowMs = Date.now(); + if (enforceThrottle && nowMs - DeveloperUnlockService.lastPollMs < DeveloperUnlockService.MIN_POLL_INTERVAL_MS) { + return new DeveloperUnlockResult(DeveloperUnlockStatus.PENDING, deviceCode); + } + DeveloperUnlockService.lastPollMs = nowMs; + + try { + const poll = await GitHubStarVerifier.pollAccessToken(deviceCode); + return await DeveloperUnlockService.handleTokenPollResult(poll, deviceCode); + } catch (err) { + await DeveloperUnlockService.clearPendingDeviceCode(); + return DeveloperUnlockService.failedResult(err, 'GitHub 授权失败'); + } + } + + static async restorePendingDeviceCode(): Promise { + DeveloperUnlockService.pendingDeviceCode = await DeveloperUnlockService.loadPendingDeviceCode(); + return DeveloperUnlockService.pendingDeviceCode; + } + + static async loadPendingDeviceCode(): Promise { + const expiresAt = await PreferencesUtil.get(SettingsKeys.DEV_GITHUB_PENDING_EXPIRES_AT, 0); + const remainingSeconds = Math.floor((expiresAt - Date.now()) / 1000); + if (remainingSeconds <= 0) { + await DeveloperUnlockService.clearPendingDeviceCode(); + return null; + } + + const deviceCode = await PreferencesUtil.get(SettingsKeys.DEV_GITHUB_PENDING_DEVICE_CODE, ''); + const userCode = await PreferencesUtil.get(SettingsKeys.DEV_GITHUB_PENDING_USER_CODE, ''); + const verificationUri = await PreferencesUtil.get(SettingsKeys.DEV_GITHUB_PENDING_VERIFICATION_URI, ''); + if (!deviceCode || !userCode || !verificationUri) { + await DeveloperUnlockService.clearPendingDeviceCode(); + return null; + } + + const verificationUriComplete = await PreferencesUtil.get( + SettingsKeys.DEV_GITHUB_PENDING_VERIFICATION_URI_COMPLETE, + '' + ); + const intervalSeconds = await PreferencesUtil.get(SettingsKeys.DEV_GITHUB_PENDING_INTERVAL, 5); + return new GitHubDeviceCode( + deviceCode, + userCode, + verificationUri, + verificationUriComplete || undefined, + remainingSeconds, + Math.max(1, intervalSeconds) + ); + } + + static async clearPendingDeviceCode(): Promise { + DeveloperUnlockService.pendingDeviceCode = null; + await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_DEVICE_CODE, ''); + await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_USER_CODE, ''); + await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_VERIFICATION_URI, ''); + await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_VERIFICATION_URI_COMPLETE, ''); + await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_EXPIRES_AT, 0); + await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_INTERVAL, 0); + } + + private static async handleTokenPollResult( + poll: GitHubTokenPollResult, + deviceCode: GitHubDeviceCode + ): Promise { + switch (poll.status) { + case GitHubTokenPollStatus.AUTHORIZED: + return await DeveloperUnlockService.handleAuthorizedPoll(poll); + case GitHubTokenPollStatus.PENDING: + case GitHubTokenPollStatus.SLOW_DOWN: + return await DeveloperUnlockService.handlePendingPoll(poll, deviceCode); + case GitHubTokenPollStatus.FAILED: + await DeveloperUnlockService.clearPendingDeviceCode(); + return new DeveloperUnlockResult( + DeveloperUnlockStatus.FAILED, + undefined, + undefined, + poll.message ?? 'GitHub 授权失败' + ); + } + await DeveloperUnlockService.clearPendingDeviceCode(); + return new DeveloperUnlockResult(DeveloperUnlockStatus.FAILED, undefined, undefined, 'GitHub 授权失败'); + } + + private static async handleAuthorizedPoll(poll: GitHubTokenPollResult): Promise { + if (!poll.accessToken) { + await DeveloperUnlockService.clearPendingDeviceCode(); + return new DeveloperUnlockResult( + DeveloperUnlockStatus.FAILED, + undefined, + undefined, + 'GitHub 未返回 access token' + ); + } + + const starCheck = await GitHubStarVerifier.checkStar(poll.accessToken); + await DeveloperUnlockService.clearPendingDeviceCode(); + await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_LOGIN, starCheck.login ?? ''); + + if (starCheck.starred) { + await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_VERIFIED_AT, Date.now()); + return new DeveloperUnlockResult(DeveloperUnlockStatus.VERIFIED, undefined, starCheck.login); + } + return new DeveloperUnlockResult(DeveloperUnlockStatus.NOT_STARRED, undefined, starCheck.login); + } + + private static async handlePendingPoll( + poll: GitHubTokenPollResult, + deviceCode: GitHubDeviceCode + ): Promise { + if (poll.intervalSeconds) { + deviceCode.intervalSeconds = poll.intervalSeconds; + await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_INTERVAL, deviceCode.intervalSeconds); + } + DeveloperUnlockService.pendingDeviceCode = deviceCode; + return new DeveloperUnlockResult(DeveloperUnlockStatus.PENDING, deviceCode); + } + + private static async savePendingDeviceCode(deviceCode: GitHubDeviceCode): Promise { + const expiresAt = Date.now() + deviceCode.expiresInSeconds * 1000; + await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_DEVICE_CODE, deviceCode.deviceCode); + await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_USER_CODE, deviceCode.userCode); + await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_VERIFICATION_URI, deviceCode.verificationUri); + await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_VERIFICATION_URI_COMPLETE, deviceCode.verificationUriComplete ?? ''); + await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_EXPIRES_AT, expiresAt); + await PreferencesUtil.put(SettingsKeys.DEV_GITHUB_PENDING_INTERVAL, deviceCode.intervalSeconds); + } + + private static failedResult(err: Error | string, fallback: string): DeveloperUnlockResult { + const message = (err as Error).message ?? String(err) ?? fallback; + return new DeveloperUnlockResult(DeveloperUnlockStatus.FAILED, undefined, undefined, message); + } +}