@orakl/protocol 0.13.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 +2 -2
- package/dist/index.js +1 -1
- package/dist/pages/app.schema.d.ts +44 -0
- package/dist/pages/app.schema.js +24 -0
- package/dist/pages/quiz.schema.d.ts +3 -0
- package/dist/pages/quiz.schema.js +3 -0
- package/dist/question.schema.d.ts +6 -0
- package/dist/question.schema.js +12 -6
- 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
|
@@ -10,7 +10,7 @@ export * from "./pages/common.schema.ts";
|
|
|
10
10
|
export * from "./pages/history.schema.ts";
|
|
11
11
|
export * from "./pages/quiz.schema.ts";
|
|
12
12
|
export type { PlayerSafeQuestion } from "./player-safe-question.ts";
|
|
13
|
-
export { ATTRIBUTION_LICENCES, CREDIT_LICENCE_LABEL, CREDIT_LICENCES, type CreditLicence, CreditLicenceSchema, DISTRIBUTABLE_LICENCES, DifficultySchema, type ImageChoiceQuestion, type ImageMatchingQuestion, isHttpsUrl, type Media, type MediaCredit, MediaCreditSchema, MediaSchema, type MediaType, MediaTypeSchema, PostAnswerNoteSchema, type Question, QuestionSchema, type QuestionSource, QuestionSourceSchema, type QuestionType, QuestionTypeSchema, type Source, SourceSchema, type TextChoiceQuestion, type TrueFalseQuestion, } from "./question.schema.ts";
|
|
13
|
+
export { ATTRIBUTION_LICENCES, CREDIT_LICENCE_LABEL, CREDIT_LICENCES, type CreditLicence, CreditLicenceSchema, DISTRIBUTABLE_LICENCES, DifficultySchema, type ImageChoiceQuestion, type ImageMatchingQuestion, isHttpsUrl, type Media, type MediaCredit, MediaCreditSchema, MediaSchema, type MediaType, MediaTypeSchema, POST_ANSWER_NOTE_MAX_LEN, PostAnswerNoteSchema, type Question, QuestionSchema, type QuestionSource, QuestionSourceSchema, type QuestionType, QuestionTypeSchema, SOURCE_LABEL_MAX_LEN, SOURCE_URL_MAX_LEN, type Source, SourceSchema, type TextChoiceQuestion, type TrueFalseQuestion, } from "./question.schema.ts";
|
|
14
14
|
export { hasMedia, isImageMatching, isTextChoice, isTrueFalse, } from "./question.ts";
|
|
15
15
|
export type { QuestionRating } from "./question-rating.ts";
|
|
16
16
|
export { OptionalScoringModeSchema, ScoringModeSchema, } from "./scoring.schema.ts";
|
|
@@ -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";
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export * from "./pages/auth.schema.js";
|
|
|
5
5
|
export * from "./pages/common.schema.js";
|
|
6
6
|
export * from "./pages/history.schema.js";
|
|
7
7
|
export * from "./pages/quiz.schema.js";
|
|
8
|
-
export { ATTRIBUTION_LICENCES, CREDIT_LICENCE_LABEL, CREDIT_LICENCES, CreditLicenceSchema, DISTRIBUTABLE_LICENCES, DifficultySchema, isHttpsUrl, MediaCreditSchema, MediaSchema, MediaTypeSchema, PostAnswerNoteSchema, QuestionSchema, QuestionSourceSchema, QuestionTypeSchema, SourceSchema, } from "./question.schema.js";
|
|
8
|
+
export { ATTRIBUTION_LICENCES, CREDIT_LICENCE_LABEL, CREDIT_LICENCES, CreditLicenceSchema, DISTRIBUTABLE_LICENCES, DifficultySchema, isHttpsUrl, MediaCreditSchema, MediaSchema, MediaTypeSchema, POST_ANSWER_NOTE_MAX_LEN, PostAnswerNoteSchema, QuestionSchema, QuestionSourceSchema, QuestionTypeSchema, SOURCE_LABEL_MAX_LEN, SOURCE_URL_MAX_LEN, SourceSchema, } from "./question.schema.js";
|
|
9
9
|
export { hasMedia, isImageMatching, isTextChoice, isTrueFalse, } from "./question.js";
|
|
10
10
|
export { OptionalScoringModeSchema, ScoringModeSchema, } from "./scoring.schema.js";
|
|
11
11
|
export { soloError, } from "./solo-ws.js";
|
|
@@ -288,6 +288,47 @@ export declare const ManageQuestionRatingsPageSchema: Schema.Struct<{
|
|
|
288
288
|
initialHasMore: typeof Schema.Boolean;
|
|
289
289
|
page: typeof Schema.Number;
|
|
290
290
|
}>;
|
|
291
|
+
declare const BankQuestionRowSchema: Schema.Struct<{
|
|
292
|
+
id: typeof Schema.String;
|
|
293
|
+
excerpt: typeof Schema.String;
|
|
294
|
+
source: Schema.Literal<["built-in", "promoted"]>;
|
|
295
|
+
category: typeof Schema.String;
|
|
296
|
+
/** The stored question type, e.g. `text_choice`. */
|
|
297
|
+
type: typeof Schema.String;
|
|
298
|
+
difficulty: Schema.NullOr<Schema.Literal<["easy", "medium", "hard"]>>;
|
|
299
|
+
/** At least one revision exists. */
|
|
300
|
+
edited: typeof Schema.Boolean;
|
|
301
|
+
}>;
|
|
302
|
+
/** One question an operator may edit on the question bank page: built-in or promoted. */
|
|
303
|
+
export type BankQuestionRow = typeof BankQuestionRowSchema.Type;
|
|
304
|
+
/** Body of `GET /api/pages/manage/bank`. */
|
|
305
|
+
export declare const ManageBankPageSchema: Schema.Struct<{
|
|
306
|
+
initial: Schema.mutable<Schema.Array$<Schema.Schema<{
|
|
307
|
+
readonly type: string;
|
|
308
|
+
readonly source: "built-in" | "promoted";
|
|
309
|
+
readonly id: string;
|
|
310
|
+
readonly category: string;
|
|
311
|
+
readonly difficulty: "medium" | "easy" | "hard" | null;
|
|
312
|
+
readonly excerpt: string;
|
|
313
|
+
readonly edited: boolean;
|
|
314
|
+
}, {
|
|
315
|
+
readonly type: string;
|
|
316
|
+
readonly source: "built-in" | "promoted";
|
|
317
|
+
readonly id: string;
|
|
318
|
+
readonly category: string;
|
|
319
|
+
readonly difficulty: "medium" | "easy" | "hard" | null;
|
|
320
|
+
readonly excerpt: string;
|
|
321
|
+
readonly edited: boolean;
|
|
322
|
+
}, never>>>;
|
|
323
|
+
initialHasMore: typeof Schema.Boolean;
|
|
324
|
+
page: typeof Schema.Number;
|
|
325
|
+
/** The search term the page was loaded with; empty for none. */
|
|
326
|
+
q: typeof Schema.String;
|
|
327
|
+
/** The category name the page was filtered to; empty for all. */
|
|
328
|
+
category: typeof Schema.String;
|
|
329
|
+
/** Category names offered by the filter, A to Z. */
|
|
330
|
+
categories: Schema.mutable<Schema.Array$<Schema.Schema<string, string, never>>>;
|
|
331
|
+
}>;
|
|
291
332
|
declare const AuthoredQuestionRowSchema: Schema.Struct<{
|
|
292
333
|
id: typeof Schema.String;
|
|
293
334
|
category: typeof Schema.String;
|
|
@@ -390,6 +431,7 @@ declare const QuizPresetSchema: Schema.Struct<{
|
|
|
390
431
|
scoring_mode: Schema.NullOr<typeof Schema.String>;
|
|
391
432
|
access_mode: typeof Schema.String;
|
|
392
433
|
max_players: Schema.NullOr<typeof Schema.Number>;
|
|
434
|
+
undo_enabled: typeof Schema.Number;
|
|
393
435
|
created_at: typeof Schema.String;
|
|
394
436
|
}>;
|
|
395
437
|
export type QuizPreset = typeof QuizPresetSchema.Type;
|
|
@@ -457,6 +499,7 @@ export declare const CuratorCreatePageSchema: Schema.Struct<{
|
|
|
457
499
|
readonly scoring_mode: string | null;
|
|
458
500
|
readonly access_mode: string;
|
|
459
501
|
readonly max_players: number | null;
|
|
502
|
+
readonly undo_enabled: number;
|
|
460
503
|
}, {
|
|
461
504
|
readonly name: string;
|
|
462
505
|
readonly id: string;
|
|
@@ -472,6 +515,7 @@ export declare const CuratorCreatePageSchema: Schema.Struct<{
|
|
|
472
515
|
readonly scoring_mode: string | null;
|
|
473
516
|
readonly access_mode: string;
|
|
474
517
|
readonly max_players: number | null;
|
|
518
|
+
readonly undo_enabled: number;
|
|
475
519
|
}, never>>>;
|
|
476
520
|
presetMarks: Schema.Record$<typeof Schema.String, Schema.Struct<{
|
|
477
521
|
closedSeasons: Schema.mutable<Schema.Array$<Schema.Schema<{
|
package/dist/pages/app.schema.js
CHANGED
|
@@ -196,6 +196,29 @@ export const ManageQuestionRatingsPageSchema = Schema.Struct({
|
|
|
196
196
|
initialHasMore: Schema.Boolean,
|
|
197
197
|
page: Schema.Number,
|
|
198
198
|
});
|
|
199
|
+
const BankQuestionRowSchema = Schema.Struct({
|
|
200
|
+
id: Schema.String,
|
|
201
|
+
excerpt: Schema.String,
|
|
202
|
+
source: Schema.Literal("built-in", "promoted"),
|
|
203
|
+
category: Schema.String,
|
|
204
|
+
/** The stored question type, e.g. `text_choice`. */
|
|
205
|
+
type: Schema.String,
|
|
206
|
+
difficulty: Schema.NullOr(Schema.Literal("easy", "medium", "hard")),
|
|
207
|
+
/** At least one revision exists. */
|
|
208
|
+
edited: Schema.Boolean,
|
|
209
|
+
});
|
|
210
|
+
/** Body of `GET /api/pages/manage/bank`. */
|
|
211
|
+
export const ManageBankPageSchema = Schema.Struct({
|
|
212
|
+
initial: Arr(BankQuestionRowSchema),
|
|
213
|
+
initialHasMore: Schema.Boolean,
|
|
214
|
+
page: Schema.Number,
|
|
215
|
+
/** The search term the page was loaded with; empty for none. */
|
|
216
|
+
q: Schema.String,
|
|
217
|
+
/** The category name the page was filtered to; empty for all. */
|
|
218
|
+
category: Schema.String,
|
|
219
|
+
/** Category names offered by the filter, A to Z. */
|
|
220
|
+
categories: Arr(Schema.String),
|
|
221
|
+
});
|
|
199
222
|
const AuthoredQuestionRowSchema = Schema.Struct({
|
|
200
223
|
id: Schema.String,
|
|
201
224
|
category: Schema.String,
|
|
@@ -259,6 +282,7 @@ const QuizPresetSchema = Schema.Struct({
|
|
|
259
282
|
scoring_mode: Schema.NullOr(Schema.String),
|
|
260
283
|
access_mode: Schema.String,
|
|
261
284
|
max_players: Schema.NullOr(Schema.Number),
|
|
285
|
+
undo_enabled: Schema.Number,
|
|
262
286
|
created_at: Schema.String,
|
|
263
287
|
});
|
|
264
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);
|
|
@@ -53,6 +53,12 @@ export declare const MediaSchema: Schema.Struct<{
|
|
|
53
53
|
licenceUrl: Schema.optional<Schema.filter<Schema.filter<typeof Schema.String>>>;
|
|
54
54
|
}>>>;
|
|
55
55
|
}>;
|
|
56
|
+
/** Max characters in a question source URL. */
|
|
57
|
+
export declare const SOURCE_URL_MAX_LEN = 2048;
|
|
58
|
+
/** Max characters in a question source label. */
|
|
59
|
+
export declare const SOURCE_LABEL_MAX_LEN = 200;
|
|
60
|
+
/** Max characters in a post-answer note. */
|
|
61
|
+
export declare const POST_ANSWER_NOTE_MAX_LEN = 160;
|
|
56
62
|
export declare const QuestionSourceSchema: Schema.Struct<{
|
|
57
63
|
url: Schema.filter<Schema.filter<typeof Schema.String>>;
|
|
58
64
|
label: Schema.optional<Schema.filter<typeof Schema.String>>;
|
package/dist/question.schema.js
CHANGED
|
@@ -77,13 +77,19 @@ export const MediaSchema = Schema.Struct({
|
|
|
77
77
|
// Security: source.url is stored in the DB and read back on results/history
|
|
78
78
|
// pages; cap to match imageUrl's bound (curator.schema.ts) since both are
|
|
79
79
|
// URLs of the same practical size.
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
/** Max characters in a question source URL. */
|
|
81
|
+
export const SOURCE_URL_MAX_LEN = 2048;
|
|
82
|
+
/** Max characters in a question source label. */
|
|
83
|
+
export const SOURCE_LABEL_MAX_LEN = 200;
|
|
84
|
+
/** Max characters in a post-answer note. */
|
|
85
|
+
export const POST_ANSWER_NOTE_MAX_LEN = 160;
|
|
86
|
+
const HttpsUrlSchema = Schema.String.pipe(Schema.maxLength(SOURCE_URL_MAX_LEN, {
|
|
87
|
+
message: () => `Source URL must be ${SOURCE_URL_MAX_LEN} characters or fewer`,
|
|
82
88
|
}), Schema.filter(isHttpsUrl, { message: () => "Source URL must be https" }));
|
|
83
89
|
export const QuestionSourceSchema = Schema.Struct({
|
|
84
90
|
url: HttpsUrlSchema,
|
|
85
|
-
label: Schema.optional(Schema.String.pipe(Schema.maxLength(
|
|
86
|
-
message: () =>
|
|
91
|
+
label: Schema.optional(Schema.String.pipe(Schema.maxLength(SOURCE_LABEL_MAX_LEN, {
|
|
92
|
+
message: () => `Source label must be ${SOURCE_LABEL_MAX_LEN} characters or fewer`,
|
|
87
93
|
}))),
|
|
88
94
|
});
|
|
89
95
|
const isQuestionSource = Schema.is(QuestionSourceSchema);
|
|
@@ -97,8 +103,8 @@ export const StoredQuestionSourceSchema = Schema.transform(Schema.Unknown, Schem
|
|
|
97
103
|
decode: (source) => (isQuestionSource(source) ? source : undefined),
|
|
98
104
|
encode: (source) => source,
|
|
99
105
|
});
|
|
100
|
-
export const PostAnswerNoteSchema = Schema.String.pipe(Schema.maxLength(
|
|
101
|
-
message: () =>
|
|
106
|
+
export const PostAnswerNoteSchema = Schema.String.pipe(Schema.maxLength(POST_ANSWER_NOTE_MAX_LEN, {
|
|
107
|
+
message: () => `Post-answer note must be ${POST_ANSWER_NOTE_MAX_LEN} characters or fewer`,
|
|
102
108
|
}));
|
|
103
109
|
const MetadataSchema = Schema.Struct({
|
|
104
110
|
source: SourceSchema,
|
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;
|