457 lines
11 KiB
TypeScript
457 lines
11 KiB
TypeScript
import { normalizeBackendBaseUrl } from './backendAuth'
|
|
|
|
export interface BackendApiError {
|
|
statusCode: number
|
|
code: string
|
|
message: string
|
|
details?: unknown
|
|
}
|
|
|
|
export interface BackendAuthLoginResult {
|
|
user?: {
|
|
id?: string
|
|
nickname?: string
|
|
avatarUrl?: string
|
|
}
|
|
tokens: {
|
|
accessToken: string
|
|
refreshToken: string
|
|
}
|
|
}
|
|
|
|
export interface BackendResolvedRelease {
|
|
launchMode: string
|
|
source: string
|
|
eventId: string
|
|
releaseId: string
|
|
configLabel: string
|
|
manifestUrl: string
|
|
manifestChecksumSha256?: string | null
|
|
routeCode?: string | null
|
|
}
|
|
|
|
export interface BackendCourseVariantSummary {
|
|
id: string
|
|
name: string
|
|
description?: string | null
|
|
routeCode?: string | null
|
|
selectable?: boolean
|
|
}
|
|
|
|
export interface BackendLaunchVariantSummary {
|
|
id: string
|
|
name: string
|
|
routeCode?: string | null
|
|
assignmentMode?: string | null
|
|
}
|
|
|
|
export interface BackendRuntimeSummary {
|
|
runtimeBindingId?: string | null
|
|
placeId?: string | null
|
|
placeName?: string | null
|
|
mapId?: string | null
|
|
mapName?: string | null
|
|
tileReleaseId?: string | null
|
|
courseSetId?: string | null
|
|
courseVariantId?: string | null
|
|
routeCode?: string | null
|
|
}
|
|
|
|
export interface BackendPresentationSummary {
|
|
presentationId?: string | null
|
|
templateKey?: string | null
|
|
version?: string | null
|
|
}
|
|
|
|
export interface BackendContentBundleSummary {
|
|
bundleId?: string | null
|
|
bundleType?: string | null
|
|
version?: string | null
|
|
}
|
|
|
|
export interface BackendEntrySessionSummary {
|
|
id: string
|
|
status: string
|
|
eventId?: string
|
|
eventName?: string
|
|
releaseId?: string | null
|
|
configLabel?: string | null
|
|
routeCode?: string | null
|
|
variantId?: string | null
|
|
variantName?: string | null
|
|
runtime?: BackendRuntimeSummary | null
|
|
launchedAt?: string | null
|
|
startedAt?: string | null
|
|
endedAt?: string | null
|
|
// 兼容前端旧字段名,避免联调过渡期多处判断
|
|
sessionId?: string
|
|
sessionStatus?: string
|
|
eventDisplayName?: string
|
|
}
|
|
|
|
export interface BackendCardResult {
|
|
id: string
|
|
type: string
|
|
title: string
|
|
subtitle?: string | null
|
|
coverUrl?: string | null
|
|
displaySlot: string
|
|
displayPriority: number
|
|
event?: {
|
|
id: string
|
|
displayName: string
|
|
summary?: string | null
|
|
} | null
|
|
htmlUrl?: string | null
|
|
}
|
|
|
|
export interface BackendEntryHomeResult {
|
|
user: {
|
|
id: string
|
|
publicId: string
|
|
status: string
|
|
nickname?: string | null
|
|
avatarUrl?: string | null
|
|
}
|
|
tenant: {
|
|
id: string
|
|
code: string
|
|
name: string
|
|
}
|
|
channel: {
|
|
id: string
|
|
code: string
|
|
type: string
|
|
platformAppId?: string | null
|
|
displayName: string
|
|
status: string
|
|
isDefault: boolean
|
|
}
|
|
cards: BackendCardResult[]
|
|
ongoingSession?: BackendEntrySessionSummary | null
|
|
recentSession?: BackendEntrySessionSummary | null
|
|
}
|
|
|
|
export interface BackendEventPlayResult {
|
|
event: {
|
|
id: string
|
|
slug: string
|
|
displayName: string
|
|
summary?: string | null
|
|
status: string
|
|
}
|
|
currentPresentation?: BackendPresentationSummary | null
|
|
currentContentBundle?: BackendContentBundleSummary | null
|
|
release?: {
|
|
id: string
|
|
configLabel: string
|
|
manifestUrl: string
|
|
manifestChecksumSha256?: string | null
|
|
routeCode?: string | null
|
|
} | null
|
|
resolvedRelease?: BackendResolvedRelease | null
|
|
play: {
|
|
canLaunch: boolean
|
|
primaryAction: string
|
|
reason: string
|
|
launchSource?: string
|
|
assignmentMode?: string | null
|
|
courseVariants?: BackendCourseVariantSummary[] | null
|
|
ongoingSession?: BackendEntrySessionSummary | null
|
|
recentSession?: BackendEntrySessionSummary | null
|
|
}
|
|
}
|
|
|
|
export interface BackendLaunchResult {
|
|
event: {
|
|
id: string
|
|
displayName: string
|
|
}
|
|
launch: {
|
|
source: string
|
|
resolvedRelease?: BackendResolvedRelease | null
|
|
config: {
|
|
configUrl: string
|
|
configLabel: string
|
|
configChecksumSha256?: string | null
|
|
releaseId: string
|
|
routeCode?: string | null
|
|
}
|
|
business: {
|
|
source: string
|
|
eventId: string
|
|
sessionId: string
|
|
sessionToken: string
|
|
sessionTokenExpiresAt: string
|
|
routeCode?: string | null
|
|
}
|
|
variant?: BackendLaunchVariantSummary | null
|
|
runtime?: BackendRuntimeSummary | null
|
|
presentation?: BackendPresentationSummary | null
|
|
contentBundle?: BackendContentBundleSummary | null
|
|
}
|
|
}
|
|
|
|
export interface BackendSessionFinishSummaryPayload {
|
|
finalDurationSec?: number
|
|
finalScore?: number
|
|
completedControls?: number
|
|
totalControls?: number
|
|
distanceMeters?: number
|
|
averageSpeedKmh?: number
|
|
maxHeartRateBpm?: number
|
|
}
|
|
|
|
export interface BackendSessionResult {
|
|
session: {
|
|
id: string
|
|
status: string
|
|
clientType: string
|
|
deviceKey: string
|
|
routeCode?: string | null
|
|
runtime?: BackendRuntimeSummary | null
|
|
sessionTokenExpiresAt: string
|
|
launchedAt: string
|
|
startedAt?: string | null
|
|
endedAt?: string | null
|
|
}
|
|
event: {
|
|
id: string
|
|
displayName: string
|
|
}
|
|
resolvedRelease?: BackendResolvedRelease | null
|
|
}
|
|
|
|
export interface BackendSessionResultView {
|
|
session: BackendEntrySessionSummary
|
|
result: {
|
|
status: string
|
|
finalDurationSec?: number
|
|
finalScore?: number
|
|
completedControls?: number
|
|
totalControls?: number
|
|
distanceMeters?: number
|
|
averageSpeedKmh?: number
|
|
maxHeartRateBpm?: number
|
|
summary?: Record<string, unknown>
|
|
}
|
|
}
|
|
|
|
export interface BackendClientLogInput {
|
|
source: string
|
|
level: 'debug' | 'info' | 'warn' | 'error'
|
|
category: string
|
|
message: string
|
|
eventId?: string
|
|
releaseId?: string
|
|
sessionId?: string
|
|
manifestUrl?: string
|
|
route?: string
|
|
occurredAt?: string
|
|
details?: Record<string, unknown>
|
|
}
|
|
|
|
type BackendEnvelope<T> = {
|
|
data: T
|
|
}
|
|
|
|
type RequestOptions = {
|
|
method: 'GET' | 'POST'
|
|
baseUrl: string
|
|
path: string
|
|
authToken?: string
|
|
body?: Record<string, unknown>
|
|
}
|
|
|
|
function requestBackend<T>(options: RequestOptions): Promise<T> {
|
|
const url = `${normalizeBackendBaseUrl(options.baseUrl)}${options.path}`
|
|
const header: Record<string, string> = {}
|
|
if (options.body) {
|
|
header['Content-Type'] = 'application/json'
|
|
}
|
|
if (options.authToken) {
|
|
header.Authorization = `Bearer ${options.authToken}`
|
|
}
|
|
|
|
return new Promise<T>((resolve, reject) => {
|
|
wx.request({
|
|
url,
|
|
method: options.method,
|
|
header,
|
|
data: options.body,
|
|
success: (response) => {
|
|
const statusCode = typeof response.statusCode === 'number' ? response.statusCode : 0
|
|
const data = response.data as BackendEnvelope<T> | { error?: { code?: string; message?: string; details?: unknown } }
|
|
if (statusCode >= 200 && statusCode < 300 && data && typeof data === 'object' && 'data' in data) {
|
|
resolve((data as BackendEnvelope<T>).data)
|
|
return
|
|
}
|
|
|
|
const errorPayload = data && typeof data === 'object' && 'error' in data
|
|
? (data as { error?: { code?: string; message?: string; details?: unknown } }).error
|
|
: undefined
|
|
reject({
|
|
statusCode,
|
|
code: errorPayload && errorPayload.code ? errorPayload.code : 'backend_error',
|
|
message: errorPayload && errorPayload.message ? errorPayload.message : `request failed: ${statusCode}`,
|
|
details: errorPayload && errorPayload.details ? errorPayload.details : response.data,
|
|
} as BackendApiError)
|
|
},
|
|
fail: (error) => {
|
|
reject({
|
|
statusCode: 0,
|
|
code: 'network_error',
|
|
message: error && error.errMsg ? error.errMsg : 'network request failed',
|
|
} as BackendApiError)
|
|
},
|
|
})
|
|
})
|
|
}
|
|
|
|
export function loginWechatMini(input: {
|
|
baseUrl: string
|
|
code: string
|
|
deviceKey: string
|
|
clientType?: string
|
|
}): Promise<BackendAuthLoginResult> {
|
|
return requestBackend<BackendAuthLoginResult>({
|
|
method: 'POST',
|
|
baseUrl: input.baseUrl,
|
|
path: '/auth/login/wechat-mini',
|
|
body: {
|
|
code: input.code,
|
|
clientType: input.clientType || 'wechat',
|
|
deviceKey: input.deviceKey,
|
|
},
|
|
})
|
|
}
|
|
|
|
export function getEventPlay(input: {
|
|
baseUrl: string
|
|
eventId: string
|
|
accessToken: string
|
|
}): Promise<BackendEventPlayResult> {
|
|
return requestBackend<BackendEventPlayResult>({
|
|
method: 'GET',
|
|
baseUrl: input.baseUrl,
|
|
path: `/events/${encodeURIComponent(input.eventId)}/play`,
|
|
authToken: input.accessToken,
|
|
})
|
|
}
|
|
|
|
export function getEntryHome(input: {
|
|
baseUrl: string
|
|
accessToken: string
|
|
channelCode: string
|
|
channelType: string
|
|
}): Promise<BackendEntryHomeResult> {
|
|
const query = `channelCode=${encodeURIComponent(input.channelCode)}&channelType=${encodeURIComponent(input.channelType)}`
|
|
return requestBackend<BackendEntryHomeResult>({
|
|
method: 'GET',
|
|
baseUrl: input.baseUrl,
|
|
path: `/me/entry-home?${query}`,
|
|
authToken: input.accessToken,
|
|
})
|
|
}
|
|
|
|
export function launchEvent(input: {
|
|
baseUrl: string
|
|
eventId: string
|
|
accessToken: string
|
|
releaseId?: string
|
|
variantId?: string
|
|
clientType: string
|
|
deviceKey: string
|
|
}): Promise<BackendLaunchResult> {
|
|
const body: Record<string, unknown> = {
|
|
clientType: input.clientType,
|
|
deviceKey: input.deviceKey,
|
|
}
|
|
if (input.releaseId) {
|
|
body.releaseId = input.releaseId
|
|
}
|
|
if (input.variantId) {
|
|
body.variantId = input.variantId
|
|
}
|
|
return requestBackend<BackendLaunchResult>({
|
|
method: 'POST',
|
|
baseUrl: input.baseUrl,
|
|
path: `/events/${encodeURIComponent(input.eventId)}/launch`,
|
|
authToken: input.accessToken,
|
|
body,
|
|
})
|
|
}
|
|
|
|
export function startSession(input: {
|
|
baseUrl: string
|
|
sessionId: string
|
|
sessionToken: string
|
|
}): Promise<BackendSessionResult> {
|
|
return requestBackend<BackendSessionResult>({
|
|
method: 'POST',
|
|
baseUrl: input.baseUrl,
|
|
path: `/sessions/${encodeURIComponent(input.sessionId)}/start`,
|
|
body: {
|
|
sessionToken: input.sessionToken,
|
|
},
|
|
})
|
|
}
|
|
|
|
export function finishSession(input: {
|
|
baseUrl: string
|
|
sessionId: string
|
|
sessionToken: string
|
|
status: 'finished' | 'failed' | 'cancelled'
|
|
summary: BackendSessionFinishSummaryPayload
|
|
}): Promise<BackendSessionResult> {
|
|
return requestBackend<BackendSessionResult>({
|
|
method: 'POST',
|
|
baseUrl: input.baseUrl,
|
|
path: `/sessions/${encodeURIComponent(input.sessionId)}/finish`,
|
|
body: {
|
|
sessionToken: input.sessionToken,
|
|
status: input.status,
|
|
summary: input.summary,
|
|
},
|
|
})
|
|
}
|
|
|
|
export function getSessionResult(input: {
|
|
baseUrl: string
|
|
accessToken: string
|
|
sessionId: string
|
|
}): Promise<BackendSessionResultView> {
|
|
return requestBackend<BackendSessionResultView>({
|
|
method: 'GET',
|
|
baseUrl: input.baseUrl,
|
|
path: `/sessions/${encodeURIComponent(input.sessionId)}/result`,
|
|
authToken: input.accessToken,
|
|
})
|
|
}
|
|
|
|
export function getMyResults(input: {
|
|
baseUrl: string
|
|
accessToken: string
|
|
limit?: number
|
|
}): Promise<BackendSessionResultView[]> {
|
|
const limit = typeof input.limit === 'number' ? input.limit : 20
|
|
return requestBackend<BackendSessionResultView[]>({
|
|
method: 'GET',
|
|
baseUrl: input.baseUrl,
|
|
path: `/me/results?limit=${encodeURIComponent(String(limit))}`,
|
|
authToken: input.accessToken,
|
|
})
|
|
}
|
|
|
|
export function postClientLog(input: {
|
|
baseUrl: string
|
|
payload: BackendClientLogInput
|
|
}): Promise<void> {
|
|
return requestBackend<void>({
|
|
method: 'POST',
|
|
baseUrl: input.baseUrl,
|
|
path: '/dev/client-logs',
|
|
body: input.payload as unknown as Record<string, unknown>,
|
|
})
|
|
}
|