103 lines
2.9 KiB
TypeScript
103 lines
2.9 KiB
TypeScript
import { app, net } from 'electron'
|
||
import type {
|
||
LicenseSession,
|
||
VersionInfo,
|
||
} from '../../src/shared/types'
|
||
import { buildDesktopRequestHeaders } from './server-api-headers'
|
||
|
||
interface ApiResult<T> {
|
||
code: string
|
||
msg: string
|
||
data: T
|
||
}
|
||
|
||
export interface DevicePayload {
|
||
deviceId: string
|
||
deviceName: string
|
||
osName: string
|
||
osVersion: string
|
||
appVersion: string
|
||
}
|
||
|
||
export class ServerApiError extends Error {
|
||
constructor(
|
||
public readonly code: string,
|
||
message: string,
|
||
) {
|
||
super(message)
|
||
this.name = 'ServerApiError'
|
||
}
|
||
}
|
||
|
||
const DEFAULT_API_BASE_URL = 'https://apimall.v2.jxxyk.com/api'
|
||
|
||
export class DesktopServerApi {
|
||
private readonly baseUrl: string
|
||
|
||
constructor(baseUrl = process.env.IMAGE_TOOL_API_BASE_URL || DEFAULT_API_BASE_URL) {
|
||
this.baseUrl = this.validateBaseUrl(baseUrl)
|
||
}
|
||
|
||
activate(activationCode: string, device: DevicePayload): Promise<LicenseSession> {
|
||
return this.post<LicenseSession>('/desktop/activate', {
|
||
activationCode,
|
||
...device,
|
||
})
|
||
}
|
||
|
||
login(token: string, device: DevicePayload): Promise<LicenseSession> {
|
||
return this.post<LicenseSession>('/desktop/login', {
|
||
token,
|
||
...device,
|
||
})
|
||
}
|
||
|
||
checkVersion(currentVersion: string): Promise<VersionInfo> {
|
||
return this.post<VersionInfo>('/desktop/version/check', {
|
||
currentVersion,
|
||
platform: 'windows',
|
||
})
|
||
}
|
||
|
||
private async post<T>(pathname: string, body: unknown): Promise<T> {
|
||
const controller = new AbortController()
|
||
const timeout = setTimeout(() => controller.abort(), 15_000)
|
||
try {
|
||
const response = await net.fetch(`${this.baseUrl}${pathname}`, {
|
||
method: 'POST',
|
||
headers: buildDesktopRequestHeaders(app.getVersion()),
|
||
body: JSON.stringify(body),
|
||
signal: controller.signal,
|
||
})
|
||
if (!response.ok) {
|
||
throw new ServerApiError(String(response.status), `授权服务暂时不可用(${response.status})`)
|
||
}
|
||
const result = (await response.json()) as ApiResult<T>
|
||
if (result.code !== '0') {
|
||
throw new ServerApiError(result.code, result.msg || '授权校验失败')
|
||
}
|
||
return result.data
|
||
} catch (error) {
|
||
if (error instanceof ServerApiError) {
|
||
throw error
|
||
}
|
||
if (error instanceof Error && error.name === 'AbortError') {
|
||
throw new ServerApiError('NETWORK_TIMEOUT', '连接授权服务超时,请检查网络后重试')
|
||
}
|
||
throw new ServerApiError('NETWORK_ERROR', '无法连接授权服务,请检查网络后重试')
|
||
} finally {
|
||
clearTimeout(timeout)
|
||
}
|
||
}
|
||
|
||
private validateBaseUrl(value: string): string {
|
||
const url = new URL(value.trim())
|
||
const localDevelopment = url.protocol === 'http:'
|
||
&& ['127.0.0.1', 'localhost'].includes(url.hostname)
|
||
if (url.protocol !== 'https:' && !(localDevelopment && !app.isPackaged)) {
|
||
throw new Error('生产授权服务必须使用 HTTPS')
|
||
}
|
||
return url.toString().replace(/\/$/, '')
|
||
}
|
||
}
|