From e12ef3bde03a9cbe0137b2f2418d4305ace05a89 Mon Sep 17 00:00:00 2001 From: Fedorov Vladimir Date: Sun, 11 Oct 2026 13:47:36 +0700 Subject: [PATCH] up --- proto/main.proto | 73 ++++++++-- src/App.vue | 4 + .../crabs/evening_detective_server/index.ts | 84 ++++++++++++ src/components/GamePage.vue | 2 + src/components/ScreenPage.vue | 4 + src/components/TeamStoryPage.vue | 2 + .../__tests__/devicePresence.test.ts | 129 ++++++++++++++++++ src/composables/useDeviceContext.ts | 9 ++ src/composables/useDevicePresence.ts | 67 +++++++++ src/stores/__tests__/devicePresence.test.ts | 22 +++ src/stores/devicePresence.ts | 32 +++++ src/utils/__tests__/deviceID.test.ts | 35 +++++ src/utils/deviceID.ts | 23 ++++ src/views/GameView.vue | 4 +- src/views/TeamStoryView.vue | 6 +- 15 files changed, 486 insertions(+), 10 deletions(-) create mode 100644 src/composables/__tests__/devicePresence.test.ts create mode 100644 src/composables/useDeviceContext.ts create mode 100644 src/composables/useDevicePresence.ts create mode 100644 src/stores/__tests__/devicePresence.test.ts create mode 100644 src/stores/devicePresence.ts create mode 100644 src/utils/__tests__/deviceID.test.ts create mode 100644 src/utils/deviceID.ts diff --git a/proto/main.proto b/proto/main.proto index c227cd9..1303a42 100644 --- a/proto/main.proto +++ b/proto/main.proto @@ -41,6 +41,23 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { }; service EveningDetectiveServer { + rpc DeviceHeartbeat(DeviceHeartbeatReq) returns (DeviceHeartbeatRsp) { + option (google.api.http) = { post: "/api/devices/heartbeat" body: "*" }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Устройства"; + summary: "Обновить присутствие подключения браузера"; + description: "Публичный endpoint. Контексты взаимоисключающие и проверяются сервером. В prod enabled=false без регистрации. UUID обозначает браузерный профиль, не физический телефон."; + security: {}; + }; + } + rpc GetDevices(GetDevicesReq) returns (GetDevicesRsp) { + option (google.api.http) = { get: "/api/devices" }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Устройства"; + summary: "Получить устройства площадки"; + description: "Только venue; организатор, администратор или разработчик. Связь актуальна менее 60 секунд; подключения удаляются через 15 минут. Фильтр gameID оставляет только подключения указанной игры."; + }; + } rpc GetTasks(GetTasksReq) returns (GetTasksRsp) { option (google.api.http) = { get: "/api/tasks" }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { @@ -638,8 +655,8 @@ service EveningDetectiveServer { rpc DownloadGameResultsPackage(GameResultsPackageReq) returns (google.api.HttpBody) { option (google.api.http) = {get: "/api/games/{ID}/results-package/download"}; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - tags: "Игры"; - summary: "Скачать свежий ZIP результатов завершённой venue-игры"; + tags : "Игры"; + summary : "Скачать свежий ZIP результатов завершённой venue-игры"; produces: "application/zip"; }; } @@ -650,11 +667,11 @@ service EveningDetectiveServer { body: "package" }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - tags: "Игры"; - summary: "Применить ZIP результатов на исходной prod-игре"; + tags : "Игры"; + summary : "Применить ZIP результатов на исходной prod-игре"; description: "HTTP принимает сырой ZIP до 1 ГиБ, включая поток без Content-Length. Ошибки: 400 неверный пакет/игра, 413 размер, 409 конфликт, 503 временный отказ."; - consumes: "application/zip"; - produces: "application/json"; + consumes : "application/zip"; + produces : "application/json"; }; } @@ -2371,12 +2388,52 @@ message TaskRsp { DeveloperTask task = 1; } message DeleteTaskRsp {} message GameResultsPackageReq { int32 ID = 1; } + message ApplyGameResultsPackageReq { - int32 ID = 1; + int32 ID = 1; google.api.HttpBody package = 2; } + message GameResultsPackageRsp { - int32 gameID = 1; + int32 gameID = 1; string packageID = 2; string appliedAt = 3; } + +message DeviceHeartbeatReq { + string deviceID = 1; + string connectionID = 2; + optional int32 organizerGameID = 3; + optional int32 teamID = 4; + string password = 5; + string screenID = 6; + string accessToken = 7; +} +message DeviceHeartbeatRsp { + optional bool enabled = 1; + google.protobuf.Timestamp serverTime = 2; +} +message GetDevicesReq { optional int32 gameID = 1; } +message GetDevicesRsp { + repeated VenueDevice devices = 1; + google.protobuf.Timestamp serverTime = 2; +} +message VenueDevice { + string ID = 1; + google.protobuf.Timestamp firstSeenAt = 2; + google.protobuf.Timestamp lastSeenAt = 3; + bool connected = 4; + repeated DeviceConnection connections = 5; +} +message DeviceConnection { + string ID = 1; + google.protobuf.Timestamp firstSeenAt = 2; + google.protobuf.Timestamp lastSeenAt = 3; + string IP = 4; + string userAgent = 5; + bool connected = 6; + optional int32 gameID = 7; + optional int32 teamID = 8; + optional int32 screenID = 9; + string kind = 10; // browser, organizer, team, screen +} diff --git a/src/App.vue b/src/App.vue index 7d216ff..6f4501d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,6 +2,10 @@ import { NConfigProvider, NMessageProvider } from 'naive-ui' import { darkTheme } from 'naive-ui' import { RouterView } from 'vue-router' + +import { useDevicePresence } from '@/composables/useDevicePresence' + +useDevicePresence()