feat: build licensed desktop image processor
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
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(/\/$/, '')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user