serverless-ircd 0.5.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 +40 -3
- package/.github/workflows/deploy-aws.yml +1 -2
- package/.github/workflows/deploy-cf-tcp.yml +1 -5
- package/.github/workflows/deploy-cf.yml +1 -2
- package/.gitmodules +3 -0
- package/CHANGELOG.md +474 -0
- package/README.md +219 -22
- package/apps/aws-stack/README.md +33 -0
- package/apps/aws-stack/package.json +1 -1
- package/apps/aws-stack/src/aws-stack.ts +74 -9
- package/apps/aws-stack/tests/coverage-command.test.ts +155 -0
- package/apps/aws-stack/tests/stack.test.ts +2 -2
- package/apps/aws-stack/tests/synth-no-bundle.test.ts +129 -0
- package/apps/aws-stack/tests/vitest.setup.ts +18 -0
- package/apps/aws-stack/vitest.config.ts +3 -0
- package/apps/cf-tcp-container/package.json +1 -1
- package/apps/cf-tcp-container/src/config-loader.ts +6 -7
- package/apps/cf-tcp-container/src/container-server.ts +3 -0
- package/apps/cf-tcp-container/tests/config-loader.test.ts +10 -3
- package/apps/cf-tcp-container/wrangler.toml +1 -1
- package/apps/cf-worker/package.json +2 -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/vitest.config.ts +1 -1
- package/apps/cf-worker/wrangler.test.toml +23 -0
- package/apps/cf-worker/wrangler.toml +59 -3
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +6 -6
- package/apps/local-cli/src/main.ts +20 -0
- package/apps/local-cli/src/server.ts +9 -7
- package/apps/local-cli/tests/e2e.test.ts +112 -28
- package/apps/local-cli/tests/tcp.test.ts +71 -19
- package/apps/local-cli/tests/ws-subprotocol.test.ts +12 -7
- 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 +2 -0
- package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
- package/docs/ADR-002-location-of-authority.md +82 -0
- package/docs/ADR-003-durable-object-sharding.md +110 -0
- package/docs/ADR-004-dynamodb-schema.md +96 -0
- package/docs/ADR-005-wss-only-transport-v1.md +83 -0
- package/docs/ADR-006-sasl-mechanism-scope.md +96 -0
- package/docs/ADR-007-deterministic-ports.md +82 -0
- package/docs/ADR-008-monorepo-tooling.md +60 -0
- package/docs/ADR-009-dual-transport-wss-and-irc-tls.md +138 -0
- package/docs/ADR-Index.md +15 -0
- package/docs/AWS-Adapter-Architecture.md +496 -0
- package/docs/AWS-Deployment.md +1275 -0
- package/docs/AWS-TCP-Deployment.md +410 -0
- package/docs/Cloudflare-Deployment-Guide.md +719 -0
- package/docs/Cloudflare-TCP-Deployment.md +510 -0
- package/docs/Home.md +16 -0
- package/docs/Observability.md +87 -0
- package/docs/PlanExtensions.md +812 -0
- package/docs/PlanIRCv3Websocket.md +503 -0
- package/docs/PlanWebClient.md +451 -0
- package/docs/Release-Process.md +443 -0
- package/docs/WebClientGuide.md +535 -0
- package/package.json +2 -2
- package/packages/aws-adapter/package.json +1 -1
- package/packages/aws-adapter/src/handlers/default.ts +3 -0
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +9 -0
- package/packages/aws-adapter/src/serialize.ts +29 -1
- package/packages/aws-adapter/tests/account-store-dynamo.test.ts +49 -8
- package/packages/aws-adapter/tests/account-store.test.ts +4 -7
- package/packages/aws-adapter/tests/aws-harness.ts +5 -2
- package/packages/aws-adapter/tests/aws-integration.test.ts +8 -4
- package/packages/aws-adapter/tests/aws-runtime.test.ts +79 -0
- package/packages/aws-adapter/tests/config-loader.test.ts +11 -2
- package/packages/aws-adapter/tests/connect.test.ts +9 -9
- package/packages/aws-adapter/tests/disconnect-fanout.test.ts +3 -4
- package/packages/aws-adapter/tests/global-setup.ts +55 -30
- package/packages/aws-adapter/tests/gone-exception.test.ts +4 -9
- package/packages/aws-adapter/tests/handlers.test.ts +90 -4
- package/packages/aws-adapter/tests/message-store.test.ts +6 -5
- package/packages/aws-adapter/tests/nlb-stream.test.ts +60 -8
- package/packages/cf-adapter/package.json +2 -1
- package/packages/cf-adapter/src/connection-do.ts +3 -0
- package/packages/cf-adapter/src/serialize.ts +25 -4
- package/packages/cf-adapter/tests/cf-harness.ts +12 -1
- package/packages/cf-adapter/tests/cf-runtime.test.ts +269 -0
- package/packages/cf-adapter/tests/channel-do.test.ts +117 -0
- package/packages/cf-adapter/tests/config-loader.test.ts +49 -0
- package/packages/cf-adapter/tests/connection-do-pure.test.ts +91 -0
- package/packages/cf-adapter/tests/d1-account-store.test.ts +48 -0
- package/packages/cf-adapter/tests/registry-do.test.ts +104 -0
- package/packages/cf-adapter/tests/serialize.test.ts +308 -0
- package/packages/cf-adapter/vitest.config.ts +1 -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/src/caps/capabilities.ts +37 -3
- package/packages/irc-core/src/caps/index.ts +1 -0
- package/packages/irc-core/src/caps/sts.ts +84 -0
- package/packages/irc-core/src/commands/account-notify.ts +53 -0
- package/packages/irc-core/src/commands/away.ts +9 -3
- package/packages/irc-core/src/commands/cap.ts +23 -5
- package/packages/irc-core/src/commands/chathistory.ts +40 -11
- package/packages/irc-core/src/commands/index.ts +34 -0
- package/packages/irc-core/src/commands/isupport.ts +53 -0
- package/packages/irc-core/src/commands/mode.ts +8 -1
- package/packages/irc-core/src/commands/monitor.ts +327 -0
- package/packages/irc-core/src/commands/multiline.ts +256 -0
- package/packages/irc-core/src/commands/pre-away.ts +112 -0
- package/packages/irc-core/src/commands/privmsg.ts +19 -12
- package/packages/irc-core/src/commands/read-marker.ts +108 -0
- package/packages/irc-core/src/commands/registration.ts +8 -0
- package/packages/irc-core/src/commands/sasl.ts +24 -1
- package/packages/irc-core/src/commands/tagmsg.ts +71 -1
- package/packages/irc-core/src/commands/whois.ts +5 -0
- package/packages/irc-core/src/config.ts +65 -10
- package/packages/irc-core/src/effects.ts +14 -0
- package/packages/irc-core/src/ports.ts +389 -0
- package/packages/irc-core/src/protocol/index.ts +14 -0
- package/packages/irc-core/src/protocol/labeled-response.ts +133 -0
- package/packages/irc-core/src/protocol/numerics.ts +15 -0
- package/packages/irc-core/src/protocol/outbound.ts +16 -1
- package/packages/irc-core/src/protocol/standard-replies.ts +277 -0
- package/packages/irc-core/src/state/connection.ts +32 -1
- package/packages/irc-core/src/types.ts +112 -0
- package/packages/irc-core/stryker.commands.conf.json +1 -2
- package/packages/irc-core/tests/away-store.test.ts +73 -0
- package/packages/irc-core/tests/batch.test.ts +15 -0
- package/packages/irc-core/tests/caps/capabilities.test.ts +119 -2
- package/packages/irc-core/tests/caps/sts.test.ts +123 -0
- package/packages/irc-core/tests/commands/account-notify.test.ts +171 -0
- package/packages/irc-core/tests/commands/away.test.ts +85 -2
- package/packages/irc-core/tests/commands/cap.test.ts +165 -0
- package/packages/irc-core/tests/commands/chathistory.test.ts +148 -1
- package/packages/irc-core/tests/commands/echo-message.test.ts +9 -3
- package/packages/irc-core/tests/commands/isupport.test.ts +80 -0
- package/packages/irc-core/tests/commands/mode.test.ts +72 -0
- package/packages/irc-core/tests/commands/monitor.test.ts +411 -0
- package/packages/irc-core/tests/commands/multiline.test.ts +428 -0
- package/packages/irc-core/tests/commands/pre-away.test.ts +241 -0
- package/packages/irc-core/tests/commands/privmsg.test.ts +154 -3
- package/packages/irc-core/tests/commands/read-marker.test.ts +211 -0
- package/packages/irc-core/tests/commands/registration.test.ts +64 -0
- package/packages/irc-core/tests/commands/sasl.test.ts +116 -2
- package/packages/irc-core/tests/commands/tagmsg.test.ts +357 -0
- package/packages/irc-core/tests/commands/whois.test.ts +34 -0
- package/packages/irc-core/tests/config.test.ts +103 -13
- package/packages/irc-core/tests/labeled-response.test.ts +181 -0
- package/packages/irc-core/tests/message-tags.test.ts +114 -0
- package/packages/irc-core/tests/monitor-store.test.ts +200 -0
- package/packages/irc-core/tests/numerics.test.ts +13 -0
- package/packages/irc-core/tests/parser.test.ts +109 -0
- package/packages/irc-core/tests/read-marker-store.test.ts +108 -0
- package/packages/irc-core/tests/standard-replies.test.ts +412 -0
- package/packages/irc-core/tests/types.test.ts +27 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +450 -3
- package/packages/irc-server/src/dispatch.ts +108 -16
- package/packages/irc-server/tests/actor.test.ts +1432 -39
- package/packages/irc-server/tests/dispatch.test.ts +263 -2
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/index.ts +6 -0
- package/packages/irc-test-support/src/scenarios.ts +24 -2
- package/packages/irc-test-support/src/test-config.ts +54 -0
- package/packages/irc-test-support/tests/test-config.test.ts +51 -0
- package/tools/ci-hardening/package.json +1 -1
- package/tools/ci-hardening/tests/ddb-local.test.ts +82 -0
- package/tools/load-test/package.json +33 -0
- package/tools/load-test/src/client.ts +351 -0
- package/tools/load-test/src/config.ts +313 -0
- package/tools/load-test/src/harness.ts +116 -0
- package/tools/load-test/src/main.ts +120 -0
- package/tools/load-test/src/metrics.ts +168 -0
- package/tools/load-test/src/report.ts +106 -0
- package/tools/load-test/tests/client.test.ts +212 -0
- package/tools/load-test/tests/config.test.ts +152 -0
- package/tools/load-test/tests/framing.test.ts +37 -0
- package/tools/load-test/tests/harness.test.ts +165 -0
- package/tools/load-test/tests/metrics.test.ts +174 -0
- package/tools/load-test/tests/report.test.ts +161 -0
- package/tools/load-test/tests/smoke.test.ts +67 -0
- package/tools/load-test/tsconfig.build.json +12 -0
- package/tools/load-test/tsconfig.test.json +10 -0
- package/tools/load-test/vitest.config.ts +29 -0
- package/tools/tcp-ws-forwarder/package.json +1 -1
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
# Web Client Guide
|
|
2
|
+
|
|
3
|
+
End-to-end contributor/operator doc for the ServerlessIRCd web client: a
|
|
4
|
+
vendored **Kiwi IRC** SPA served at `/app/` and a static project landing
|
|
5
|
+
page served at `/` by the Cloudflare Worker. The browser opens a native
|
|
6
|
+
`wss://` WebSocket straight to the Worker's existing IRC-over-WebSocket
|
|
7
|
+
endpoint — no proxy, gateway, or transport adaptation in the hot path.
|
|
8
|
+
|
|
9
|
+
Cross-reference: `PLAN.md` §6.1 (CF mapping), `docs/PlanWebClient.md`
|
|
10
|
+
(design), `docs/Cloudflare-Deployment-Guide.md` (Worker deploy),
|
|
11
|
+
`apps/cf-worker/wrangler.toml`, `apps/web/`.
|
|
12
|
+
|
|
13
|
+
**Acceptance criterion (TICKET-139):** a new contributor can
|
|
14
|
+
`git submodule update --init`, `pnpm install`,
|
|
15
|
+
`pnpm --filter web build:staging`, and `wrangler deploy --env staging`
|
|
16
|
+
following only this doc.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. How the pieces fit
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
24
|
+
│ Browser │
|
|
25
|
+
│ GET / → landing page (project front door) │
|
|
26
|
+
│ GET /app/ → Kiwi IRC SPA (index.html + JS bundle) │
|
|
27
|
+
│ wss://.../ → one IRC message per WS text frame │
|
|
28
|
+
└──────────────────────────────────────────────────────────────┘
|
|
29
|
+
│ │
|
|
30
|
+
│ HTTPS (static assets) │ wss (IRC-over-WebSocket)
|
|
31
|
+
▼ ▼
|
|
32
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
33
|
+
│ apps/cf-worker (Cloudflare Worker) │
|
|
34
|
+
│ fetch(request, env): │
|
|
35
|
+
│ Upgrade: websocket → Origin check → ConnectionDO │
|
|
36
|
+
│ GET /health → plaintext liveness string │
|
|
37
|
+
│ else → env.ASSETS (apps/web/dist) │
|
|
38
|
+
└──────────────────────────────────────────────────────────────┘
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The Worker is the single origin for the landing page, the SPA, **and**
|
|
42
|
+
the WebSocket. Same-origin by default — which is what makes the CSWSH
|
|
43
|
+
defense in §5 almost zero-config. A Cloudflare Pages deployment of the
|
|
44
|
+
same `dist/` is a documented alternative for prod (see §7).
|
|
45
|
+
|
|
46
|
+
Path layout on the Worker:
|
|
47
|
+
|
|
48
|
+
| Path | Served by | Content |
|
|
49
|
+
|------------|--------------------------|----------------------------------|
|
|
50
|
+
| `/` | `[assets]` binding | `dist/index.html` (landing page) |
|
|
51
|
+
| `/app/` | `[assets]` binding | `dist/app/index.html` (Kiwi SPA) |
|
|
52
|
+
| `/app/static/...` | `[assets]` binding | Kiwi JS/CSS/themes + baked config |
|
|
53
|
+
| `/health` | `worker.ts` handler | Plaintext liveness string |
|
|
54
|
+
| `/` (WS upgrade) | `worker.ts` → `ConnectionDO` | IRC session |
|
|
55
|
+
|
|
56
|
+
The WS endpoint and the landing page share path `/`. The Worker runs
|
|
57
|
+
first for every request (`run_worker_first = true` in `wrangler.toml`)
|
|
58
|
+
so the `Upgrade: websocket` check fires before the asset platform
|
|
59
|
+
serves `/` — otherwise an upgrade would get a `200 OK` landing page
|
|
60
|
+
instead of a `101` switch.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 2. Prerequisites
|
|
65
|
+
|
|
66
|
+
| Requirement | Version / detail |
|
|
67
|
+
|---|---|
|
|
68
|
+
| Node.js | ≥ 24 (matches CI; `engines.node` in root `package.json`) |
|
|
69
|
+
| pnpm | 11.x (`packageManager` pin in root `package.json`) |
|
|
70
|
+
| yarn | Required **only** to build the SPA. Kiwi ships a `yarn.lock`; `corepack enable` provides yarn on any Node. |
|
|
71
|
+
| `wrangler` CLI | Comes from `apps/cf-worker/devDependencies`; no global install needed. |
|
|
72
|
+
| Cloudflare account | For deploy only (see `docs/Cloudflare-Deployment-Guide.md`). Local dev needs nothing. |
|
|
73
|
+
|
|
74
|
+
Confirm the local environment:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
node --version # v24.x or newer
|
|
78
|
+
corepack enable # lets the pinned pnpm + yarn run
|
|
79
|
+
pnpm --version # 11.x
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 3. Quickstart (clean checkout → deployed staging SPA)
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# 1. Pull submodules (docs wiki + Kiwi IRC upstream sources).
|
|
88
|
+
git submodule update --init
|
|
89
|
+
|
|
90
|
+
# 2. Install workspace deps.
|
|
91
|
+
pnpm install
|
|
92
|
+
|
|
93
|
+
# 3. Build the SPA + landing page (default config = staging target).
|
|
94
|
+
pnpm --filter web build:staging
|
|
95
|
+
# Equivalent: pnpm --filter @serverless-ircd/web build:staging
|
|
96
|
+
# → apps/web/dist/index.html (landing page)
|
|
97
|
+
# → apps/web/dist/app/index.html (Kiwi SPA)
|
|
98
|
+
# → apps/web/dist/app/static/config.json (baked, env-specific)
|
|
99
|
+
|
|
100
|
+
# 4. Build the rest of the workspace (Worker imports compiled dist/).
|
|
101
|
+
pnpm build
|
|
102
|
+
|
|
103
|
+
# 5. Serve everything locally (Worker + SPA + landing page).
|
|
104
|
+
pnpm --filter @serverless-ircd/cf-worker dev
|
|
105
|
+
# → http://localhost:8787/ landing page
|
|
106
|
+
# → http://localhost:8787/app/ Kiwi SPA
|
|
107
|
+
# → ws://localhost:8787/ IRC-over-WebSocket (same endpoint)
|
|
108
|
+
|
|
109
|
+
# 6. Deploy staging (Worker + assets in one command).
|
|
110
|
+
pnpm deploy:cf:staging
|
|
111
|
+
# → wrangler deploy --env staging
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Open `http://localhost:8787/app/` in a browser — Kiwi boots, reads
|
|
115
|
+
`/app/static/config.json`, and opens a `wss://` (or `ws://` in local
|
|
116
|
+
dev) connection to the same origin.
|
|
117
|
+
|
|
118
|
+
> **Forgot the submodule?** `apps/web/scripts/build.mjs` fails fast:
|
|
119
|
+
> `Kiwi upstream missing at .../upstream. Run
|
|
120
|
+
> 'git submodule update --init apps/web/upstream' first.`
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 4. The build pipeline (`apps/web/scripts/build.mjs`)
|
|
125
|
+
|
|
126
|
+
The build is an orchestrated layering over the upstream Kiwi build —
|
|
127
|
+
**we own no IRC logic**, only config + asset placement.
|
|
128
|
+
|
|
129
|
+
### 4.1 Per-environment config matrix
|
|
130
|
+
|
|
131
|
+
| `pnpm ...` command | Reads | Bakes into `dist/app/static/config.json` |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| `--filter web build` | `static/config.json` | default (staging-shaped) config |
|
|
134
|
+
| `--filter web build:staging` | `static/config.staging.json` | staging config |
|
|
135
|
+
| `--filter web build:prod` | `static/config.prod.json` | prod config |
|
|
136
|
+
|
|
137
|
+
`--env` is parsed by `src/build-env.ts`; an unknown value aborts the
|
|
138
|
+
build with a readable error. The selected file is validated against the
|
|
139
|
+
Zod schema (`src/config-schema.ts`) **before** it is baked, so a
|
|
140
|
+
malformed config fails the build rather than shipping a broken SPA.
|
|
141
|
+
|
|
142
|
+
### 4.2 What the build does, in order
|
|
143
|
+
|
|
144
|
+
1. **Verify the submodule** exists (`upstream/`), or fail with the
|
|
145
|
+
hint quoted above.
|
|
146
|
+
2. **`yarn install --frozen-lockfile`** inside `upstream/` (only if
|
|
147
|
+
`upstream/node_modules` is missing). `COREPACK_ROOT` is set so yarn
|
|
148
|
+
classic doesn't refuse on the pnpm-managed workspace root. On
|
|
149
|
+
Node ≥ 17 the OpenSSL legacy provider is enabled for webpack 5's md4
|
|
150
|
+
hashing.
|
|
151
|
+
3. **Patch `upstream/vue.config.js`** `publicPath: ''` →
|
|
152
|
+
`publicPath: '/app/'`, run `yarn build`, then **restore** the file
|
|
153
|
+
in `finally` so the submodule stays clean. This is why every hashed
|
|
154
|
+
asset URL comes out `/app/static/js/app.<hash>.js` instead of
|
|
155
|
+
`static/js/...`, which would 404 at root.
|
|
156
|
+
4. **Copy `upstream/dist/` → `apps/web/dist/app/`.**
|
|
157
|
+
5. **Rewrite bare `static/...` refs** in the baked `index.html` (e.g.
|
|
158
|
+
the favicon, which webpack doesn't run through `publicPath`) to
|
|
159
|
+
`/app/static/...`.
|
|
160
|
+
6. **Validate + bake the per-env config**: read
|
|
161
|
+
`static/config.<env>.json`, run `parseKiwiConfig`, re-serialise
|
|
162
|
+
(stable key order), write to `dist/app/static/config.json`.
|
|
163
|
+
7. **Copy the landing page** `landing/index.html` → `dist/index.html`.
|
|
164
|
+
|
|
165
|
+
Output is reproducible: same checkout + same `--env` → byte-identical
|
|
166
|
+
`dist/` (modulo upstream webpack hash stability).
|
|
167
|
+
|
|
168
|
+
### 4.3 The config schema (`src/config-schema.ts`)
|
|
169
|
+
|
|
170
|
+
The schema models only the fields ServerlessIRCd cares about; the rest
|
|
171
|
+
of Kiwi's surface is passed through untouched (`.passthrough()`).
|
|
172
|
+
|
|
173
|
+
```jsonc
|
|
174
|
+
// static/config.staging.json
|
|
175
|
+
{
|
|
176
|
+
"windowTitle": "ServerlessIRCd (staging)",
|
|
177
|
+
"startupOptions": {
|
|
178
|
+
"server": "{{hostname}}", // resolved to window.location.hostname at boot
|
|
179
|
+
"direct": true, // MUST be true — direct WS, no kiwiServer gateway
|
|
180
|
+
"tls": true, // MUST be true — wss:// (page is HTTPS)
|
|
181
|
+
"port": 443,
|
|
182
|
+
"channel": "#welcome",
|
|
183
|
+
"nick": "Guest"
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Field notes (see the schema docstrings for the full rationale):
|
|
189
|
+
|
|
190
|
+
- **`startupOptions.server`** — `{{hostname}}` (or `{{host}}` to keep
|
|
191
|
+
a non-default port) is a Kiwi template resolved by the browser to
|
|
192
|
+
`window.location.hostname` at load time. This is what lets one build
|
|
193
|
+
artifact target staging, prod, preview, and PR deploys without a
|
|
194
|
+
per-env hostname. A literal hostname is also accepted.
|
|
195
|
+
- **`startupOptions.direct`** — MUST be `true`. ServerlessIRCd is a
|
|
196
|
+
direct IRC-over-WebSocket server; the Kiwi default (`false`) routes
|
|
197
|
+
through a `kiwiServer` WebIRC gateway that nothing serves here.
|
|
198
|
+
- **`startupOptions.tls`** — MUST be `true` in any HTTPS deployment
|
|
199
|
+
(browsers refuse `ws://` on an HTTPS page).
|
|
200
|
+
- **`startupOptions.direct_path`** — the WS path. Kiwi reads
|
|
201
|
+
`direct_path` (NOT `path`); the Worker exposes its WS upgrade at
|
|
202
|
+
root, so this MUST be `/` if supplied. Optional — when omitted,
|
|
203
|
+
irc-framework builds `wss://host:port` (no path), which the browser
|
|
204
|
+
normalises to `wss://host:port/`.
|
|
205
|
+
- **`startupOptions.channel`** — must begin with `#` (matches
|
|
206
|
+
`CHANTYPES=#`); comma-separated lists are accepted.
|
|
207
|
+
|
|
208
|
+
A malformed config aborts the build with an aggregated error, e.g.:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
config.staging.json: Invalid Kiwi web config:
|
|
212
|
+
- startupOptions.port: port must be a valid TCP port (1-65535)
|
|
213
|
+
- startupOptions.channel: must begin with '#'
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 4.4 Tests
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
pnpm --filter web test # schema + build-env + workspace tests
|
|
220
|
+
pnpm --filter web typecheck
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The schema and build-env tests are pure (no Kiwi build needed). The
|
|
224
|
+
build-smoke tests (`tests/build-smoke.test.ts`) assert the **landing
|
|
225
|
+
page** source unconditionally and the `dist/` output only when a build
|
|
226
|
+
is present (`describe.skipIf(!built)`) — the Kiwi build is too heavy
|
|
227
|
+
for the unit suite, so run `pnpm --filter web build` first to exercise
|
|
228
|
+
those assertions.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## 5. CSWSH defense and the `WEB_ORIGINS` var
|
|
233
|
+
|
|
234
|
+
WebSocket upgrades do **not** follow the same-origin policy. A
|
|
235
|
+
malicious page can `new WebSocket('wss://irc.example.com/')` from the
|
|
236
|
+
victim's browser and drive an IRC session with the victim's
|
|
237
|
+
credentials (Cross-Site WebSocket Hijacking, CSWSH). The Worker
|
|
238
|
+
rejects upgrades whose `Origin` is not allowed — see
|
|
239
|
+
`apps/cf-worker/src/origin-allowlist.ts`.
|
|
240
|
+
|
|
241
|
+
### 5.1 Two enforcement modes (evaluated in order)
|
|
242
|
+
|
|
243
|
+
1. **Explicit allowlist** — when `WEB_ORIGINS` is set, only listed
|
|
244
|
+
origins proceed. Use this for cross-origin deployments (SPA on a
|
|
245
|
+
different domain than the Worker).
|
|
246
|
+
2. **Auto-derive (same-origin)** — when `WEB_ORIGINS` is unset/empty
|
|
247
|
+
(the default), the Worker compares the browser's `Origin` against
|
|
248
|
+
the request's own origin (`${protocol}//${host}`). If they match,
|
|
249
|
+
the page opening the WS is the same origin as the server → safe.
|
|
250
|
+
|
|
251
|
+
Non-browser clients (curl, WeeChat, the `tcp-ws-forwarder`, scripted
|
|
252
|
+
test harnesses) never send `Origin`; their upgrades pass through
|
|
253
|
+
unchanged in **both** modes, so existing tooling keeps working.
|
|
254
|
+
|
|
255
|
+
### 5.2 `WEB_ORIGINS` is optional
|
|
256
|
+
|
|
257
|
+
Because mode 2 (same-origin auto-derive) is the default and requires
|
|
258
|
+
zero per-env configuration, **`WEB_ORIGINS` is optional**. Most
|
|
259
|
+
deployments never need to set it: the SPA and the Worker are on the
|
|
260
|
+
same origin, so the auto-derive check passes for every browser and
|
|
261
|
+
rejects every cross-origin attempt. It works for `*.workers.dev`,
|
|
262
|
+
custom domains, and preview URLs alike.
|
|
263
|
+
|
|
264
|
+
Set `WEB_ORIGINS` only when the SPA is served from a **different**
|
|
265
|
+
origin than the Worker (e.g. a Cloudflare Pages deployment — see §7 —
|
|
266
|
+
or a separate static host). It is a comma-separated list of origins,
|
|
267
|
+
lowercased and whitespace-trimmed:
|
|
268
|
+
|
|
269
|
+
```toml
|
|
270
|
+
# apps/cf-worker/wrangler.toml — cross-origin example
|
|
271
|
+
[vars]
|
|
272
|
+
WEB_ORIGINS = "https://irc.example.com,https://web.example.com"
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
# or per-deploy via the secret store / dashboard env vars:
|
|
277
|
+
wrangler secret put WEB_ORIGINS --env staging
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### 5.3 Responses
|
|
281
|
+
|
|
282
|
+
| Scenario | Response |
|
|
283
|
+
|---|---|
|
|
284
|
+
| `Origin` allowed (allowlist or same-origin) | `101 Switching Protocols` → `ConnectionDO` |
|
|
285
|
+
| `Origin` header absent (non-browser client) | `101` (unchanged) |
|
|
286
|
+
| `Origin` present but not allowed | `403 Forbidden` (`WebSocket upgrade refused: the request Origin is not allowed.`) |
|
|
287
|
+
| No allowlist AND `Host` undeterminable | `403 Forbidden` (defensive fail-closed; does not fire on Cloudflare, which always sets `Host`) |
|
|
288
|
+
|
|
289
|
+
The 403 path is unit-tested in `apps/cf-worker/tests/ws-origin.test.ts`
|
|
290
|
+
and the pure policy in `origin-allowlist.test.ts`. There is no
|
|
291
|
+
fail-open path.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## 6. Local development
|
|
296
|
+
|
|
297
|
+
### 6.1 Serve the SPA from the Worker (`wrangler dev`)
|
|
298
|
+
|
|
299
|
+
This is the canonical local loop — same-origin, real `workerd`, real
|
|
300
|
+
Durable Objects:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
pnpm --filter web build # build the SPA + landing page
|
|
304
|
+
pnpm build # build the rest of the workspace
|
|
305
|
+
pnpm --filter @serverless-ircd/cf-worker dev
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Then:
|
|
309
|
+
|
|
310
|
+
- `http://localhost:8787/` — landing page.
|
|
311
|
+
- `http://localhost:8787/app/` — Kiwi SPA (reads
|
|
312
|
+
`/app/static/config.json`, opens `ws://localhost:8787/`).
|
|
313
|
+
- `http://localhost:8787/health` — plaintext liveness.
|
|
314
|
+
|
|
315
|
+
Smoke the WS path directly (no browser needed):
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
node apps/cf-worker/scripts/smoke.mjs
|
|
319
|
+
# → replays CONNECT/NICK/USER/JOIN/PRIVMSG/QUIT, asserts 001/376/353/366
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### 6.2 Iterate on the SPA config without rebuilding Kiwi
|
|
323
|
+
|
|
324
|
+
The Kiwi upstream build is the slow step. For a config-only change,
|
|
325
|
+
edit `static/config.staging.json`, then re-run
|
|
326
|
+
`pnpm --filter web build:staging` — step 2 of the build reuses
|
|
327
|
+
`upstream/node_modules` and skips straight to re-baking the config
|
|
328
|
+
(steps 4–6 are fast).
|
|
329
|
+
|
|
330
|
+
### 6.3 Iterate on the landing page
|
|
331
|
+
|
|
332
|
+
`landing/index.html` is pure static HTML + inline CSS, no JavaScript,
|
|
333
|
+
no build step. Edit it and re-run the build (it is copied verbatim to
|
|
334
|
+
`dist/index.html`); or just preview it directly in a browser — it has
|
|
335
|
+
no runtime dependencies.
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## 7. Optional: Cloudflare Pages instead of Worker assets
|
|
340
|
+
|
|
341
|
+
The build output (`apps/web/dist/`) is identical for both hosting
|
|
342
|
+
options; the choice is a deploy-time knob, not a build-time one. Use
|
|
343
|
+
Pages when you want to decouple static deploys from Worker deploys
|
|
344
|
+
(e.g. the frontend changes more often than the IRCd), or when prod
|
|
345
|
+
favors a dedicated static edge.
|
|
346
|
+
|
|
347
|
+
### 7.1 Set up Pages
|
|
348
|
+
|
|
349
|
+
1. Push the repo (or just `apps/web/dist/`) to a Pages-connected
|
|
350
|
+
branch, or use `wrangler pages deploy apps/web/dist`.
|
|
351
|
+
2. Set the Pages project's output directory to `apps/web/dist` and the
|
|
352
|
+
build command to `pnpm --filter web build:prod` (plus `pnpm build`
|
|
353
|
+
if the Worker is deployed from the same repo).
|
|
354
|
+
3. The Pages project is reachable at
|
|
355
|
+
`https://<project>.pages.dev` (or a custom domain).
|
|
356
|
+
|
|
357
|
+
### 7.2 Allow the Pages origin on the Worker
|
|
358
|
+
|
|
359
|
+
Because the SPA is now on a **different** origin than the Worker, the
|
|
360
|
+
same-origin auto-derive (§5.1 mode 2) will reject the browser's WS
|
|
361
|
+
upgrade. Set `WEB_ORIGINS` on the Worker to the Pages origin:
|
|
362
|
+
|
|
363
|
+
```toml
|
|
364
|
+
# apps/cf-worker/wrangler.toml
|
|
365
|
+
[vars]
|
|
366
|
+
WEB_ORIGINS = "https://<project>.pages.dev"
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
The SPA config's `startupOptions.server` still resolves to the
|
|
370
|
+
**Worker's** hostname (where the WS endpoint lives), not the Pages
|
|
371
|
+
hostname — so set it to a literal if you've pinned it, or keep
|
|
372
|
+
`{{hostname}}` only if Pages and the Worker share a domain.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## 8. Optional: `webircgateway` SockJS fallback
|
|
377
|
+
|
|
378
|
+
ServerlessIRCd already speaks the wire format browser Kiwi expects:
|
|
379
|
+
native WebSocket text frames, one IRC message per frame, with
|
|
380
|
+
`\r\n`-joined-frame tolerance. The Worker's WS endpoint at `/` is all
|
|
381
|
+
Kiwi needs. **You do not need `webircgateway` for normal operation.**
|
|
382
|
+
|
|
383
|
+
When is it useful? Corporate proxies and some mobile carriers block
|
|
384
|
+
raw `wss://` (or upgrade it in ways that break long-lived frames).
|
|
385
|
+
`webircgateway` provides a SockJS + Kiwi multi-server transport that
|
|
386
|
+
falls back to HTTP polling when raw WS is unavailable. If your users
|
|
387
|
+
report "can't connect from the office Wi-Fi", that's the trigger.
|
|
388
|
+
|
|
389
|
+
The gateway is vendored separately (see the `webircgateway/` submodule
|
|
390
|
+
if present in your checkout). Running it as a sidecar is a deployment
|
|
391
|
+
concern outside this doc's scope; the key point is that it is
|
|
392
|
+
**strictly opt-in**. The default SPA build does not reference it, and
|
|
393
|
+
no `kiwiServer` / gateway URL is baked into the config.
|
|
394
|
+
|
|
395
|
+
To wire it up when needed, point Kiwi's `startupOptions.kiwiServer`
|
|
396
|
+
at the gateway and set `startupOptions.direct: false` — the opposite
|
|
397
|
+
of the ServerlessIRCd default. Document this only for the affected
|
|
398
|
+
deployment; don't change the default config.
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## 9. Customising the landing page
|
|
403
|
+
|
|
404
|
+
The landing page (`apps/web/landing/index.html`) is the project's front
|
|
405
|
+
door: a visitor hitting `/` learns what ServerlessIRCd is and clicks
|
|
406
|
+
through to `/app/` to launch the client. It is intentionally
|
|
407
|
+
framework-free (pure HTML + inline CSS, no JS) so it renders without
|
|
408
|
+
JavaScript and costs nothing to serve.
|
|
409
|
+
|
|
410
|
+
To customise:
|
|
411
|
+
|
|
412
|
+
- **Copy/branding** — edit the `<header>`, `<main>`, and `<footer>`
|
|
413
|
+
blocks directly. The CSS variables at the top of the `<style>` block
|
|
414
|
+
(`--bg`, `--surface`, `--accent`, …) theme the whole page in one
|
|
415
|
+
place.
|
|
416
|
+
- **Links** — the page links to `/app/` (the SPA), `docs/`, and the
|
|
417
|
+
source repo. Update the repo URLs and the docs path to match your
|
|
418
|
+
deployment.
|
|
419
|
+
- **Accessibility** — keep the declared viewport meta tag and the
|
|
420
|
+
no-`<script>` invariant; both are asserted by
|
|
421
|
+
`tests/build-smoke.test.ts`. The build copies the file verbatim to
|
|
422
|
+
`dist/index.html`, so what you author is exactly what is served.
|
|
423
|
+
|
|
424
|
+
Re-run `pnpm --filter web build` (any `--env`) after edits; the build
|
|
425
|
+
test suite will fail loudly if you accidentally remove the `/app/`
|
|
426
|
+
link, the docs link, or the viewport tag.
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## 10. Troubleshooting
|
|
431
|
+
|
|
432
|
+
### 10.1 `Kiwi upstream missing at .../upstream`
|
|
433
|
+
|
|
434
|
+
The submodule wasn't initialised. Run:
|
|
435
|
+
|
|
436
|
+
```bash
|
|
437
|
+
git submodule update --init apps/web/upstream
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
The build fails fast with this hint rather than a confusing webpack
|
|
441
|
+
error.
|
|
442
|
+
|
|
443
|
+
### 10.2 SPA loads but never connects ("connection refused" / spinning)
|
|
444
|
+
|
|
445
|
+
Almost always a config issue. Open the browser devtools Network tab
|
|
446
|
+
and check the WS upgrade:
|
|
447
|
+
|
|
448
|
+
- **`ws://` on an HTTPS page** — `startupOptions.tls` must be `true`
|
|
449
|
+
(browsers refuse insecure WS from secure pages). The default configs
|
|
450
|
+
set it; a hand-edited config may not.
|
|
451
|
+
- **`direct: false`** — Kiwi is probing a `kiwiServer` gateway that
|
|
452
|
+
nothing serves. `startupOptions.direct` MUST be `true`.
|
|
453
|
+
- **Wrong server** — if you pinned a literal `server`, it must be the
|
|
454
|
+
Worker's hostname. `{{hostname}}` resolves to
|
|
455
|
+
`window.location.hostname` and is the safest default.
|
|
456
|
+
- **403 on upgrade** — the `Origin` was rejected (§5). Check
|
|
457
|
+
`WEB_ORIGINS` if you're cross-origin, or confirm the SPA and Worker
|
|
458
|
+
are same-origin.
|
|
459
|
+
|
|
460
|
+
### 10.3 Asset 404s at `/static/...` (instead of `/app/static/...`)
|
|
461
|
+
|
|
462
|
+
The `publicPath` patch in step 3 of the build didn't take. Confirm
|
|
463
|
+
`upstream/vue.config.js` still contains a `publicPath: ''` literal to
|
|
464
|
+
patch against (the build restores it after building). If upstream
|
|
465
|
+
changed the phrasing, the patch regex in `scripts/build.mjs` needs an
|
|
466
|
+
update.
|
|
467
|
+
|
|
468
|
+
### 10.4 `200 OK` instead of `101 Switching Protocols` on WS upgrade
|
|
469
|
+
|
|
470
|
+
The asset platform served the landing page for the upgrade request.
|
|
471
|
+
This means `run_worker_first = true` is missing or false in the
|
|
472
|
+
`[assets]` block of `wrangler.toml`. The WS endpoint and the landing
|
|
473
|
+
page share path `/`, so the Worker MUST run first for everything and
|
|
474
|
+
fall through to assets only for non-WS requests.
|
|
475
|
+
|
|
476
|
+
### 10.5 `Cannot find module '@serverless-ircd/cf-adapter'`
|
|
477
|
+
|
|
478
|
+
The workspace packages weren't built. Run `pnpm build` from the repo
|
|
479
|
+
root before `pnpm --filter web build` or `wrangler dev` — the build
|
|
480
|
+
script and the Worker entry import compiled `dist/` from the workspace
|
|
481
|
+
packages.
|
|
482
|
+
|
|
483
|
+
### 10.6 Build-smoke tests fail in CI but pass locally
|
|
484
|
+
|
|
485
|
+
The `dist/`-dependent assertions in `tests/build-smoke.test.ts` are
|
|
486
|
+
gated on `existsSync(dist/app/index.html)` and are skipped when the
|
|
487
|
+
SPA hasn't been built. CI runs `pnpm --filter web build` (or
|
|
488
|
+
`pnpm build`) before `pnpm --filter web test`, so the assertions fire
|
|
489
|
+
there. If you run `pnpm --filter web test` locally without building,
|
|
490
|
+
only the landing-page source assertions run — that's expected, not a
|
|
491
|
+
regression.
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## 11. Quick reference
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
# One-time / per-clone
|
|
499
|
+
corepack enable
|
|
500
|
+
git submodule update --init # docs + apps/web/upstream (Kiwi)
|
|
501
|
+
pnpm install
|
|
502
|
+
|
|
503
|
+
# Build the SPA + landing page (pick the env)
|
|
504
|
+
pnpm --filter web build # default config (staging-shaped)
|
|
505
|
+
pnpm --filter web build:staging # static/config.staging.json
|
|
506
|
+
pnpm --filter web build:prod # static/config.prod.json
|
|
507
|
+
|
|
508
|
+
# Local dev (Worker serves SPA + landing page + WS on one origin)
|
|
509
|
+
pnpm build # build the rest of the workspace
|
|
510
|
+
pnpm --filter @serverless-ircd/cf-worker dev # http://localhost:8787
|
|
511
|
+
node apps/cf-worker/scripts/smoke.mjs # WS smoke
|
|
512
|
+
|
|
513
|
+
# Deploy staging (Worker + assets in one command)
|
|
514
|
+
pnpm deploy:cf:staging # wrangler deploy --env staging
|
|
515
|
+
|
|
516
|
+
# Tests / lint
|
|
517
|
+
pnpm --filter web test
|
|
518
|
+
pnpm --filter web typecheck
|
|
519
|
+
pnpm lint
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
Key files:
|
|
523
|
+
|
|
524
|
+
| Path | What |
|
|
525
|
+
|---|---|
|
|
526
|
+
| `apps/web/scripts/build.mjs` | Orchestrates the upstream Kiwi build + layers our config. |
|
|
527
|
+
| `apps/web/src/config-schema.ts` | Zod schema for `static/config.*.json` (the only TS we own). |
|
|
528
|
+
| `apps/web/src/build-env.ts` | `--env` → config-file resolution (unit-tested). |
|
|
529
|
+
| `apps/web/static/config.{,staging,prod}.json` | Per-env Kiwi configs (validated at build time). |
|
|
530
|
+
| `apps/web/landing/index.html` | Static landing page (project front door), copied to `dist/index.html`. |
|
|
531
|
+
| `apps/web/tests/build-smoke.test.ts` | Landing-page + build-output assertions. |
|
|
532
|
+
| `apps/cf-worker/src/worker.ts` | Edge handler: WS → `ConnectionDO`, `/health`, else `env.ASSETS`. |
|
|
533
|
+
| `apps/cf-worker/src/origin-allowlist.ts` | CSWSH `Origin` policy (pure, unit-tested). |
|
|
534
|
+
| `apps/cf-worker/wrangler.toml` | `[assets]` binding + per-env blocks + `WEB_ORIGINS` var. |
|
|
535
|
+
| `apps/web/upstream/` | Kiwi IRC source (git submodule, tag-pinned). |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "serverless-ircd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Serverless IRC daemon with a platform-agnostic core and Cloudflare Workers + AWS adapters",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"typescript": "^5.9.3",
|
|
18
18
|
"vite": "^7.3.6",
|
|
19
19
|
"vitest": "^4.1.10",
|
|
20
|
-
"@serverless-ircd/aws-adapter": "0.
|
|
20
|
+
"@serverless-ircd/aws-adapter": "0.7.0"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "turbo run build",
|
|
@@ -194,6 +194,9 @@ export async function handleDefault(params: DefaultParams): Promise<{ statusCode
|
|
|
194
194
|
...(params.accounts !== undefined ? { accounts: params.accounts } : {}),
|
|
195
195
|
...(params.mtlsIdentity !== undefined ? { mtlsIdentity: params.mtlsIdentity } : {}),
|
|
196
196
|
...(params.history !== undefined ? { history: params.history } : {}),
|
|
197
|
+
// API Gateway terminates TLS before the Lambda is invoked, so every
|
|
198
|
+
// WebSocket connection is secure → user mode `S`.
|
|
199
|
+
secure: true,
|
|
197
200
|
});
|
|
198
201
|
|
|
199
202
|
try {
|
|
@@ -158,6 +158,13 @@ export interface NlbStreamParams {
|
|
|
158
158
|
* graceful error-suffixed `382`.
|
|
159
159
|
*/
|
|
160
160
|
configLoader?: () => Promise<ServerConfig>;
|
|
161
|
+
/**
|
|
162
|
+
* Whether the NLB listener terminates TLS for this flow. Set to `true`
|
|
163
|
+
* when the target group fronts a TLS listener (the `irc+tls://` port) so
|
|
164
|
+
* the connection surfaces user mode `S`; omit / set `false` for a plain
|
|
165
|
+
* TCP listener. The stack code sets this from the listener protocol.
|
|
166
|
+
*/
|
|
167
|
+
secure?: boolean;
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
/**
|
|
@@ -253,6 +260,8 @@ export async function handleNlbStream(
|
|
|
253
260
|
...(params.accounts !== undefined ? { accounts: params.accounts } : {}),
|
|
254
261
|
...(params.mtlsIdentity !== undefined ? { mtlsIdentity: params.mtlsIdentity } : {}),
|
|
255
262
|
...(params.history !== undefined ? { history: params.history } : {}),
|
|
263
|
+
// Surface TLS fact when the NLB listener terminates TLS (irc+tls port).
|
|
264
|
+
...(params.secure === true ? { secure: true } : {}),
|
|
256
265
|
});
|
|
257
266
|
|
|
258
267
|
if (lines.length > 0) {
|
|
@@ -56,6 +56,13 @@ export interface MarshalledConnection {
|
|
|
56
56
|
connectionId: ConnId;
|
|
57
57
|
registration: RegistrationState;
|
|
58
58
|
capNegotiating: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Transport TLS fact (user mode `S`). Optional on the persisted shape so
|
|
61
|
+
* rows written before the field shipped deserialize with a `false`
|
|
62
|
+
* default via the coalescing in {@link unmarshalConnection}. Always
|
|
63
|
+
* written by {@link marshalConnection}.
|
|
64
|
+
*/
|
|
65
|
+
secure?: boolean;
|
|
59
66
|
caps: string[];
|
|
60
67
|
/**
|
|
61
68
|
* Present (as a JS `Set`, auto-marshalled to `SS`) only when the
|
|
@@ -102,6 +109,7 @@ export function marshalConnection(state: ConnectionState, idleSince: number): Ma
|
|
|
102
109
|
connectionId: state.id,
|
|
103
110
|
registration: state.registration,
|
|
104
111
|
capNegotiating: state.capNegotiating,
|
|
112
|
+
secure: state.secure,
|
|
105
113
|
caps: [...state.caps],
|
|
106
114
|
userModes: { ...state.userModes },
|
|
107
115
|
lastSeen: state.lastSeen,
|
|
@@ -140,13 +148,20 @@ export function unmarshalConnection(row: MarshalledConnection): ConnectionState
|
|
|
140
148
|
id: row.connectionId,
|
|
141
149
|
registration: row.registration,
|
|
142
150
|
capNegotiating: row.capNegotiating,
|
|
151
|
+
// `secure` is absent on rows persisted before user mode `S` shipped;
|
|
152
|
+
// default to false (plain transport) so the state shape stays satisfied.
|
|
153
|
+
secure: row.secure ?? false,
|
|
143
154
|
caps: new Set<string>(row.caps),
|
|
144
155
|
// `joinedChannels` is absent on rows that never joined a channel
|
|
145
156
|
// (DynamoDB rejects empty sets); default to an empty Set. The SDK
|
|
146
157
|
// unmarshalls `SS` back to a native Set, which the Set constructor
|
|
147
158
|
// copies; arrays (older rows) also iterate fine.
|
|
148
159
|
joinedChannels: new Set<ChanName>(row.joinedChannels ?? []),
|
|
149
|
-
userModes
|
|
160
|
+
// `userModes.tls` is absent on rows persisted before user mode `S`
|
|
161
|
+
// shipped; default to false so the field is a real boolean rather than
|
|
162
|
+
// `undefined`. Cast to Partial so TypeScript accepts the explicit
|
|
163
|
+
// default without flagging the spread overwrite (TS2783).
|
|
164
|
+
userModes: normalizeUserModes(row.userModes),
|
|
150
165
|
lastSeen: row.lastSeen,
|
|
151
166
|
connectedSince: row.connectedSince,
|
|
152
167
|
};
|
|
@@ -162,6 +177,19 @@ export function unmarshalConnection(row: MarshalledConnection): ConnectionState
|
|
|
162
177
|
return state;
|
|
163
178
|
}
|
|
164
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Defaults `tls` to false on a deserialized `UserModes`. Rows persisted
|
|
182
|
+
* before user mode `S` shipped lack the field; the DynamoDB SDK round-trip
|
|
183
|
+
* yields `undefined`, which would leak into runtime code expecting a real
|
|
184
|
+
* boolean. Casting to `Partial` lets the explicit default apply without
|
|
185
|
+
* TypeScript flagging the spread overwrite (TS2783).
|
|
186
|
+
*/
|
|
187
|
+
function normalizeUserModes(um: UserModes): UserModes {
|
|
188
|
+
const out: Partial<UserModes> = { ...um };
|
|
189
|
+
if (out.tls === undefined) out.tls = false;
|
|
190
|
+
return out as UserModes;
|
|
191
|
+
}
|
|
192
|
+
|
|
165
193
|
// ---------------------------------------------------------------------------
|
|
166
194
|
// Nicks
|
|
167
195
|
// ---------------------------------------------------------------------------
|