| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- /**
- * 更新发布工具 - 认证与服务器连接模块
- *
- * Copyright © 2025 imengyu.top imengyu-update-server
- */
- import { readFile, writeFile } from 'node:fs/promises';
- import path from 'node:path';
- import { dirname } from 'node:path';
- import { fileURLToPath } from 'node:url';
- import axios from 'axios';
- import md5 from 'md5';
- import { password, input } from '@inquirer/prompts';
- import { config } from './postConfig.mjs';
- import consola from 'consola';
- import { logError } from './utils.mjs';
- // 基础配置
- // ========================================
- const __filename = fileURLToPath(import.meta.url);
- const __dirname = dirname(__filename);
- export const constant = {
- ServerUrl: config.server,
- TokenSave: path.resolve(__dirname, './_token.json'),
- };
- let currentData = {
- token: '',
- refreshToken: '',
- identifier: '',
- };
- function initIdentifier() {
- if (!currentData.identifier)
- currentData.identifier = `commandClient${Math.floor(Math.random() * 1000)}`;
- }
- function getErrorMessage(e) {
- return e instanceof Error ? e.message : (typeof e === 'object' ? e : '' + e);
- }
- async function saveCurrentData() {
- await writeFile(constant.TokenSave, JSON.stringify(currentData));
- }
- // Axios 实例
- // ========================================
- export const axiosInstance = axios.create({
- baseURL: constant.ServerUrl,
- timeoutErrorMessage: '请求超时,请检查网络连接',
- responseType: 'json',
- withCredentials: false,
- validateStatus: () => true,
- });
- axiosInstance.interceptors.request.use((value) => {
- value.headers['authorization'] = JSON.stringify({
- auth: currentData?.token?.authName,
- validity: currentData?.token?.authKey,
- nonce: 'aaaaaaaaaa',
- identifier: currentData.identifier,
- key: 'abc123',
- });
- value.url =
- value.url +
- (value.url.includes('?') ? '&' : '?') +
- `identifier=${currentData.identifier}`;
- return value;
- });
- let refreshPromise = null;
- async function tryRefreshToken() {
- if (!currentData.refreshToken) return false;
- if (refreshPromise) return refreshPromise;
- refreshPromise = (async () => {
- try {
- const res = await axios.post(
- `${constant.ServerUrl}auth/refresh?identifier=${currentData.identifier}`,
- { refreshToken: currentData.refreshToken },
- { responseType: 'json', validateStatus: () => true }
- );
- if (res.data && res.data.success) {
- currentData.token = {
- authName: res.data.data.authName,
- authKey: res.data.data.authKey,
- };
- if (res.data.data.refreshToken)
- currentData.refreshToken = res.data.data.refreshToken;
- await saveCurrentData();
- return true;
- } else
- return false;
- } catch {
- // 刷新失败
- }
- return false;
- })();
- try {
- return await refreshPromise;
- } finally {
- refreshPromise = null;
- }
- }
- axiosInstance.interceptors.response.use((value) => {
- if (value.data.success)
- return value.data;
- const err = value.data;
- err.__config = value.config;
- return Promise.reject(err);
- }, (error) => {
- return Promise.reject(error);
- });
- axiosInstance.interceptors.response.use(null, async (error) => {
- const code = error?.code;
- if (
- (code === 40102 || code === 40103) &&
- currentData.refreshToken &&
- !error.__retried
- ) {
- const refreshed = await tryRefreshToken();
- if (refreshed) {
- const cfg = error.__config || error.config;
- if (cfg) {
- error.__retried = true;
- return axiosInstance.request(cfg);
- }
- }
- }
- return Promise.reject(error);
- });
- // 认证初始化
- // ========================================
- /**
- * 初始化认证模块,从本地加载 token,并检查登录状态
- * @returns {Promise<void>}
- */
- export async function initAuth() {
- try {
- const res = await readFile(constant.TokenSave);
- const parsed = JSON.parse(res);
- if (parsed && typeof parsed === 'object') {
- currentData = { ...currentData, ...parsed };
- }
- } catch {
- // 无 token 文件时使用默认值
- }
- initIdentifier();
- if (currentData.token) {
- try {
- const res = await axiosInstance.get('/user/user-info');
- if (res?.data) {
- consola.info(`当前登录用户: ${res.data.name || res.data.user} (ID: ${res.data.id})`);
- return;
- }
- } catch {
- // 登录已过期,尝试刷新 token
- }
- if (currentData.refreshToken) {
- consola.info('尝试刷新 token');
- try {
- const res = await tryRefreshToken();
- if (res) {
- consola.success('刷新登录成功');
- return;
- }
- } catch (e) {
- logError('刷新 token 失败', e);
- }
- }
- }
- consola.warn('未登录,进入登录界面');
- await interactiveLogin();
- }
- /**
- * 获取当前认证数据(只读)
- */
- export function getCurrentData() {
- return { ...currentData };
- }
- // 登录相关
- // ========================================
- /**
- * 执行登录请求并保存 token
- * @param {string} user 用户名
- * @param {string} pass 密码
- */
- async function doLogin(user, pass) {
- const res = await axiosInstance.post('/auth?rember=true', {
- method: 'key',
- key: `${user}@${md5(pass)}`,
- });
- currentData.token = {
- authName: res.data.authName,
- authKey: res.data.authKey,
- };
- if (res.data.refreshToken)
- currentData.refreshToken = res.data.refreshToken;
- await saveCurrentData();
- }
- /**
- * 交互式登录,循环直至登录成功或用户取消
- */
- async function interactiveLogin() {
- while (true) {
- try {
- const user = await input({ message: '输入用户名' });
- const pass = await password({ message: '输入密码' });
- await doLogin(user, pass);
- consola.success('登录成功');
- return;
- } catch (e) {
- if (e?.name === 'ExitPromptError' || e?.name === 'CancelPromptError')
- return;
- logError('登录失败', getErrorMessage(e));
- }
- }
- }
- /**
- * 检查登录状态
- */
- export async function checkLogged() {
- try {
- await axiosInstance.get('/auth');
- consola.success('已登录');
- } catch (e) {
- logError('获取状态失败:', getErrorMessage(e));
- }
- }
- /**
- * 登录
- * @param {string} user 用户名
- */
- export async function login(user) {
- try {
- const username = user || await input({ message: '输入用户名' });
- const pass = await password({ message: '输入密码' });
- await doLogin(username, pass);
- consola.success('登录成功');
- } catch (e) {
- logError('登录失败', getErrorMessage(e));
- }
- }
- /**
- * 退出登录
- */
- export async function logout() {
- currentData.token = '';
- currentData.refreshToken = '';
- await saveCurrentData();
- try {
- await axiosInstance.delete('/auth');
- consola.success('退出登录成功');
- } catch (e) {
- logError('退出登录失败', getErrorMessage(e));
- }
- }
|