serverless-ircd 0.6.0 → 0.7.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/.github/workflows/ci.yml +6 -1
- package/.gitmodules +3 -0
- package/CHANGELOG.md +214 -0
- package/README.md +110 -23
- package/apps/aws-stack/package.json +1 -1
- package/apps/cf-tcp-container/package.json +1 -1
- package/apps/cf-worker/package.json +1 -1
- package/apps/cf-worker/src/origin-allowlist.ts +99 -0
- package/apps/cf-worker/src/worker.ts +89 -9
- package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +18 -0
- package/apps/cf-worker/tests/fixtures/web-dist/index.html +18 -0
- package/apps/cf-worker/tests/origin-allowlist.test.ts +101 -0
- package/apps/cf-worker/tests/smoke.test.ts +28 -2
- package/apps/cf-worker/tests/ws-origin.test.ts +89 -0
- package/apps/cf-worker/wrangler.test.toml +23 -0
- package/apps/cf-worker/wrangler.toml +54 -0
- package/apps/local-cli/package.json +1 -1
- package/apps/web/landing/index.html +217 -0
- package/apps/web/package.json +28 -0
- package/apps/web/scripts/build.mjs +125 -0
- package/apps/web/src/build-env.ts +43 -0
- package/apps/web/src/config-schema.ts +138 -0
- package/apps/web/static/config.json +28 -0
- package/apps/web/static/config.prod.json +28 -0
- package/apps/web/static/config.staging.json +28 -0
- package/apps/web/tests/build-env.test.ts +63 -0
- package/apps/web/tests/build-smoke.test.ts +103 -0
- package/apps/web/tests/config-schema.test.ts +432 -0
- package/apps/web/tests/workspace.test.ts +12 -0
- package/apps/web/tsconfig.json +10 -0
- package/apps/web/upstream/.browserslistrc +3 -0
- package/apps/web/upstream/.editorconfig +9 -0
- package/apps/web/upstream/.eslintignore +3 -0
- package/apps/web/upstream/.eslintrc.js +90 -0
- package/apps/web/upstream/.github/FUNDING.yml +12 -0
- package/apps/web/upstream/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- package/apps/web/upstream/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/apps/web/upstream/.github/workflows/push.yml +26 -0
- package/apps/web/upstream/.github/workflows/release.yml +33 -0
- package/apps/web/upstream/.github/workflows/release_manual.yml +55 -0
- package/apps/web/upstream/.prettierrc.js +9 -0
- package/apps/web/upstream/.stylelintignore +5 -0
- package/apps/web/upstream/.stylelintrc.js +21 -0
- package/apps/web/upstream/LICENSE +201 -0
- package/apps/web/upstream/README.md +76 -0
- package/apps/web/upstream/babel.config.js +25 -0
- package/apps/web/upstream/crowdin.yml +3 -0
- package/apps/web/upstream/index.html +35 -0
- package/apps/web/upstream/jest.config.js +7 -0
- package/apps/web/upstream/jsdoc.json +22 -0
- package/apps/web/upstream/package.json +80 -0
- package/apps/web/upstream/scripts/pre-commit +43 -0
- package/apps/web/upstream/src/components/App.vue +516 -0
- package/apps/web/upstream/src/components/AppSettings.vue +587 -0
- package/apps/web/upstream/src/components/AutoComplete.vue +284 -0
- package/apps/web/upstream/src/components/Avatar.vue +105 -0
- package/apps/web/upstream/src/components/AwayStatusIndicator.vue +77 -0
- package/apps/web/upstream/src/components/BufferKey.vue +121 -0
- package/apps/web/upstream/src/components/BufferSettings.vue +97 -0
- package/apps/web/upstream/src/components/Captcha.vue +85 -0
- package/apps/web/upstream/src/components/ChannelBanlist.vue +96 -0
- package/apps/web/upstream/src/components/ChannelInfo.vue +159 -0
- package/apps/web/upstream/src/components/ChannelInvitelist.vue +276 -0
- package/apps/web/upstream/src/components/ChannelList.vue +345 -0
- package/apps/web/upstream/src/components/Container.vue +401 -0
- package/apps/web/upstream/src/components/ContainerHeader.vue +522 -0
- package/apps/web/upstream/src/components/ControlInput.vue +1016 -0
- package/apps/web/upstream/src/components/LoadingAnimation.vue +104 -0
- package/apps/web/upstream/src/components/MediaViewer.vue +199 -0
- package/apps/web/upstream/src/components/MessageInfo.vue +192 -0
- package/apps/web/upstream/src/components/MessageList.vue +1028 -0
- package/apps/web/upstream/src/components/MessageListAvatar.vue +20 -0
- package/apps/web/upstream/src/components/MessageListMessageCompact.vue +361 -0
- package/apps/web/upstream/src/components/MessageListMessageInline.vue +257 -0
- package/apps/web/upstream/src/components/MessageListMessageModern.vue +430 -0
- package/apps/web/upstream/src/components/NetworkSettings.vue +528 -0
- package/apps/web/upstream/src/components/Nicklist.vue +309 -0
- package/apps/web/upstream/src/components/NicklistUser.vue +181 -0
- package/apps/web/upstream/src/components/NotConnected.vue +271 -0
- package/apps/web/upstream/src/components/SelfUser.vue +283 -0
- package/apps/web/upstream/src/components/ServerSelector.vue +162 -0
- package/apps/web/upstream/src/components/ServerView.vue +126 -0
- package/apps/web/upstream/src/components/SettingsAdvanced.vue +232 -0
- package/apps/web/upstream/src/components/SettingsAliases.vue +121 -0
- package/apps/web/upstream/src/components/Sidebar.vue +429 -0
- package/apps/web/upstream/src/components/SidebarAboutBuffer.vue +288 -0
- package/apps/web/upstream/src/components/SidebarState.vue +145 -0
- package/apps/web/upstream/src/components/StartupError.vue +35 -0
- package/apps/web/upstream/src/components/StateBrowser.vue +420 -0
- package/apps/web/upstream/src/components/StateBrowserBuffer.vue +124 -0
- package/apps/web/upstream/src/components/StateBrowserNetwork.vue +823 -0
- package/apps/web/upstream/src/components/StateBrowserUsermenu.vue +162 -0
- package/apps/web/upstream/src/components/TypingStatusIndicator.vue +34 -0
- package/apps/web/upstream/src/components/TypingUsersList.vue +56 -0
- package/apps/web/upstream/src/components/UrlEmbed.vue +149 -0
- package/apps/web/upstream/src/components/UserBox.vue +728 -0
- package/apps/web/upstream/src/components/inputtools/Emoji.vue +67 -0
- package/apps/web/upstream/src/components/inputtools/TextStyle.vue +142 -0
- package/apps/web/upstream/src/components/startups/CommonLayout.vue +208 -0
- package/apps/web/upstream/src/components/startups/CustomServer.vue +495 -0
- package/apps/web/upstream/src/components/startups/KiwiBnc.vue +361 -0
- package/apps/web/upstream/src/components/startups/Personal.vue +317 -0
- package/apps/web/upstream/src/components/startups/Welcome.vue +544 -0
- package/apps/web/upstream/src/components/startups/ZncLogin.vue +229 -0
- package/apps/web/upstream/src/components/utils/InputConfirm.vue +68 -0
- package/apps/web/upstream/src/components/utils/InputPrompt.vue +118 -0
- package/apps/web/upstream/src/components/utils/InputText.vue +172 -0
- package/apps/web/upstream/src/components/utils/IrcInput.vue +579 -0
- package/apps/web/upstream/src/components/utils/PluginWrapper.vue +26 -0
- package/apps/web/upstream/src/components/utils/TabbedView.vue +149 -0
- package/apps/web/upstream/src/components/utils/TransitionExpand.vue +97 -0
- package/apps/web/upstream/src/helpers/Colours.js +128 -0
- package/apps/web/upstream/src/helpers/IrcdDiffs.js +26 -0
- package/apps/web/upstream/src/helpers/Md5.js +193 -0
- package/apps/web/upstream/src/helpers/Misc.js +401 -0
- package/apps/web/upstream/src/helpers/TextFormatting.js +237 -0
- package/apps/web/upstream/src/libs/AliasRewriter.js +157 -0
- package/apps/web/upstream/src/libs/AudioManager.js +60 -0
- package/apps/web/upstream/src/libs/BouncerMiddleware.js +247 -0
- package/apps/web/upstream/src/libs/BouncerProvider.js +551 -0
- package/apps/web/upstream/src/libs/ChathistoryMiddleware.js +153 -0
- package/apps/web/upstream/src/libs/ConfigLoader.js +94 -0
- package/apps/web/upstream/src/libs/EmojiProvider.js +46 -0
- package/apps/web/upstream/src/libs/GlobalApi.js +346 -0
- package/apps/web/upstream/src/libs/IPC.js +51 -0
- package/apps/web/upstream/src/libs/InputHandler.js +921 -0
- package/apps/web/upstream/src/libs/IrcClient.js +1506 -0
- package/apps/web/upstream/src/libs/Logger.js +71 -0
- package/apps/web/upstream/src/libs/Message.js +169 -0
- package/apps/web/upstream/src/libs/MessageFormatter.js +364 -0
- package/apps/web/upstream/src/libs/MessageParser.js +249 -0
- package/apps/web/upstream/src/libs/Notifications.js +78 -0
- package/apps/web/upstream/src/libs/ServerConnection.js +270 -0
- package/apps/web/upstream/src/libs/ServerSession.js +102 -0
- package/apps/web/upstream/src/libs/SoundBleep.js +20 -0
- package/apps/web/upstream/src/libs/StatePersistence.js +90 -0
- package/apps/web/upstream/src/libs/ThemeManager.js +128 -0
- package/apps/web/upstream/src/libs/TypingMiddleware.js +105 -0
- package/apps/web/upstream/src/libs/WindowTitle.js +63 -0
- package/apps/web/upstream/src/libs/batchedAdd.js +74 -0
- package/apps/web/upstream/src/libs/bufferTools.js +177 -0
- package/apps/web/upstream/src/libs/polyfill/Element.closest.js +18 -0
- package/apps/web/upstream/src/libs/renderers/Html.js +113 -0
- package/apps/web/upstream/src/libs/settingTools.js +31 -0
- package/apps/web/upstream/src/libs/state/BufferState.js +703 -0
- package/apps/web/upstream/src/libs/state/NetworkState.js +158 -0
- package/apps/web/upstream/src/libs/state/UserState.js +89 -0
- package/apps/web/upstream/src/libs/state/common.js +26 -0
- package/apps/web/upstream/src/libs/state.js +961 -0
- package/apps/web/upstream/src/libs/storage/Local.js +51 -0
- package/apps/web/upstream/src/main.js +566 -0
- package/apps/web/upstream/src/res/autocompleteCommands.js +31 -0
- package/apps/web/upstream/src/res/configTemplates.js +373 -0
- package/apps/web/upstream/src/res/globalStyle.css +277 -0
- package/apps/web/upstream/src/res/kiwiLoadingLogo.png +0 -0
- package/apps/web/upstream/src/res/locales/app.af-ZA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ar-SA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.bg-BG.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.bs-BA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ca-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.cs-CZ.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.da-DK.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.de-DE.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.dev.po +1246 -0
- package/apps/web/upstream/src/res/locales/app.el-GR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.en-US.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-419.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-AR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-EM.po +536 -0
- package/apps/web/upstream/src/res/locales/app.es-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-US.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.eu-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.fi-FI.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.fr-FR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.gl-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.he-IL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.hi-IN.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.hu-HU.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.id-ID.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.it-IT.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ja-JP.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ko-KR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.nl-NL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.no-NO.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.pl-PL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.pt-BR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.pt-PT.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ro-RO.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ru-RU.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sl-SI.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sq-AL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sr-SP.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sv-SE.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.tr-TR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.uk-UA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.vi-VN.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.zh-CN.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.zh-TW.po +1145 -0
- package/apps/web/upstream/src/res/localesList.json +27 -0
- package/apps/web/upstream/src/res/logo.png +0 -0
- package/apps/web/upstream/src/thirdparty/about.html +43 -0
- package/apps/web/upstream/src/thirdparty/index.js +5 -0
- package/apps/web/upstream/src/thirdparty/kiwiirccom.vue +185 -0
- package/apps/web/upstream/static/config.json +28 -0
- package/apps/web/upstream/static/emoticons/smile.png +0 -0
- package/apps/web/upstream/static/favicon.png +0 -0
- package/apps/web/upstream/static/highlight.mp3 +0 -0
- package/apps/web/upstream/static/highlight.ogg +0 -0
- package/apps/web/upstream/static/locales/.gitignore +2 -0
- package/apps/web/upstream/static/plugins/customise.html.example +10 -0
- package/apps/web/upstream/static/themes/coffee/theme.css +145 -0
- package/apps/web/upstream/static/themes/common/base.css +1064 -0
- package/apps/web/upstream/static/themes/dark/theme.css +196 -0
- package/apps/web/upstream/static/themes/default/theme.css +92 -0
- package/apps/web/upstream/static/themes/elite/theme.css +248 -0
- package/apps/web/upstream/static/themes/grayfox/theme.css +143 -0
- package/apps/web/upstream/static/themes/nightswatch/theme.css +367 -0
- package/apps/web/upstream/static/themes/osprey/theme.css +115 -0
- package/apps/web/upstream/static/themes/radioactive/theme.css +1211 -0
- package/apps/web/upstream/static/themes/sky/theme.css +107 -0
- package/apps/web/upstream/tests/unit/BatchAdd.spec.js +174 -0
- package/apps/web/upstream/tests/unit/MessageParser.spec.js +125 -0
- package/apps/web/upstream/tests/unit/Misc.spec.js +24 -0
- package/apps/web/upstream/tests/unit/NetworkState.spec.js +58 -0
- package/apps/web/upstream/tests/unit/StartupError.spec.js +20 -0
- package/apps/web/upstream/vue.config.js +150 -0
- package/apps/web/upstream/yarn.lock +9767 -0
- package/apps/web/vitest.config.ts +13 -0
- package/biome.json +1 -0
- package/docs/Home.md +1 -0
- package/docs/WebClientGuide.md +535 -0
- package/package.json +2 -2
- package/packages/aws-adapter/package.json +1 -1
- package/packages/cf-adapter/package.json +1 -1
- package/packages/cf-adapter/tests/cf-harness.ts +12 -1
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/reports/mutation/mutation.html +342 -0
- package/packages/irc-core/tests/batch.test.ts +15 -0
- package/packages/irc-core/tests/numerics.test.ts +13 -0
- package/packages/irc-core/tests/parser.test.ts +109 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/scenarios.ts +24 -2
- package/tools/ci-hardening/package.json +1 -1
- package/tools/load-test/package.json +1 -1
- package/tools/tcp-ws-forwarder/package.json +1 -1
|
@@ -34,6 +34,21 @@ describe('wrapBatch — framing', () => {
|
|
|
34
34
|
expect(out).toEqual<RawLine[]>([]);
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
+
it('returns an empty BatchFrame carrying the framing metadata when lines is empty', () => {
|
|
38
|
+
// Empty batches are spec-forbidden and silently elided, but the returned
|
|
39
|
+
// array still advertises the id/type/start/body/end surface so callers
|
|
40
|
+
// can read the framing shape without a special-case branch.
|
|
41
|
+
const empty = wrapBatch([], 'ref7', 'chathistory', '#foo');
|
|
42
|
+
expect(empty).toHaveLength(0);
|
|
43
|
+
expect(empty.id).toBe('ref7');
|
|
44
|
+
expect(empty.type).toBe('chathistory');
|
|
45
|
+
expect(empty.start).toEqual({ text: '' });
|
|
46
|
+
expect(empty.body).toEqual([]);
|
|
47
|
+
expect(empty.end).toEqual({ text: '' });
|
|
48
|
+
// Non-enumerable props must not leak into JSON serialization.
|
|
49
|
+
expect(JSON.stringify(empty)).toBe('[]');
|
|
50
|
+
});
|
|
51
|
+
|
|
37
52
|
it('preserves the order of the inner lines', () => {
|
|
38
53
|
const lines = [L('one'), L('two'), L('three'), L('four')];
|
|
39
54
|
const out = wrapBatch(lines, 'x', 'test');
|
|
@@ -162,6 +162,19 @@ describe('numericToName', () => {
|
|
|
162
162
|
it('returns undefined for an unregistered code', () => {
|
|
163
163
|
expect(numericToName.get(600)).toBeUndefined();
|
|
164
164
|
});
|
|
165
|
+
|
|
166
|
+
it('round-trips every entry in the Numerics registry (reverse-lookup parity)', () => {
|
|
167
|
+
// Pin the map-construction arrow function: every [name, code] pair in
|
|
168
|
+
// the forward table MUST appear inverted in numericToName. A mutated
|
|
169
|
+
// mapper (e.g. `() => undefined`) would leave the reverse table empty
|
|
170
|
+
// or fail to construct, which this exhaustive check catches.
|
|
171
|
+
const entries = Object.entries(Numerics) as ReadonlyArray<[NumericCode, number]>;
|
|
172
|
+
expect(entries.length).toBeGreaterThan(0);
|
|
173
|
+
for (const [name, code] of entries) {
|
|
174
|
+
expect(numericToName.get(code)).toBe(name);
|
|
175
|
+
}
|
|
176
|
+
expect(numericToName.size).toBe(entries.length);
|
|
177
|
+
});
|
|
165
178
|
});
|
|
166
179
|
|
|
167
180
|
describe('isNumericCommand', () => {
|
|
@@ -133,31 +133,58 @@ describe('parse — framing tolerance', () => {
|
|
|
133
133
|
it('strips a lone trailing LF', () => {
|
|
134
134
|
expect(parse('PING :tok\n').params).toEqual(['tok']);
|
|
135
135
|
});
|
|
136
|
+
|
|
137
|
+
it('preserves CR/LF that appears inside the trailing param', () => {
|
|
138
|
+
// The CR/LF strip regex is anchored to the END of the line only; a CR/LF
|
|
139
|
+
// embedded in a trailing param must survive into the parsed value.
|
|
140
|
+
expect(parse('PRIVMSG #foo :a\rb').params[1]).toBe('a\rb');
|
|
141
|
+
expect(parse('PRIVMSG #foo :a\nb').params[1]).toBe('a\nb');
|
|
142
|
+
});
|
|
136
143
|
});
|
|
137
144
|
|
|
138
145
|
describe('parse — malformed input', () => {
|
|
146
|
+
// Each case asserts both the error class AND the diagnostic message text.
|
|
147
|
+
// The message assertion is what pins the failure to the *correct* guard:
|
|
148
|
+
// several guards fall through to a sibling throw of the same class, so a
|
|
149
|
+
// bare `toThrow(IrcParseError)` would miss removed/disabled guards.
|
|
150
|
+
|
|
139
151
|
it('throws IrcParseError on empty input', () => {
|
|
140
152
|
expect(() => parse('')).toThrow(IrcParseError);
|
|
153
|
+
expect(() => parse('')).toThrow('empty message');
|
|
141
154
|
});
|
|
142
155
|
|
|
143
156
|
it('throws IrcParseError on whitespace-only input', () => {
|
|
144
157
|
expect(() => parse(' ')).toThrow(IrcParseError);
|
|
158
|
+
expect(() => parse(' ')).toThrow('invalid command token');
|
|
145
159
|
});
|
|
146
160
|
|
|
147
161
|
it('throws IrcParseError when only a source prefix is present', () => {
|
|
148
162
|
expect(() => parse(':nick!user@host')).toThrow(IrcParseError);
|
|
163
|
+
expect(() => parse(':nick!user@host')).toThrow('source prefix with no command');
|
|
149
164
|
});
|
|
150
165
|
|
|
151
166
|
it('throws IrcParseError when a source is followed by no command', () => {
|
|
152
167
|
expect(() => parse(':irc.example.com ')).toThrow(IrcParseError);
|
|
168
|
+
expect(() => parse(':irc.example.com ')).toThrow('missing command');
|
|
153
169
|
});
|
|
154
170
|
|
|
155
171
|
it('throws IrcParseError when a tag section has no body', () => {
|
|
156
172
|
expect(() => parse('@time=now')).toThrow(IrcParseError);
|
|
173
|
+
expect(() => parse('@time=now')).toThrow('tag section with no message body');
|
|
157
174
|
});
|
|
158
175
|
|
|
159
176
|
it('throws IrcParseError on an invalid command token', () => {
|
|
160
177
|
expect(() => parse('12 hi')).toThrow(IrcParseError);
|
|
178
|
+
expect(() => parse('12 hi')).toThrow('invalid command token');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('sets the error `name` to "IrcParseError" for instanceof-equivalent checks', () => {
|
|
182
|
+
try {
|
|
183
|
+
parse('');
|
|
184
|
+
expect.fail('expected parse to throw');
|
|
185
|
+
} catch (err) {
|
|
186
|
+
expect((err as Error).name).toBe('IrcParseError');
|
|
187
|
+
}
|
|
161
188
|
});
|
|
162
189
|
});
|
|
163
190
|
|
|
@@ -203,3 +230,85 @@ describe('parse — RFC 1459 §2.3 input line-length cap', () => {
|
|
|
203
230
|
}
|
|
204
231
|
});
|
|
205
232
|
});
|
|
233
|
+
|
|
234
|
+
describe('parse — command token grammar (anchored regex)', () => {
|
|
235
|
+
// COMMAND_RE is anchored at both ends (^...$). A command token must be
|
|
236
|
+
// EITHER all letters OR exactly three digits — mixed alphanumerics are a
|
|
237
|
+
// protocol violation. These tests pin both anchors: removing ^ or $
|
|
238
|
+
// would let the partial-match mutations through.
|
|
239
|
+
|
|
240
|
+
it('rejects a command token with trailing digits (end anchor)', () => {
|
|
241
|
+
expect(() => parse('PRIVMSG123 #foo :hi')).toThrow(IrcParseError);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('rejects a command token with leading digits (start anchor)', () => {
|
|
245
|
+
expect(() => parse('123PRIVMSG #foo :hi')).toThrow(IrcParseError);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('rejects a two-digit numeric command (length pin)', () => {
|
|
249
|
+
expect(() => parse('12 #foo')).toThrow(IrcParseError);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('parse — source prefix shape (strict property presence)', () => {
|
|
254
|
+
// `user` and `host` must be ABSENT (not merely `undefined`) when the
|
|
255
|
+
// source has no `!user` / `@host` component. `toEqual` ignores
|
|
256
|
+
// undefined-valued properties, so these mutants would survive a normal
|
|
257
|
+
// equality check; `toStrictEqual` does not.
|
|
258
|
+
|
|
259
|
+
it('omits user AND host from a bare-servername source', () => {
|
|
260
|
+
expect(parse(':irc.example.com NOTICE * :hi').source).toStrictEqual({
|
|
261
|
+
name: 'irc.example.com',
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('omits host from a nick!user source (no @host)', () => {
|
|
266
|
+
expect(parse(':nick!user PRIVMSG #foo :hi').source).toStrictEqual({
|
|
267
|
+
name: 'nick',
|
|
268
|
+
user: 'user',
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('omits user from a nick@host source (no !user)', () => {
|
|
273
|
+
expect(parse(':nick@host PRIVMSG #foo :hi').source).toStrictEqual({
|
|
274
|
+
name: 'nick',
|
|
275
|
+
host: 'host',
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('does not set a source property on a source-less message', () => {
|
|
280
|
+
const msg = parse('PRIVMSG #foo :hi');
|
|
281
|
+
expect('source' in msg).toBe(false);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe('parse — middle parameter whitespace collapsing', () => {
|
|
286
|
+
it('drops empty params produced by runs of spaces between middles', () => {
|
|
287
|
+
// The split-then-filter pipeline removes the empty strings that
|
|
288
|
+
// consecutive spaces generate. Removing the filter (or mutating the
|
|
289
|
+
// predicate to a tautology) would leak '' entries here.
|
|
290
|
+
expect(parse(':nick JOIN #a #b').params).toEqual(['#a', '#b']);
|
|
291
|
+
expect(parse(':nick JOIN #a #b ').params).toEqual(['#a', '#b']);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
describe('unescapeTagValue — boundary conditions', () => {
|
|
296
|
+
// Imported indirectly through parse() tag values; these pin the loop
|
|
297
|
+
// bounds so off-by-one mutations on the backslash lookahead can't survive.
|
|
298
|
+
|
|
299
|
+
it('keeps a lone trailing backslash verbatim', () => {
|
|
300
|
+
// `\` at the end with no following char must not read past the end of
|
|
301
|
+
// the string and must be emitted literally.
|
|
302
|
+
expect(parse('@a=foo\\ PRIVMSG #foo :hi').tags.a).toBe('foo\\');
|
|
303
|
+
expect(parse('@a=\\ PRIVMSG #foo :hi').tags.a).toBe('\\');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('handles a tag value that is only a backslash', () => {
|
|
307
|
+
expect(parse('@a=\\ PRIVMSG #foo :hi').tags.a).toBe('\\');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('decodes a trailing escape sequence with no following character partially', () => {
|
|
311
|
+
// A complete `\\` pair decodes to a single backslash.
|
|
312
|
+
expect(parse('@a=x\\\\ PRIVMSG #foo :hi').tags.a).toBe('x\\');
|
|
313
|
+
});
|
|
314
|
+
});
|
|
@@ -48,6 +48,21 @@ export function runIrcScenarios(factories: readonly IrcHarnessFactory[]): void {
|
|
|
48
48
|
return undefined;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Numeric error codes that PRIVMSG/NOTICE rejection paths would emit
|
|
53
|
+
* (401 nick-target only). RFC: NOTICE MUST NOT generate any of these.
|
|
54
|
+
* Used by the NOTICE scenario so a late-arriving MOTD/welcome numeric
|
|
55
|
+
* (e.g. 376) on a slow transport does not false-positive the check.
|
|
56
|
+
*/
|
|
57
|
+
const NOTICE_ERROR_CODES: ReadonlySet<string> = new Set([
|
|
58
|
+
'401', // ERR_NOSUCHNICK
|
|
59
|
+
'403', // ERR_NOSUCHCHANNEL
|
|
60
|
+
'404', // ERR_CANNOTSENDTOCHAN
|
|
61
|
+
'411', // ERR_NORECIPIENT
|
|
62
|
+
'412', // ERR_NOTEXTTOSEND
|
|
63
|
+
'442', // ERR_NOTONCHANNEL
|
|
64
|
+
]);
|
|
65
|
+
|
|
51
66
|
/**
|
|
52
67
|
* Synchronization helper: alice (already in #chan) waits until she sees
|
|
53
68
|
* bob's JOIN broadcast. By the time this resolves, the runtime has also
|
|
@@ -442,8 +457,15 @@ export function runIrcScenarios(factories: readonly IrcHarnessFactory[]): void {
|
|
|
442
457
|
bob.received.length = 0;
|
|
443
458
|
await alice.send('NOTICE #notice :quiet note');
|
|
444
459
|
await bob.waitForLine((l) => l.includes('NOTICE #notice :quiet note'));
|
|
445
|
-
|
|
446
|
-
|
|
460
|
+
// RFC: NOTICE MUST NOT generate an error reply. Check only the
|
|
461
|
+
// NOTICE-relevant error numerics — not any numeric — so a delayed
|
|
462
|
+
// MOTD/welcome line draining through a slow transport (e.g. the CF
|
|
463
|
+
// WebSocket pool under load) does not false-positive the assertion.
|
|
464
|
+
const noticeErrors = alice.received.filter((l) => {
|
|
465
|
+
const code = numericOf(l);
|
|
466
|
+
return code !== undefined && NOTICE_ERROR_CODES.has(code);
|
|
467
|
+
});
|
|
468
|
+
expect(noticeErrors).toEqual([]);
|
|
447
469
|
await harness.close();
|
|
448
470
|
});
|
|
449
471
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serverless-ircd/tcp-ws-forwarder",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Local TCP to ws/wss forwarder so standard IRC clients can reach the WebSocket-only ServerlessIRCd transport",
|
|
6
6
|
"license": "BSD-3-Clause",
|