up
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { test } from 'node:test'
|
||||
import { createEveningDetectiveServerClient } from './api/generated/crabs/evening_detective_server/index.js'
|
||||
import { apiPlan, apiScreenMode, apiScreenCommand } from './api-values.js'
|
||||
import { parsePlan } from './screen-plan.js'
|
||||
import { parseRemote } from './remote-state.js'
|
||||
|
||||
test('клиент proto задаёт маршруты, методы и JSON enum существующего gateway', async () => {
|
||||
const calls: { path: string; method: string; body: string | null }[] = []
|
||||
const client = createEveningDetectiveServerClient(async (request) => {
|
||||
calls.push(request)
|
||||
return {}
|
||||
})
|
||||
await client.Login({ email: 'dev@example.test', password: 'SECRET' })
|
||||
await client.Refresh({ refreshToken: 'SECRET' })
|
||||
await client.GetMe({})
|
||||
await client.GetGames({})
|
||||
await client.GetGame({ ID: 7 })
|
||||
await client.GetScreenPlan({ gameID: 7 })
|
||||
await client.GetGameMusic({ gameID: 7 })
|
||||
const plan = parsePlan({
|
||||
steps: [
|
||||
{ mode: 8, text: 'Улика' },
|
||||
{ mode: 7, timerDurationSeconds: 60 },
|
||||
],
|
||||
})
|
||||
await client.SaveScreenPlan({ gameID: 7, ...apiPlan(plan) })
|
||||
await client.SetScreenMode({ gameID: 7, mode: apiScreenMode(2) })
|
||||
await client.SendScreenCommand({
|
||||
gameID: 7,
|
||||
command: apiScreenCommand(6),
|
||||
positionMs: 1500,
|
||||
volume: 50,
|
||||
})
|
||||
assert.deepEqual(
|
||||
calls.map((c) => [c.method, c.path]),
|
||||
[
|
||||
['POST', 'api/auth/login'],
|
||||
['POST', 'api/auth/refresh'],
|
||||
['GET', 'api/users/me'],
|
||||
['GET', 'api/games'],
|
||||
['GET', 'api/games/7'],
|
||||
['GET', 'api/games/7/screen-plan'],
|
||||
['GET', 'api/games/7/music'],
|
||||
['PUT', 'api/games/7/screen-plan'],
|
||||
['POST', 'api/games/7/screens/mode'],
|
||||
['POST', 'api/games/7/screens/command'],
|
||||
],
|
||||
)
|
||||
assert.deepEqual(parsePlan(JSON.parse(calls[7]!.body!)), plan)
|
||||
assert.equal(JSON.parse(calls[7]!.body!).steps[0].mode, 'SCREEN_MODE_TEXT')
|
||||
assert.equal(JSON.parse(calls[8]!.body!).mode, 'SCREEN_MODE_INTRO')
|
||||
assert.equal(JSON.parse(calls[9]!.body!).command, 'SCREEN_COMMAND_SEEK')
|
||||
assert.throws(() => apiScreenMode(99), /режим/)
|
||||
assert.throws(() => apiScreenCommand(99), /команда/)
|
||||
})
|
||||
|
||||
test('ответ proto сохраняет строковый int64, enum и отсутствующие поля для проверки границы', async () => {
|
||||
const client = createEveningDetectiveServerClient(async () =>
|
||||
JSON.parse(
|
||||
'{"state":{"stateVersion":"9007199254740993","mode":"SCREEN_MODE_TEXT","text":"Текст","positionMs":"1500"}}',
|
||||
),
|
||||
)
|
||||
const next = parseRemote(
|
||||
(await client.GetScreenState({ screenID: '12', accessToken: 'SECRET' })).state,
|
||||
)
|
||||
assert.equal(next.version, 9007199254740993n)
|
||||
assert.equal(next.mode, 8)
|
||||
assert.equal(next.position, 1.5)
|
||||
assert.equal(next.music, null)
|
||||
const invalid = createEveningDetectiveServerClient(async () => ({
|
||||
state: { stateVersion: 9007199254740992 },
|
||||
}))
|
||||
assert.throws(() => parseRemote({ stateVersion: 9007199254740992 }), /версия/)
|
||||
await assert.rejects(
|
||||
async () =>
|
||||
parseRemote((await invalid.GetScreenState({ screenID: '12', accessToken: 'SECRET' })).state),
|
||||
/версия/,
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,44 @@
|
||||
import type {
|
||||
ScreenCommand,
|
||||
ScreenMode,
|
||||
SaveScreenPlanReq,
|
||||
} from './api/generated/crabs/evening_detective_server/index.js'
|
||||
import type { ScreenPlan } from '../src/types/screen.js'
|
||||
const modes = [
|
||||
'SCREEN_MODE_UNSPECIFIED',
|
||||
'SCREEN_MODE_COVER',
|
||||
'SCREEN_MODE_INTRO',
|
||||
'SCREEN_MODE_INTRO_DONE',
|
||||
'SCREEN_MODE_CONCLUSION',
|
||||
'SCREEN_MODE_CONCLUSION_DONE',
|
||||
'SCREEN_MODE_RESULTS',
|
||||
'SCREEN_MODE_TIMER',
|
||||
'SCREEN_MODE_TEXT',
|
||||
] as const satisfies readonly ScreenMode[]
|
||||
const commands = [
|
||||
'SCREEN_COMMAND_UNSPECIFIED',
|
||||
'SCREEN_COMMAND_PLAY',
|
||||
'SCREEN_COMMAND_PAUSE',
|
||||
'SCREEN_COMMAND_RESUME',
|
||||
'SCREEN_COMMAND_STOP',
|
||||
'SCREEN_COMMAND_REPLAY',
|
||||
'SCREEN_COMMAND_SEEK',
|
||||
'SCREEN_COMMAND_VOLUME',
|
||||
'SCREEN_COMMAND_TIMER_START',
|
||||
'SCREEN_COMMAND_TIMER_PAUSE',
|
||||
'SCREEN_COMMAND_TIMER_RESUME',
|
||||
'SCREEN_COMMAND_TIMER_RESET',
|
||||
] as const satisfies readonly ScreenCommand[]
|
||||
export function apiScreenMode(mode: number): ScreenMode {
|
||||
const value = modes[mode]
|
||||
if (!value) throw new Error('Некорректный режим экрана')
|
||||
return value
|
||||
}
|
||||
export function apiScreenCommand(command: number): ScreenCommand {
|
||||
const value = commands[command]
|
||||
if (!value) throw new Error('Некорректная команда экрана')
|
||||
return value
|
||||
}
|
||||
export function apiPlan(plan: ScreenPlan): Pick<SaveScreenPlanReq, 'steps' | 'currentStep'> {
|
||||
return { ...plan, steps: plan.steps.map((step) => ({ ...step, mode: apiScreenMode(step.mode) })) }
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -188,9 +188,9 @@ async function fixture(t: TestContext, secure = true) {
|
||||
if (init?.method === 'POST') {
|
||||
const body = JSON.parse(String(init.body)) as Record<string, unknown>
|
||||
const state = objectMusic(20, Number(body.trackIndex ?? 0))
|
||||
state.playback = Number(body.playback ?? 1)
|
||||
model.remote.music = {
|
||||
...state,
|
||||
playback: body.playback,
|
||||
volume: body.volume,
|
||||
loop: body.loop,
|
||||
positionMs: body.positionMs,
|
||||
@@ -1160,12 +1160,12 @@ test('локальные режим, музыка и таймер публику
|
||||
f.controller.output(true)
|
||||
await f.online.poll()
|
||||
await f.online.planCommand({ action: 'mode', mode: 3 })
|
||||
assert.equal(f.model.remote.mode, 3)
|
||||
assert.equal(f.model.remote.mode, 'SCREEN_MODE_INTRO_DONE')
|
||||
await f.online.planCommand({ action: 'timer-set', label: 'Отдых', durationSeconds: 60 })
|
||||
assert.equal(f.model.remote.timerDurationSeconds, 60)
|
||||
await f.online.planCommand({ action: 'play', channel: 'music' })
|
||||
const music = f.model.remote.music as { playback: number }
|
||||
assert.equal(music.playback, 2)
|
||||
const music = f.model.remote.music as { playback: string }
|
||||
assert.equal(music.playback, 'MUSIC_PLAYBACK_PLAYING')
|
||||
const operation = f.controller.state.channels.music.operation
|
||||
await f.online.poll()
|
||||
assert.equal(f.controller.state.channels.music.operation, operation)
|
||||
|
||||
+103
-144
@@ -1,3 +1,5 @@
|
||||
import { createEveningDetectiveServerClient } from './api/generated/crabs/evening_detective_server/index.js'
|
||||
import { apiScreenMode, apiScreenCommand, apiPlan } from './api-values.js'
|
||||
import { ApiError, NetworkError, SessionChangedError, networkFailure } from './request-error.js'
|
||||
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
@@ -449,12 +451,9 @@ export class Online {
|
||||
const version = this.planVersion
|
||||
this.lastPlan = Date.now()
|
||||
try {
|
||||
const result = await this.api(
|
||||
`/api/games/${pkg.package.game.id}/screen-plan`,
|
||||
undefined,
|
||||
true,
|
||||
AbortSignal.timeout(5000),
|
||||
)
|
||||
const result = await this.client(true, AbortSignal.timeout(5000)).GetScreenPlan({
|
||||
gameID: pkg.package.game.id,
|
||||
})
|
||||
await this.acceptPlan(parsePlan(result.plan), pkg.key, generation, version)
|
||||
} catch (error) {
|
||||
if (generation === this.generation && version === this.planVersion)
|
||||
@@ -472,13 +471,10 @@ export class Online {
|
||||
const version = ++this.planVersion
|
||||
this.planBusy++
|
||||
try {
|
||||
const result = await this.json(
|
||||
await this.authorized(`/api/games/${pkg.package.game.id}/screen-plan`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(plan),
|
||||
}),
|
||||
)
|
||||
const result = await this.client().SaveScreenPlan({
|
||||
gameID: pkg.package.game.id,
|
||||
...apiPlan(plan),
|
||||
})
|
||||
if (generation !== this.generation) throw new SessionChangedError()
|
||||
await this.acceptPlan(parsePlan(result.plan), pkg.key, generation, version)
|
||||
} finally {
|
||||
@@ -576,12 +572,10 @@ export class Online {
|
||||
return
|
||||
}
|
||||
if (intent.action === 'step') {
|
||||
const result = await this.api(
|
||||
`/api/games/${pkg.package.game.id}/screen-plan/step`,
|
||||
{ currentStep: intent.index },
|
||||
true,
|
||||
AbortSignal.timeout(5000),
|
||||
)
|
||||
const result = await this.client(true, AbortSignal.timeout(5000)).SetScreenPlanStep({
|
||||
gameID: pkg.package.game.id,
|
||||
currentStep: intent.index,
|
||||
})
|
||||
const confirmed = parsePlan(result.plan)
|
||||
const shown = this.controller.state.catalog.steps[intent.index]
|
||||
await this.acceptPlan(confirmed, pkg.key, generation, version)
|
||||
@@ -599,29 +593,29 @@ export class Online {
|
||||
}
|
||||
} else if ('channel' in intent && intent.channel === 'music') {
|
||||
const c = localState.channels.music
|
||||
await this.api(
|
||||
`/api/games/${pkg.package.game.id}/music`,
|
||||
{
|
||||
playlistID: c.playlistId ?? 0,
|
||||
trackIndex: c.trackIndex,
|
||||
positionMs: Math.max(0, Math.round(c.position * 1000)),
|
||||
volume: Math.round(c.volume),
|
||||
loop: c.loop,
|
||||
playback: c.desired === 'playing' ? 2 : c.desired === 'paused' ? 3 : 1,
|
||||
},
|
||||
true,
|
||||
AbortSignal.timeout(5000),
|
||||
)
|
||||
await this.client(true, AbortSignal.timeout(5000)).SetGameMusic({
|
||||
gameID: pkg.package.game.id,
|
||||
playlistID: c.playlistId ?? 0,
|
||||
trackIndex: c.trackIndex,
|
||||
positionMs: Math.max(0, Math.round(c.position * 1000)),
|
||||
volume: Math.round(c.volume),
|
||||
loop: c.loop,
|
||||
playback:
|
||||
c.desired === 'playing'
|
||||
? 'MUSIC_PLAYBACK_PLAYING'
|
||||
: c.desired === 'paused'
|
||||
? 'MUSIC_PLAYBACK_PAUSED'
|
||||
: 'MUSIC_PLAYBACK_STOPPED',
|
||||
})
|
||||
} else if (intent.action === 'mode' || intent.action === 'select') {
|
||||
await this.api(
|
||||
`/api/games/${pkg.package.game.id}/screens/mode`,
|
||||
{ mode: localState.scene.mode },
|
||||
true,
|
||||
AbortSignal.timeout(5000),
|
||||
)
|
||||
await this.client(true, AbortSignal.timeout(5000)).SetScreenMode({
|
||||
gameID: pkg.package.game.id,
|
||||
mode: apiScreenMode(localState.scene.mode),
|
||||
})
|
||||
} else if (intent.action === 'text') {
|
||||
const path = `/api/games/${pkg.package.game.id}/screen-plan`
|
||||
const current = parsePlan((await this.api(path)).plan)
|
||||
const current = parsePlan(
|
||||
(await this.client().GetScreenPlan({ gameID: pkg.package.game.id })).plan,
|
||||
)
|
||||
if (
|
||||
generation !== this.generation ||
|
||||
session !== this.controller.state.session ||
|
||||
@@ -637,13 +631,7 @@ export class Online {
|
||||
timerLabel: '',
|
||||
timerDurationSeconds: 0,
|
||||
})
|
||||
await this.json(
|
||||
await this.authorized(path, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(current),
|
||||
}),
|
||||
)
|
||||
await this.client().SaveScreenPlan({ gameID: pkg.package.game.id, ...apiPlan(current) })
|
||||
}
|
||||
if (
|
||||
generation !== this.generation ||
|
||||
@@ -651,7 +639,10 @@ export class Online {
|
||||
version !== this.planVersion
|
||||
)
|
||||
return
|
||||
const result = await this.api(`${path}/step`, { currentStep: index })
|
||||
const result = await this.client().SetScreenPlanStep({
|
||||
gameID: pkg.package.game.id,
|
||||
currentStep: index,
|
||||
})
|
||||
await this.acceptPlan(parsePlan(result.plan), pkg.key, generation, version)
|
||||
if (
|
||||
generation !== this.generation ||
|
||||
@@ -661,21 +652,18 @@ export class Online {
|
||||
return
|
||||
this.controller.markStep(index)
|
||||
} else if (intent.action === 'timer-set') {
|
||||
await this.api(
|
||||
`/api/games/${pkg.package.game.id}/screens/timer`,
|
||||
{ label: intent.label, durationSeconds: intent.durationSeconds },
|
||||
true,
|
||||
AbortSignal.timeout(5000),
|
||||
)
|
||||
await this.client(true, AbortSignal.timeout(5000)).SetScreenTimer({
|
||||
gameID: pkg.package.game.id,
|
||||
label: intent.label,
|
||||
durationSeconds: intent.durationSeconds,
|
||||
})
|
||||
} else {
|
||||
const index = this.controller.state.scene.stepIndex
|
||||
if (index !== null && index !== this.plan.currentStep) {
|
||||
const result = await this.api(
|
||||
`/api/games/${pkg.package.game.id}/screen-plan/step`,
|
||||
{ currentStep: index },
|
||||
true,
|
||||
AbortSignal.timeout(5000),
|
||||
)
|
||||
const result = await this.client(true, AbortSignal.timeout(5000)).SetScreenPlanStep({
|
||||
gameID: pkg.package.game.id,
|
||||
currentStep: index,
|
||||
})
|
||||
await this.acceptPlan(parsePlan(result.plan), pkg.key, generation, version)
|
||||
if (
|
||||
generation !== this.generation ||
|
||||
@@ -699,16 +687,12 @@ export class Online {
|
||||
}
|
||||
if (!(intent.action in commands)) throw new Error('Некорректная команда плана')
|
||||
expectedCommand = commands[intent.action as keyof typeof commands]
|
||||
await this.api(
|
||||
`/api/games/${pkg.package.game.id}/screens/command`,
|
||||
{
|
||||
command: expectedCommand,
|
||||
positionMs: 'position' in intent ? Math.round(intent.position * 1000) : 0,
|
||||
volume: 'volume' in intent ? intent.volume : this.controller.state.channels.main.volume,
|
||||
},
|
||||
true,
|
||||
AbortSignal.timeout(5000),
|
||||
)
|
||||
await this.client(true, AbortSignal.timeout(5000)).SendScreenCommand({
|
||||
gameID: pkg.package.game.id,
|
||||
command: apiScreenCommand(expectedCommand),
|
||||
positionMs: 'position' in intent ? Math.round(intent.position * 1000) : 0,
|
||||
volume: 'volume' in intent ? intent.volume : this.controller.state.channels.main.volume,
|
||||
})
|
||||
}
|
||||
if (
|
||||
generation !== this.generation ||
|
||||
@@ -719,12 +703,10 @@ export class Online {
|
||||
this.planNotice = ''
|
||||
// Ответ следующего опроса становится базой: собственная команда не применяется дважды.
|
||||
if (this.screen) {
|
||||
const result = await this.api(
|
||||
'/api/screens/state',
|
||||
{ screenID: this.screen.id, accessToken: this.screen.token },
|
||||
false,
|
||||
AbortSignal.timeout(5000),
|
||||
)
|
||||
const result = await this.client(false, AbortSignal.timeout(5000)).GetScreenState({
|
||||
screenID: this.screen.id,
|
||||
accessToken: this.screen.token,
|
||||
})
|
||||
if (generation !== this.generation || session !== this.controller.state.session) return
|
||||
const remote = parseRemote(result.state)
|
||||
if (remote.gameId !== pkg.package.game.id) throw new Error('Привязка экрана изменилась')
|
||||
@@ -748,12 +730,9 @@ export class Online {
|
||||
needsMainSync = !isMusicCommand
|
||||
}
|
||||
if (needsMainSync && remote.command === 0) {
|
||||
const result = await this.api(
|
||||
`/api/games/${pkg.package.game.id}/screen-plan`,
|
||||
undefined,
|
||||
true,
|
||||
AbortSignal.timeout(5000),
|
||||
)
|
||||
const result = await this.client(true, AbortSignal.timeout(5000)).GetScreenPlan({
|
||||
gameID: pkg.package.game.id,
|
||||
})
|
||||
await this.acceptPlan(parsePlan(result.plan), pkg.key, generation, version)
|
||||
if (
|
||||
generation !== this.generation ||
|
||||
@@ -1021,19 +1000,18 @@ export class Online {
|
||||
if (this.refreshPromise) return this.refreshPromise
|
||||
const c = this.credentials!
|
||||
this.refreshPromise = (async () => {
|
||||
const response = await this.raw(c.server, '/api/auth/refresh', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ refreshToken: c.refresh }),
|
||||
})
|
||||
if (generation !== this.generation) throw new SessionChangedError()
|
||||
if (response.status === 401) {
|
||||
this.credentials = null
|
||||
this.state.authenticated = false
|
||||
await this.persistSecrets()
|
||||
throw new ApiError(401)
|
||||
let result
|
||||
try {
|
||||
result = await this.client(false, undefined, c.server).Refresh({ refreshToken: c.refresh })
|
||||
} catch (error) {
|
||||
if (generation !== this.generation) throw new SessionChangedError()
|
||||
if (error instanceof ApiError && error.status === 401) {
|
||||
this.credentials = null
|
||||
this.state.authenticated = false
|
||||
await this.persistSecrets()
|
||||
}
|
||||
throw error
|
||||
}
|
||||
const result = await this.json(response)
|
||||
if (generation !== this.generation) throw new SessionChangedError()
|
||||
this.credentials = {
|
||||
...c,
|
||||
@@ -1070,21 +1048,19 @@ export class Online {
|
||||
}
|
||||
return response
|
||||
}
|
||||
private async api(path: string, body?: object, authorized = true, signal?: AbortSignal) {
|
||||
const init: RequestInit =
|
||||
body === undefined
|
||||
? { signal }
|
||||
: {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
signal,
|
||||
}
|
||||
return this.json(
|
||||
authorized
|
||||
? await this.authorized(path, init)
|
||||
: await this.raw(this.state.server, path, init),
|
||||
)
|
||||
private client(authorized = true, signal?: AbortSignal, server = this.state.server) {
|
||||
return createEveningDetectiveServerClient(async (request) => {
|
||||
const init: RequestInit = {
|
||||
method: request.method,
|
||||
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||
body: request.body,
|
||||
signal,
|
||||
}
|
||||
const path = `/${request.path}`
|
||||
return this.json(
|
||||
authorized ? await this.authorized(path, init) : await this.raw(server, path, init),
|
||||
)
|
||||
})
|
||||
}
|
||||
async act(input: unknown): Promise<OnlineState> {
|
||||
const data = object(input)
|
||||
@@ -1127,12 +1103,7 @@ export class Online {
|
||||
this.state.games = []
|
||||
await this.persistSecrets()
|
||||
this.state.server = server
|
||||
const response = await this.raw(server, '/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
})
|
||||
const result = await this.json(response)
|
||||
const result = await this.client(false, undefined, server).Login({ email, password })
|
||||
this.credentials = {
|
||||
server,
|
||||
access: token(result.accessToken),
|
||||
@@ -1140,7 +1111,7 @@ export class Online {
|
||||
screen: null,
|
||||
}
|
||||
try {
|
||||
const me = object((await this.api('/api/users/me')).user)
|
||||
const me = object((await this.client().GetMe({})).user)
|
||||
if (
|
||||
!Array.isArray(me.roles) ||
|
||||
!me.roles.some((r) => ['admin', 'organizer', 'author'].includes(String(r)))
|
||||
@@ -1261,17 +1232,13 @@ export class Online {
|
||||
this.state.packagesRevision++
|
||||
await this.saveSelection()
|
||||
}
|
||||
const result = await this.api('/api/screens', {}, false)
|
||||
const result = await this.client(false).CreateScreenSession({})
|
||||
const s: ScreenSession = {
|
||||
id: String(integer(result.screenID, 1)),
|
||||
token: token(result.accessToken),
|
||||
gameId,
|
||||
}
|
||||
await this.api(`/api/screens/${s.id}/bind`, {
|
||||
screenID: s.id,
|
||||
accessToken: s.token,
|
||||
gameID: s.gameId,
|
||||
})
|
||||
await this.client().BindScreen({ screenID: s.id, accessToken: s.token, gameID: s.gameId })
|
||||
this.screen = s
|
||||
this.state.screenId = s.id
|
||||
this.state.connection = 'waiting'
|
||||
@@ -1285,10 +1252,7 @@ export class Online {
|
||||
case 'sound': {
|
||||
const generation = this.generation
|
||||
if (!this.screen) throw new Error('Сначала подключите управление')
|
||||
await this.api(`/api/screens/${this.screen.id}/sound`, {
|
||||
screenID: this.screen.id,
|
||||
soundEnabled: true,
|
||||
})
|
||||
await this.client().SetScreenSound({ screenID: this.screen.id, soundEnabled: true })
|
||||
if (generation === this.generation) this.state.soundEnabled = true
|
||||
return
|
||||
}
|
||||
@@ -1299,7 +1263,7 @@ export class Online {
|
||||
}
|
||||
}
|
||||
private async loadGames() {
|
||||
const result = await this.api('/api/games')
|
||||
const result = await this.client().GetGames({})
|
||||
if (!Array.isArray(result.games ?? [])) throw new Error('Некорректный список игр')
|
||||
this.state.games = ((result.games ?? []) as unknown[]).map((value) => {
|
||||
const g = object(value)
|
||||
@@ -1328,11 +1292,10 @@ export class Online {
|
||||
this.resultsBusy = true
|
||||
this.lastResults = Date.now()
|
||||
try {
|
||||
const result = await this.api(
|
||||
'/api/screens/results',
|
||||
{ screenID: s.id, accessToken: s.token },
|
||||
false,
|
||||
)
|
||||
const result = await this.client(false).GetScreenResults({
|
||||
screenID: s.id,
|
||||
accessToken: s.token,
|
||||
})
|
||||
if (generation !== this.generation || key !== this.controller.state.catalog.key) return
|
||||
if (!Array.isArray(result.questions ?? []) || !Array.isArray(result.teams ?? []))
|
||||
throw new Error('Некорректные результаты')
|
||||
@@ -1378,12 +1341,10 @@ export class Online {
|
||||
const version = this.planVersion
|
||||
this.pollBusy = true
|
||||
try {
|
||||
const result = await this.api(
|
||||
'/api/screens/state',
|
||||
{ screenID: s.id, accessToken: s.token },
|
||||
false,
|
||||
AbortSignal.timeout(5000),
|
||||
)
|
||||
const result = await this.client(false, AbortSignal.timeout(5000)).GetScreenState({
|
||||
screenID: s.id,
|
||||
accessToken: s.token,
|
||||
})
|
||||
if (generation !== this.generation || version !== this.planVersion) return
|
||||
let next = parseRemote(result.state)
|
||||
if (next.gameId !== s.gameId) {
|
||||
@@ -1458,12 +1419,10 @@ export class Online {
|
||||
await this.synchronize()
|
||||
return
|
||||
}
|
||||
const result = await this.api(
|
||||
'/api/screens/music/advance',
|
||||
{ screenID: this.screen.id, accessToken: this.screen.token },
|
||||
false,
|
||||
AbortSignal.timeout(5000),
|
||||
)
|
||||
const result = await this.client(false, AbortSignal.timeout(5000)).AdvanceScreenMusic({
|
||||
screenID: this.screen.id,
|
||||
accessToken: this.screen.token,
|
||||
})
|
||||
if (generation !== this.generation || report.session !== this.controller.state.session) return
|
||||
const music = parseRemote({ music: result.state }).music
|
||||
if (this.received) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createEveningDetectiveServerClient } from './api/generated/crabs/evening_detective_server/index.js'
|
||||
import {
|
||||
ApiError,
|
||||
NetworkError,
|
||||
@@ -240,10 +241,18 @@ export async function prepareScreenPackage(
|
||||
AbortSignal.timeout(30_000),
|
||||
...(request.signal ? [request.signal] : []),
|
||||
])
|
||||
async function get(suffix: string): Promise<unknown> {
|
||||
const client = createEveningDetectiveServerClient(async (requestData, meta) => {
|
||||
const suffix =
|
||||
meta.method === 'GetGameMusic'
|
||||
? '/music'
|
||||
: meta.method === 'GetScreenPlan'
|
||||
? '/screen-plan'
|
||||
: ''
|
||||
let response: Response
|
||||
try {
|
||||
response = await fetchAPI(`${server}/api/games/${request.gameId}${suffix}`, {
|
||||
response = await fetchAPI(`${server}/${requestData.path}`, {
|
||||
method: requestData.method,
|
||||
body: requestData.body,
|
||||
headers: { Authorization: `Bearer ${request.token}`, Accept: 'application/json' },
|
||||
credentials: 'omit',
|
||||
redirect: 'error',
|
||||
@@ -292,8 +301,12 @@ export async function prepareScreenPackage(
|
||||
request.onRequestFailure?.({ path: `/api/games/{id}${suffix}`, reason: 'INVALID_JSON' })
|
||||
throw new Error('Некорректный JSON в ответе API')
|
||||
}
|
||||
}
|
||||
const [game, plan, music] = await Promise.all([get(''), get('/screen-plan'), get('/music')])
|
||||
})
|
||||
const [game, plan, music] = await Promise.all([
|
||||
client.GetGame({ ID: request.gameId }),
|
||||
client.GetScreenPlan({ gameID: request.gameId }),
|
||||
client.GetGameMusic({ gameID: request.gameId }),
|
||||
])
|
||||
return buildScreenPackage(server, request.gameId, { game, plan, music })
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user