@orakl/protocol 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/error-codes.d.ts +1 -1
- package/dist/game.settings.d.ts +5 -0
- package/dist/game.settings.js +8 -0
- package/dist/index.d.ts +1 -1
- package/dist/pages/app.schema.d.ts +3 -0
- package/dist/pages/app.schema.js +1 -0
- package/dist/pages/quiz.schema.d.ts +3 -0
- package/dist/pages/quiz.schema.js +3 -0
- package/dist/ws-game.schema.d.ts +3 -0
- package/dist/ws-game.schema.js +5 -1
- package/dist/ws.d.ts +42 -2
- package/package.json +1 -1
package/dist/error-codes.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export type ErrorCode = "no_active_lobby" | "no_active_game" | "device_blocked" | "nickname_taken" | "nickname_locked" | "device_in_lobby" | "player_not_found" | "player_not_pending" | "intermission_invalid_phase" | "not_in_intermission" | "category_name_exists" | "category_not_found" | "category_has_questions" | "tag_name_exists" | "tag_not_found" | "tag_has_questions" | "tag_in_season" | "category_in_season" | "season_name_exists" | "season_not_found" | "user_not_found" | "curation_request_pending" | "curator_self_join" | "already_flagged" | "question_not_found" | "invalid_answer" | "game_in_progress" | "already_answered" | "observer_forbidden" | "no_questions" | "season_out_of_season" | "no_categories" | "too_few_players" | "hosting_closed" | "not_answered" | "emote_not_in_set" | "flag_not_toggleable" | "solo_invalid_phase" | "solo_invalid_answer" | "solo_nonce_mismatch" | "solo_answer_expired" | "solo_guest_limit" | "solo_no_questions" | "solo_season_out_of_season" | "solo_resume_denied" | "solo_auth_failed";
|
|
1
|
+
export type ErrorCode = "no_active_lobby" | "no_active_game" | "device_blocked" | "nickname_taken" | "nickname_locked" | "device_in_lobby" | "player_not_found" | "player_not_pending" | "intermission_invalid_phase" | "not_in_intermission" | "category_name_exists" | "category_not_found" | "category_has_questions" | "tag_name_exists" | "tag_not_found" | "tag_has_questions" | "tag_in_season" | "category_in_season" | "season_name_exists" | "season_not_found" | "user_not_found" | "curation_request_pending" | "curator_self_join" | "already_flagged" | "question_not_found" | "invalid_answer" | "game_in_progress" | "already_answered" | "observer_forbidden" | "no_questions" | "season_out_of_season" | "no_categories" | "too_few_players" | "hosting_closed" | "not_answered" | "emote_not_in_set" | "flag_not_toggleable" | "undo_disabled" | "undo_exhausted" | "solo_invalid_phase" | "solo_invalid_answer" | "solo_nonce_mismatch" | "solo_answer_expired" | "solo_guest_limit" | "solo_no_questions" | "solo_season_out_of_season" | "solo_resume_denied" | "solo_auth_failed";
|
package/dist/game.settings.d.ts
CHANGED
|
@@ -79,6 +79,11 @@ export declare const GAME: {
|
|
|
79
79
|
readonly player: {
|
|
80
80
|
readonly maxNicknameLength: 20;
|
|
81
81
|
};
|
|
82
|
+
readonly undo: {
|
|
83
|
+
readonly budgetFraction: 0.1;
|
|
84
|
+
readonly minBudget: 1;
|
|
85
|
+
readonly lastCallSeconds: 5;
|
|
86
|
+
};
|
|
82
87
|
readonly sse: {
|
|
83
88
|
readonly keepaliveIntervalMs: 15000;
|
|
84
89
|
};
|
package/dist/game.settings.js
CHANGED
|
@@ -55,6 +55,14 @@ export const GAME = {
|
|
|
55
55
|
player: {
|
|
56
56
|
maxNicknameLength: 20,
|
|
57
57
|
},
|
|
58
|
+
undo: {
|
|
59
|
+
// Per-player budget = max(minBudget, ceil(totalQuestions * budgetFraction)).
|
|
60
|
+
budgetFraction: 0.1,
|
|
61
|
+
minBudget: 1,
|
|
62
|
+
// Grace window once every hero has answered, during which an undo may
|
|
63
|
+
// still cancel the round close.
|
|
64
|
+
lastCallSeconds: 5,
|
|
65
|
+
},
|
|
58
66
|
sse: {
|
|
59
67
|
keepaliveIntervalMs: 15000,
|
|
60
68
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,6 @@ export type { StreakFlourish } from "./streak-flourish.ts";
|
|
|
19
19
|
export type { LeaderboardBoard, LeaderboardDifficulty, LeaderboardScope, } from "./wire/leaderboard.ts";
|
|
20
20
|
export type { OperatorQuestionRevision, PublishManyResult, QuestionRevision, } from "./wire/question-bank.ts";
|
|
21
21
|
export type { FlagDetail, FlagReporter, FlagStatus, QuestionFlagAnswerSnapshot, QuestionSnapshot, } from "./wire/question-flags.ts";
|
|
22
|
-
export type { ClientMessage, CuratorAdvanceMessage, CuratorIntermissionMessage, CuratorPlayerActionMessage, CuratorResetMessage, CuratorStartMessage, CuratorStartRoleSelectionMessage, CuratorStopMessage, GameErrorMessage, GameFinalScoresMessage, GameIntermissionMessage, GamePingMessage, GameQuestionMessage, GameResultsReadyMessage, GameResumeMessage, GameRoundResultMessage, GameStartMessage, GameTickMessage, JoinAcceptedMessage, JoinRejectedMessage, LobbyEndedMessage, LobbyResetMessage, LobbyUpdateMessage, PlayerAnsweredMessage, PlayerAnswerMessage, PlayerEmoteBroadcastMessage, PlayerEmoteMessage, PlayerLeaveMessage, PlayerNicknameChangedMessage, PlayerQuestionAnswer, PlayerRateAckMessage, PlayerRateMessage, PlayerRemovedMessage, PlayerRoleMessage, RateAckReason, RoleSelectionLockedMessage, RoleSelectionStartMessage, RoleSelectionUpdateMessage, ServerMessage, WsAuthMessage, WsPongMessage, } from "./ws.ts";
|
|
22
|
+
export type { ClientMessage, CuratorAdvanceMessage, CuratorIntermissionMessage, CuratorPlayerActionMessage, CuratorResetMessage, CuratorStartMessage, CuratorStartRoleSelectionMessage, CuratorStopMessage, GameErrorMessage, GameFinalScoresMessage, GameIntermissionMessage, GameLastCallCancelledMessage, GameLastCallMessage, GamePingMessage, GameQuestionMessage, GameResultsReadyMessage, GameResumeMessage, GameRoundResultMessage, GameStartMessage, GameTickMessage, JoinAcceptedMessage, JoinRejectedMessage, LobbyEndedMessage, LobbyResetMessage, LobbyUpdateMessage, PlayerAnsweredMessage, PlayerAnswerMessage, PlayerEmoteBroadcastMessage, PlayerEmoteMessage, PlayerLeaveMessage, PlayerNicknameChangedMessage, PlayerQuestionAnswer, PlayerRateAckMessage, PlayerRateMessage, PlayerRemovedMessage, PlayerRoleMessage, PlayerUnansweredMessage, PlayerUndoAckMessage, PlayerUndoMessage, RateAckReason, RoleSelectionLockedMessage, RoleSelectionStartMessage, RoleSelectionUpdateMessage, ServerMessage, WsAuthMessage, WsPongMessage, } from "./ws.ts";
|
|
23
23
|
export { ClientMessageSchema } from "./ws-game.schema.ts";
|
|
24
24
|
export { SoloClientMessageSchema } from "./ws-solo.schema.ts";
|
|
@@ -431,6 +431,7 @@ declare const QuizPresetSchema: Schema.Struct<{
|
|
|
431
431
|
scoring_mode: Schema.NullOr<typeof Schema.String>;
|
|
432
432
|
access_mode: typeof Schema.String;
|
|
433
433
|
max_players: Schema.NullOr<typeof Schema.Number>;
|
|
434
|
+
undo_enabled: typeof Schema.Number;
|
|
434
435
|
created_at: typeof Schema.String;
|
|
435
436
|
}>;
|
|
436
437
|
export type QuizPreset = typeof QuizPresetSchema.Type;
|
|
@@ -498,6 +499,7 @@ export declare const CuratorCreatePageSchema: Schema.Struct<{
|
|
|
498
499
|
readonly scoring_mode: string | null;
|
|
499
500
|
readonly access_mode: string;
|
|
500
501
|
readonly max_players: number | null;
|
|
502
|
+
readonly undo_enabled: number;
|
|
501
503
|
}, {
|
|
502
504
|
readonly name: string;
|
|
503
505
|
readonly id: string;
|
|
@@ -513,6 +515,7 @@ export declare const CuratorCreatePageSchema: Schema.Struct<{
|
|
|
513
515
|
readonly scoring_mode: string | null;
|
|
514
516
|
readonly access_mode: string;
|
|
515
517
|
readonly max_players: number | null;
|
|
518
|
+
readonly undo_enabled: number;
|
|
516
519
|
}, never>>>;
|
|
517
520
|
presetMarks: Schema.Record$<typeof Schema.String, Schema.Struct<{
|
|
518
521
|
closedSeasons: Schema.mutable<Schema.Array$<Schema.Schema<{
|
package/dist/pages/app.schema.js
CHANGED
|
@@ -282,6 +282,7 @@ const QuizPresetSchema = Schema.Struct({
|
|
|
282
282
|
scoring_mode: Schema.NullOr(Schema.String),
|
|
283
283
|
access_mode: Schema.String,
|
|
284
284
|
max_players: Schema.NullOr(Schema.Number),
|
|
285
|
+
undo_enabled: Schema.Number,
|
|
285
286
|
created_at: Schema.String,
|
|
286
287
|
});
|
|
287
288
|
/** What stopped a saved Quiz's members from drawing: closed or gone Seasons, gone or hidden categories. */
|
|
@@ -69,6 +69,9 @@ export type PlayerJourney = typeof PlayerJourneySchema.Type;
|
|
|
69
69
|
/** Body of `GET /api/quiz/journey`: a `PlayerJourney` plus the registry-miss diagnosis. */
|
|
70
70
|
export declare const QuizJourneyPageSchema: Schema.Struct<{
|
|
71
71
|
miss: Schema.NullOr<Schema.Literal<["lobby_lost"]>>;
|
|
72
|
+
/** The caller's token names a lobby that is gone for good (closed or
|
|
73
|
+
* expired): the client can say so instead of showing a bare code entry. */
|
|
74
|
+
lobbyGone: Schema.optional<typeof Schema.Boolean>;
|
|
72
75
|
membership: Schema.Literal<["none", "pending", "active"]>;
|
|
73
76
|
phase: Schema.NullOr<Schema.Literal<["lobby", "playing", "round_results", "final_scores", "role_selection", "intermission"]>>;
|
|
74
77
|
state: Schema.Literal<["code-entry", "setup", "pending", "lobby", "playing", "role_selection", "final_scores"]>;
|
|
@@ -70,6 +70,9 @@ const PlayerJourneySchema = Schema.Struct({
|
|
|
70
70
|
export const QuizJourneyPageSchema = Schema.Struct({
|
|
71
71
|
...PlayerJourneySchema.fields,
|
|
72
72
|
miss: Schema.NullOr(Schema.Literal("lobby_lost")),
|
|
73
|
+
/** The caller's token names a lobby that is gone for good (closed or
|
|
74
|
+
* expired): the client can say so instead of showing a bare code entry. */
|
|
75
|
+
lobbyGone: Schema.optional(Schema.Boolean),
|
|
73
76
|
});
|
|
74
77
|
// ---- quiz context (the `/quiz/*` layout's session seed) ----
|
|
75
78
|
const DifficultyFilterSchema = Schema.Literal(...GAME.roundConfig.difficultyOptions);
|
package/dist/ws-game.schema.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export declare const ClientMessageSchema: Schema.Union<[Schema.Struct<{
|
|
|
16
16
|
role: Schema.Literal<["player", "observer"]>;
|
|
17
17
|
}>, Schema.Struct<{
|
|
18
18
|
type: Schema.Literal<["player:leave"]>;
|
|
19
|
+
}>, Schema.Struct<{
|
|
20
|
+
type: Schema.Literal<["player:undo"]>;
|
|
19
21
|
}>, Schema.Struct<{
|
|
20
22
|
type: Schema.Literal<["player:rate"]>;
|
|
21
23
|
questionId: Schema.filter<typeof Schema.NonEmptyString>;
|
|
@@ -29,6 +31,7 @@ export declare const ClientMessageSchema: Schema.Union<[Schema.Struct<{
|
|
|
29
31
|
difficulty: Schema.optional<Schema.Literal<["all", "easy", "medium", "hard"]>>;
|
|
30
32
|
advanceMode: Schema.optional<Schema.Literal<["auto_5", "auto_10", "manual"]>>;
|
|
31
33
|
scoringMode: Schema.optional<Schema.Literal<["classic", "time-based"]>>;
|
|
34
|
+
undoEnabled: Schema.optional<typeof Schema.Boolean>;
|
|
32
35
|
preloadedQuestions: Schema.optional<Schema.filter<Schema.Array$<Schema.Union<[Schema.Struct<{
|
|
33
36
|
type: Schema.Literal<["text_choice", "image_choice", "true_false", "finish_line", "audio_identify", "sequence_order", "estimate_range", "fill_blank", "matching_pairs", "progressive_reveal"]>;
|
|
34
37
|
id: Schema.filter<typeof Schema.NonEmptyString>;
|
package/dist/ws-game.schema.js
CHANGED
|
@@ -46,6 +46,9 @@ const PlayerRoleMessageSchema = Schema.Struct({
|
|
|
46
46
|
const PlayerLeaveMessageSchema = Schema.Struct({
|
|
47
47
|
type: Schema.Literal("player:leave"),
|
|
48
48
|
});
|
|
49
|
+
const PlayerUndoMessageSchema = Schema.Struct({
|
|
50
|
+
type: Schema.Literal("player:undo"),
|
|
51
|
+
});
|
|
49
52
|
const PlayerRateMessageSchema = Schema.Struct({
|
|
50
53
|
type: Schema.Literal("player:rate"),
|
|
51
54
|
questionId: BoundedIdSchema,
|
|
@@ -104,6 +107,7 @@ const CuratorStartMessageSchema = Schema.Struct({
|
|
|
104
107
|
difficulty: Schema.optional(DifficultyFilterSchema),
|
|
105
108
|
advanceMode: Schema.optional(Schema.Literal(...GAME.roundConfig.advanceModeOptions)),
|
|
106
109
|
scoringMode: OptionalScoringModeSchema,
|
|
110
|
+
undoEnabled: Schema.optional(Schema.Boolean),
|
|
107
111
|
// Generous headroom above any real round size (max 100) so a legitimate
|
|
108
112
|
// curator import is never rejected; still bounded against a DoS payload.
|
|
109
113
|
preloadedQuestions: Schema.optional(Schema.Array(PreloadedQuestionSchema).pipe(Schema.maxItems(500))),
|
|
@@ -135,5 +139,5 @@ const WsPongMessageSchema = Schema.Struct({
|
|
|
135
139
|
});
|
|
136
140
|
// Discriminant: Effect Schema detects the shared `type: Schema.Literal(...)`
|
|
137
141
|
// field and short-circuits matching — every member needs a unique `type`.
|
|
138
|
-
export const ClientMessageSchema = Schema.Union(WsAuthMessageSchema, PlayerAnswerMessageSchema, PlayerEmoteMessageSchema, PlayerRoleMessageSchema, PlayerLeaveMessageSchema, PlayerRateMessageSchema, CuratorStartMessageSchema, CuratorAdvanceMessageSchema, CuratorStartRoleSelectionMessageSchema, CuratorIntermissionMessageSchema, CuratorPlayerActionMessageSchema, CuratorStopMessageSchema, CuratorResetMessageSchema, WsPongMessageSchema);
|
|
142
|
+
export const ClientMessageSchema = Schema.Union(WsAuthMessageSchema, PlayerAnswerMessageSchema, PlayerEmoteMessageSchema, PlayerRoleMessageSchema, PlayerLeaveMessageSchema, PlayerUndoMessageSchema, PlayerRateMessageSchema, CuratorStartMessageSchema, CuratorAdvanceMessageSchema, CuratorStartRoleSelectionMessageSchema, CuratorIntermissionMessageSchema, CuratorPlayerActionMessageSchema, CuratorStopMessageSchema, CuratorResetMessageSchema, WsPongMessageSchema);
|
|
139
143
|
const _assertNoClientMessageDrift = null;
|
package/dist/ws.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { GameAnswerRecord } from "./game-answer-record.ts";
|
|
|
6
6
|
import type { MediaCredit, MediaType, QuestionType } from "./question.schema.ts";
|
|
7
7
|
import type { QuestionRating } from "./question-rating.ts";
|
|
8
8
|
import type { StreakFlourish } from "./streak-flourish.ts";
|
|
9
|
-
export type ClientMessage = WsAuthMessage | PlayerAnswerMessage | PlayerEmoteMessage | PlayerRoleMessage | PlayerLeaveMessage | PlayerRateMessage | CuratorStartMessage | CuratorAdvanceMessage | CuratorStartRoleSelectionMessage | CuratorIntermissionMessage | CuratorPlayerActionMessage | CuratorStopMessage | CuratorResetMessage | WsPongMessage;
|
|
9
|
+
export type ClientMessage = WsAuthMessage | PlayerAnswerMessage | PlayerEmoteMessage | PlayerRoleMessage | PlayerLeaveMessage | PlayerUndoMessage | PlayerRateMessage | CuratorStartMessage | CuratorAdvanceMessage | CuratorStartRoleSelectionMessage | CuratorIntermissionMessage | CuratorPlayerActionMessage | CuratorStopMessage | CuratorResetMessage | WsPongMessage;
|
|
10
10
|
/** First frame for native/display (cross-origin, no cookie): authenticates the
|
|
11
11
|
* socket with the bearer game token (ADR 0014). Web rides the cookie instead. */
|
|
12
12
|
export interface WsAuthMessage {
|
|
@@ -35,6 +35,12 @@ export interface PlayerRoleMessage {
|
|
|
35
35
|
export interface PlayerLeaveMessage {
|
|
36
36
|
type: "player:leave";
|
|
37
37
|
}
|
|
38
|
+
/** Player withdraws their answer to the open question and may answer again.
|
|
39
|
+
* Needs the curator's undo flag and remaining budget; the server replies with
|
|
40
|
+
* `player:undo-ack`. */
|
|
41
|
+
export interface PlayerUndoMessage {
|
|
42
|
+
type: "player:undo";
|
|
43
|
+
}
|
|
38
44
|
/** Rate a question revealed in this game — from a signed-in player who played
|
|
39
45
|
* its round, or the curator. The server answers with `player:rate-ack`
|
|
40
46
|
* echoing `questionId`; `reason: "disabled"` when the rating flag is off. */
|
|
@@ -57,6 +63,8 @@ export interface CuratorStartMessage {
|
|
|
57
63
|
difficulty?: DifficultyFilter;
|
|
58
64
|
advanceMode?: AdvanceMode;
|
|
59
65
|
scoringMode?: ScoringMode;
|
|
66
|
+
/** Players may withdraw and redo answers, within a per-player budget. */
|
|
67
|
+
undoEnabled?: boolean;
|
|
60
68
|
preloadedQuestions?: QuizQuestion[];
|
|
61
69
|
}
|
|
62
70
|
export interface CuratorAdvanceMessage {
|
|
@@ -89,7 +97,7 @@ export interface CuratorPlayerActionMessage {
|
|
|
89
97
|
export interface WsPongMessage {
|
|
90
98
|
type: "pong";
|
|
91
99
|
}
|
|
92
|
-
export type ServerMessage = JoinAcceptedMessage | JoinRejectedMessage | LobbyUpdateMessage | LobbyEndedMessage | LobbyResetMessage | PlayerNicknameChangedMessage | GameStartMessage | GameQuestionMessage | GameTickMessage | PlayerAnsweredMessage | GameRoundResultMessage | GameFinalScoresMessage | GameResultsReadyMessage | RoleSelectionStartMessage | RoleSelectionUpdateMessage | RoleSelectionLockedMessage | PlayerRemovedMessage | GameIntermissionMessage | GameResumeMessage | PlayerEmoteBroadcastMessage | GameErrorMessage | GamePingMessage | PlayerRateAckMessage;
|
|
100
|
+
export type ServerMessage = JoinAcceptedMessage | JoinRejectedMessage | LobbyUpdateMessage | LobbyEndedMessage | LobbyResetMessage | PlayerNicknameChangedMessage | GameStartMessage | GameQuestionMessage | GameTickMessage | PlayerAnsweredMessage | GameRoundResultMessage | GameFinalScoresMessage | GameResultsReadyMessage | RoleSelectionStartMessage | RoleSelectionUpdateMessage | RoleSelectionLockedMessage | PlayerRemovedMessage | GameIntermissionMessage | GameResumeMessage | PlayerEmoteBroadcastMessage | GameErrorMessage | GamePingMessage | PlayerRateAckMessage | PlayerUndoAckMessage | PlayerUnansweredMessage | GameLastCallMessage | GameLastCallCancelledMessage;
|
|
93
101
|
export interface JoinAcceptedMessage {
|
|
94
102
|
type: "join:accepted";
|
|
95
103
|
playerId: string;
|
|
@@ -139,6 +147,8 @@ export interface GameStartMessage {
|
|
|
139
147
|
totalQuestions: number;
|
|
140
148
|
timerDuration: TimerDuration;
|
|
141
149
|
advanceMode: AdvanceMode;
|
|
150
|
+
/** Undos each player may use this game; absent or 0 = undo is off. */
|
|
151
|
+
undoBudget?: number;
|
|
142
152
|
}
|
|
143
153
|
/** Question sent to players — isCorrect is omitted to prevent cheating */
|
|
144
154
|
export interface PlayerQuestionAnswer {
|
|
@@ -177,6 +187,10 @@ export interface GameQuestionMessage {
|
|
|
177
187
|
* resets to 0 until the next player:answered frame). Omitted on broadcast. */
|
|
178
188
|
answeredCount?: number;
|
|
179
189
|
answeredTotal?: number;
|
|
190
|
+
/** This recipient's undos left, set only on a snapshot replay. */
|
|
191
|
+
undoRemaining?: number;
|
|
192
|
+
/** Seconds left of an active last call, set only on a snapshot replay. */
|
|
193
|
+
lastCallSeconds?: number;
|
|
180
194
|
}
|
|
181
195
|
export interface GameTickMessage {
|
|
182
196
|
type: "game:tick";
|
|
@@ -194,6 +208,32 @@ export interface PlayerAnsweredMessage {
|
|
|
194
208
|
answeredCount: number;
|
|
195
209
|
totalCount: number;
|
|
196
210
|
}
|
|
211
|
+
/** A player withdrew their answer. Curator/display only. */
|
|
212
|
+
export interface PlayerUnansweredMessage {
|
|
213
|
+
type: "player:unanswered";
|
|
214
|
+
playerId: string;
|
|
215
|
+
answeredCount: number;
|
|
216
|
+
totalCount: number;
|
|
217
|
+
}
|
|
218
|
+
/** Reply to `player:undo`, sent to the acting socket only. */
|
|
219
|
+
export interface PlayerUndoAckMessage {
|
|
220
|
+
type: "player:undo-ack";
|
|
221
|
+
ok: boolean;
|
|
222
|
+
/** Undos the player has left after this request. */
|
|
223
|
+
remaining: number;
|
|
224
|
+
/** Why the undo was refused; set when `ok` is false. */
|
|
225
|
+
reason?: ErrorCode;
|
|
226
|
+
}
|
|
227
|
+
/** Every active hero has answered with undo on: the round closes after
|
|
228
|
+
* `seconds` unless an undo cancels the last call. */
|
|
229
|
+
export interface GameLastCallMessage {
|
|
230
|
+
type: "game:last-call";
|
|
231
|
+
seconds: number;
|
|
232
|
+
}
|
|
233
|
+
/** An undo during the last call cancelled it; the round resumes its timer. */
|
|
234
|
+
export interface GameLastCallCancelledMessage {
|
|
235
|
+
type: "game:last-call-cancelled";
|
|
236
|
+
}
|
|
197
237
|
export interface GameRoundResultMessage {
|
|
198
238
|
type: "game:round-result";
|
|
199
239
|
correctAnswerId: string;
|