Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Protocol: Add undocumented message types #9

Merged
merged 11 commits into from
Jun 2, 2021
50 changes: 38 additions & 12 deletions protocol/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,15 @@ export namespace Protocol {
export type NotificationTitle = string & As<'NotificationTitle'>;
/** Token used to determine whether a notification should highlight a user. */
export type HighlightToken = string & As<'HighlightToken'>;
/** Group name matched by `|tempnotify|` and `|tempnotifyoff|` messages. */
export type TempNotifyName = string & As<'TempNotifyName'>;

/** HTML which should be sanitized before display. */
export type HTML = string & As<'HTML'>;
/** A name to allow for matching two different `|uhtml|` messages. */
export type UHTMLName = string & As<'UHTMLName'>;
/** CSS selector name used in `|selectorhtml|` messages. */
export type SelectorName = string & As<'SelectorName'>;
/** A string which should be parsed as JSON. */
export type JSON = string & As<'JSON'>;

Expand Down Expand Up @@ -651,6 +655,21 @@ export namespace Protocol {
'|controlshtml|': readonly ['controlshtml', HTML];
'|fieldhtml|': readonly ['fieldhtml', HTML];
'|debug|': readonly ['debug', Message];
'|deinit|': readonly ['deinit'];
'|selectorhtml|': readonly ['selectorhtml', SelectorName, HTML];
'|refresh|': readonly ['refresh'];
'|tempnotify|': readonly [
'tempnotify', TempNotifyName, Message, ...[] | [Message] | [Message, string]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's going on with this type? The ...[] is kind of confusing to me, as it the array nesting. Can this be simplified?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's parsed as ...([] | [Message] | [Message, string]) due to operator precedence and tuple spreading, though some style guides enforce the parentheses anyway for clarity. Could do that or manually split it into a 3-array union, your call.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the parens will help at the bare minimum.

['tempnotify', TempNotifyName, Message] | ['tempnotify', TempNotifyName, Message, Message] | ['tempnotify', TempNotifyName, Message, Message, string] seems the most clear (unless I'm mistaking what this type actually is?). ['tempnotify', TempNotifyName, Message, Message?, string?] seems less precise but possibly how I might type this if I were doing it, as I'm not positive the extra precision really matters. Up to you which route you want to go, but please add parens if you want to keep the current approach :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ? suffix allows for undefined and/or skipping the tuple element, so that might not be ideal. Probably best to expand it into the type union then.

Will do this for |expire| as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's less accurate, but we're also trying to put super rigorous types on top of something which is basically just raw strings on the PS side, so I'm usually not to bothered about dropping an ? for convenience because PS is more likely to break things in the protocol than the undefined is going to cause problems for people :).

Technically its also the case that for like ['expire'] you can still index at 1 and get undefined, so really the main difference with ? is that you're basically implying the array should be length 2 with the second element occasionally undefined, though if I really wanted to be explicit about that I'd probably go Message | undefined (its also the case that nothing in the protocol can be undefined because we can't translate undefined over the wire).

tl;dr pedantically yes, the ? implies some things different than the union, but in practice I'm not too hot and bothered by it in the context of the PS protocol :)

];
'|tempnotifyoff|': readonly ['tempnotifyoff', TempNotifyName];
'|noinit|':
| readonly ['noinit', 'joinfailed' | 'namerequired' | 'nonexistent', Message]
| readonly ['noinit', 'rename', RoomID, RoomTitle];
'|hidelines|':
| readonly ['hidelines', 'delete' | 'hide', ID, Num]
| readonly ['hidelines', 'unlink', ID];
'|expire|': readonly ['expire', Message?];
'|askreg|': readonly ['askreg', ID];
}

export interface TournamentArgs {
Expand Down Expand Up @@ -1663,8 +1682,13 @@ export type MoveName = Protocol.MoveName;
export type Message = Protocol.Message;
export type Timestamp = Protocol.Timestamp;

export type NotificationTitle = Protocol.NotificationTitle;
export type HighlightToken = Protocol.HighlightToken;
export type TempNotifyName = Protocol.TempNotifyName;

export type HTML = Protocol.HTML;
export type UHTMLName = Protocol.UHTMLName;
export type SelectorName = Protocol.SelectorName;
export type JSON = Protocol.JSON;

export type RoomID = Protocol.RoomID;
Expand Down Expand Up @@ -1803,18 +1827,20 @@ export const Protocol = new class {
'|nametaken|': 1, '|challstr|': 1, '|updateuser|': 1, '|formats|': 1, '|updatesearch|': 1,
'|message|': 1, '|updatechallenges|': 1, '|queryresponse|': 1, '|unlink|': 1, '|raw|': 1,
'|error|': 1, '|bigerror|': 1, '|chatmsg|': 1, '|chatmsg-raw|': 1, '|controlshtml|': 1,
'|fieldhtml|': 1, '|debug|': 1, '|tournament|create|': 1, '|tournament|update|': 1,
'|tournament|updateEnd|': 1, '|tournament|error|': 1, '|tournament|forceend|': 1,
'|tournament|join|': 1, '|tournament|leave|': 1, '|tournament|replace|': 1,
'|tournament|start|': 1, '|tournament|disqualify|': 1, '|tournament|battlestart|': 1,
'|tournament|battleend|': 1, '|tournament|end|': 1, '|tournament|scouting|': 1,
'|tournament|autostart|': 1, '|tournament|autodq|': 1, '|player|': 1, '|teamsize|': 1,
'|gametype|': 1, '|gen|': 1, '|tier|': 1, '|rated|': 1, '|seed|': 1, '|rule|': 1, '|split|': 1,
'|teampreview|': 1, '|clearpoke|': 1, '|poke|': 1, '|start|': 1, '|done|': 1, '|request|': 1,
'|inactive|': 1, '|inactiveoff|': 1, '|upkeep|': 1, '|turn|': 1, '|win|': 1, '|tie|': 1,
'|move|': 1, '|switch|': 1, '|drag|': 1, '|detailschange|': 1, '|replace|': 1, '|swap|': 1,
'|cant|': 1, '|faint|': 1, '|-formechange|': 1, '|-fail|': 1, '|-block|': 1, '|-notarget|': 1,
'|-miss|': 1, '|-damage|': 1, '|-heal|': 1, '|-sethp|': 1, '|-status|': 1, '|-curestatus|': 1,
'|fieldhtml|': 1, '|debug|': 1, '|deinit|': 1, '|selectorhtml|': 1, '|refresh|': 1,
'|tempnotify|': 1, '|tempnotifyoff|': 1, '|noinit|': 1, '|hidelines|': 1, '|expire|': 1,
'|askreg|': 1, '|tournament|create|': 1, '|tournament|update|': 1, '|tournament|updateEnd|': 1,
'|tournament|error|': 1, '|tournament|forceend|': 1, '|tournament|join|': 1,
'|tournament|leave|': 1, '|tournament|replace|': 1, '|tournament|start|': 1,
'|tournament|disqualify|': 1, '|tournament|battlestart|': 1, '|tournament|battleend|': 1,
'|tournament|end|': 1, '|tournament|scouting|': 1, '|tournament|autostart|': 1,
'|tournament|autodq|': 1, '|player|': 1, '|teamsize|': 1, '|gametype|': 1, '|gen|': 1,
'|tier|': 1, '|rated|': 1, '|seed|': 1, '|rule|': 1, '|split|': 1, '|teampreview|': 1,
'|clearpoke|': 1, '|poke|': 1, '|start|': 1, '|done|': 1, '|request|': 1, '|inactive|': 1,
'|inactiveoff|': 1, '|upkeep|': 1, '|turn|': 1, '|win|': 1, '|tie|': 1, '|move|': 1,
'|switch|': 1, '|drag|': 1, '|detailschange|': 1, '|replace|': 1, '|swap|': 1, '|cant|': 1,
'|faint|': 1, '|-formechange|': 1, '|-fail|': 1, '|-block|': 1, '|-notarget|': 1, '|-miss|': 1,
'|-damage|': 1, '|-heal|': 1, '|-sethp|': 1, '|-status|': 1, '|-curestatus|': 1,
'|-cureteam|': 1, '|-boost|': 1, '|-unboost|': 1, '|-setboost|': 1, '|-swapboost|': 1,
'|-invertboost|': 1, '|-clearboost|': 1, '|-clearallboost|': 1, '|-clearpositiveboost|': 1,
'|-ohko|': 1, '|-clearnegativeboost|': 1, '|-copyboost|': 1, '|-weather|': 1,
Expand Down
44 changes: 44 additions & 0 deletions protocol/src/verifier/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,50 @@ class Handler implements Required<Protocol.Handler<boolean>> {
return args.length === 2 && !!args[1];
}

'|deinit|'(args: Args['|deinit|']) {
return args.length === 1;
}

'|selectorhtml|'(args: Args['|selectorhtml|']) {
return args.length === 3 && !!args[1] && !!args[2];
}

'|refresh|'(args: Args['|refresh|']) {
return args.length === 1;
}

'|tempnotify|'(args: Args['|tempnotify|']) {
return args.length >= 3 && !!args[1] && !!args[2] &&
(args.length === 3 ||
(args.length >= 4 && !!args[3] &&
(args.length === 4 || (args.length === 5 && !!args[4]))));
}

'|tempnotifyoff|'(args: Args['|tempnotifyoff|']) {
return args.length === 2 && !!args[1];
}

'|noinit|'(args: Args['|noinit|']) {
return args.length >= 3 && !!args[2] &&
(((args[1] === 'joinfailed' || args[1] === 'namerequired' || args[1] === 'nonexistent') &&
args.length === 3) ||
(args[1] === 'rename' && args.length === 4 && !!args[3]));
}

'|hidelines|'(args: Args['|hidelines|']) {
return args.length >= 3 && !!args[2] &&
(((args[1] === 'delete' || args[1] === 'hide') && args.length === 4 && verifyNum(args[3])) ||
(args[1] === 'unlink' && args.length === 3));
}

'|expire|'(args: Args['|expire|']) {
return args.length === 1 || (args.length === 2 && !!args[1]);
}

'|askreg|'(args: Args['|askreg|']) {
return args.length === 2 && !!args[1];
}

'|tournament|create|'(args: Args['|tournament|create|']) {
return args.length === 4 && !!args[2] && verifyNum(args[3] as Protocol.Num);
}
Expand Down