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,18 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<!--
|
|
3
|
+
Test-only fixture mirroring the real `apps/web/dist/app/index.html` produced
|
|
4
|
+
by the Kiwi SPA build (`pnpm --filter web build`). The cf-worker smoke suite
|
|
5
|
+
points its `[assets]` binding here so the worker's fall-through routing
|
|
6
|
+
(WS → ConnectionDO, /health → plaintext, else → ASSETS) is exercised without
|
|
7
|
+
depending on the (slow, submodule-heavy) Kiwi build. Production points at
|
|
8
|
+
`apps/web/dist`; this fixture only stands in for the test run.
|
|
9
|
+
-->
|
|
10
|
+
<html lang="en">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="utf-8" />
|
|
13
|
+
<title>KiwiIRC</title>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<div id="app"></div>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<!--
|
|
3
|
+
Test-only fixture standing in for the landing page shipped by the
|
|
4
|
+
`apps/web` build (`apps/web/dist/index.html`). Production will serve a
|
|
5
|
+
real landing page at `/`; the cf-worker smoke suite asserts that `GET /`
|
|
6
|
+
is served by the `[assets]` binding rather than the legacy plaintext
|
|
7
|
+
health response (now exclusive to `/health`).
|
|
8
|
+
-->
|
|
9
|
+
<html lang="en">
|
|
10
|
+
<head>
|
|
11
|
+
<meta charset="utf-8" />
|
|
12
|
+
<title>ServerlessIRCd</title>
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<h1>ServerlessIRCd</h1>
|
|
16
|
+
<p>A serverless IRC daemon.</p>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the WebSocket `Origin` allowlist helpers.
|
|
3
|
+
*
|
|
4
|
+
* The worker's CSWSH defense is split into two pure functions
|
|
5
|
+
* (`parseWebOrigins`, `decideOrigin`) so the policy logic is exhaustively
|
|
6
|
+
* coverable without driving a Durable Object through `worker.fetch`. The
|
|
7
|
+
* integration-side assertions (status codes against the real handler) live
|
|
8
|
+
* in `ws-origin.test.ts`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, it } from 'vitest';
|
|
12
|
+
import { decideOrigin, parseWebOrigins } from '../src/origin-allowlist';
|
|
13
|
+
|
|
14
|
+
describe('parseWebOrigins', () => {
|
|
15
|
+
it('splits a comma-separated list into a set of normalized origins', () => {
|
|
16
|
+
expect(parseWebOrigins('https://example.com, https://app.example.com')).toEqual(
|
|
17
|
+
new Set(['https://example.com', 'https://app.example.com']),
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('lowercases origins for case-insensitive comparison', () => {
|
|
22
|
+
expect(parseWebOrigins('https://Example.COM/Path').has('https://example.com/path')).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('drops whitespace-only and empty entries', () => {
|
|
26
|
+
expect(parseWebOrigins('https://example.com, , ,https://b.example.com')).toEqual(
|
|
27
|
+
new Set(['https://example.com', 'https://b.example.com']),
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('returns an empty set for an empty/undefined var (fail-closed sentinel)', () => {
|
|
32
|
+
expect(parseWebOrigins('').size).toBe(0);
|
|
33
|
+
expect(parseWebOrigins(' ').size).toBe(0);
|
|
34
|
+
expect(parseWebOrigins(undefined).size).toBe(0);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('decideOrigin', () => {
|
|
39
|
+
const allowlist = parseWebOrigins('https://example.com, https://app.example.com');
|
|
40
|
+
|
|
41
|
+
// ---- missing Origin = non-browser client (curl, WeeChat, tcp-ws-forwarder)
|
|
42
|
+
|
|
43
|
+
it('allows a missing Origin header (non-browser client)', () => {
|
|
44
|
+
expect(decideOrigin(null, allowlist, 'https://example.com')).toBe('allow');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('allows an empty Origin header (treated like a missing header)', () => {
|
|
48
|
+
expect(decideOrigin('', allowlist, 'https://example.com')).toBe('allow');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// ---- explicit allowlist (WEB_ORIGINS is set)
|
|
52
|
+
|
|
53
|
+
it('allows an Origin that is in the allowlist', () => {
|
|
54
|
+
expect(decideOrigin('https://example.com', allowlist, 'https://example.com')).toBe('allow');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('allows an Origin that differs only by case', () => {
|
|
58
|
+
expect(decideOrigin('https://EXAMPLE.com', allowlist, 'https://example.com')).toBe('allow');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('denies an Origin that is not in the allowlist', () => {
|
|
62
|
+
expect(decideOrigin('https://evil.example.net', allowlist, 'https://example.com')).toBe('deny');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('denies an Origin that matches requestOrigin but is NOT in the allowlist (allowlist wins)', () => {
|
|
66
|
+
// WEB_ORIGINS takes precedence: if the operator pinned an explicit list,
|
|
67
|
+
// same-origin is NOT automatically allowed unless the origin is listed.
|
|
68
|
+
const strict = parseWebOrigins('https://only-this.com');
|
|
69
|
+
expect(decideOrigin('https://example.com', strict, 'https://example.com')).toBe('deny');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ---- auto-derive (WEB_ORIGINS is empty — same-origin enforcement)
|
|
73
|
+
|
|
74
|
+
it('auto-allows a same-origin Origin when the allowlist is empty', () => {
|
|
75
|
+
expect(decideOrigin('https://example.com', new Set(), 'https://example.com')).toBe('allow');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('auto-denies a cross-origin Origin when the allowlist is empty', () => {
|
|
79
|
+
expect(decideOrigin('https://evil.example.net', new Set(), 'https://example.com')).toBe('deny');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('auto-derive is case-insensitive', () => {
|
|
83
|
+
expect(decideOrigin('https://EXAMPLE.com', new Set(), 'https://example.com')).toBe('allow');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('auto-allows a missing Origin even when the allowlist is empty (non-browser client)', () => {
|
|
87
|
+
expect(decideOrigin(null, new Set(), 'https://example.com')).toBe('allow');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// ---- fail-closed (cannot determine same-origin AND no allowlist)
|
|
91
|
+
|
|
92
|
+
it('fail-closes when a browser sends an Origin but neither allowlist nor requestOrigin exist', () => {
|
|
93
|
+
expect(decideOrigin('https://example.com', new Set(), null)).toBe('fail-closed');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('still allows a missing Origin even when neither allowlist nor requestOrigin exist (non-browser client)', () => {
|
|
97
|
+
// Non-browser clients never send Origin; they pass through regardless of
|
|
98
|
+
// configuration state. Only browser-sent Origins can be fail-closed.
|
|
99
|
+
expect(decideOrigin(null, new Set(), null)).toBe('allow');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -30,6 +30,12 @@ declare global {
|
|
|
30
30
|
SERVER_NAME: string;
|
|
31
31
|
NETWORK_NAME: string;
|
|
32
32
|
MOTD_LINES: string;
|
|
33
|
+
// Static-assets binding (`[assets]` in `wrangler.test.toml`, pointed at
|
|
34
|
+
// `tests/fixtures/web-dist`). Production points at `apps/web/dist`; the
|
|
35
|
+
// fixture mirrors the `app/index.html` + `index.html` layout so the
|
|
36
|
+
// worker's fall-through routing is exercised without depending on the
|
|
37
|
+
// Kiwi SPA build.
|
|
38
|
+
ASSETS: Fetcher;
|
|
33
39
|
}
|
|
34
40
|
}
|
|
35
41
|
}
|
|
@@ -47,13 +53,33 @@ function numericEquals(line: string, code: string): boolean {
|
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
describe('cf-worker deploy glue', () => {
|
|
50
|
-
it('
|
|
51
|
-
const response = await worker.fetch(new Request('https://irc.example.com/'), env);
|
|
56
|
+
it('GET /health returns a human-readable plaintext health response', async () => {
|
|
57
|
+
const response = await worker.fetch(new Request('https://irc.example.com/health'), env);
|
|
52
58
|
expect(response.status).toBe(200);
|
|
59
|
+
expect(response.headers.get('content-type')).toBe('text/plain; charset=utf-8');
|
|
53
60
|
const body = await response.text();
|
|
54
61
|
expect(body.toLowerCase()).toContain('serverlessircd');
|
|
55
62
|
});
|
|
56
63
|
|
|
64
|
+
it('GET /app/ serves the SPA index from the [assets] binding', async () => {
|
|
65
|
+
const response = await worker.fetch(new Request('https://irc.example.com/app/'), env);
|
|
66
|
+
expect(response.status).toBe(200);
|
|
67
|
+
const body = await response.text();
|
|
68
|
+
// The fixture SPA HTML carries this marker so the test asserts the
|
|
69
|
+
// assets binding (not the /health plaintext branch) produced the body.
|
|
70
|
+
expect(body).toContain('<title>KiwiIRC</title>');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('GET / (landing page) is served by the [assets] binding, not the health plaintext', async () => {
|
|
74
|
+
const response = await worker.fetch(new Request('https://irc.example.com/'), env);
|
|
75
|
+
expect(response.status).toBe(200);
|
|
76
|
+
const body = await response.text();
|
|
77
|
+
// The fixture landing page carries this marker; the old health
|
|
78
|
+
// plaintext response is now exclusively at /health.
|
|
79
|
+
expect(body).toContain('<title>ServerlessIRCd</title>');
|
|
80
|
+
expect(body).not.toContain('Connect via WebSocket');
|
|
81
|
+
});
|
|
82
|
+
|
|
57
83
|
it('completes CONNECT → REGISTER → JOIN → PRIVMSG → QUIT', async () => {
|
|
58
84
|
// ----- CONNECT: WebSocket upgrade through the worker's fetch handler.
|
|
59
85
|
const upgrade = new Request('https://irc.example.com/', {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for the WebSocket `Origin` policy (CSWSH defense).
|
|
3
|
+
*
|
|
4
|
+
* Drives the production worker entry (`src/worker.ts`) end-to-end through
|
|
5
|
+
* `worker.fetch` to assert the `Origin` header policy is enforced at the
|
|
6
|
+
* edge, before any upgrade reaches `ConnectionDO`. The pure policy logic
|
|
7
|
+
* is covered separately in `origin-allowlist.test.ts`; these tests assert
|
|
8
|
+
* the wiring (status codes + that the DO is never reached on a rejection).
|
|
9
|
+
*
|
|
10
|
+
* Two modes are exercised:
|
|
11
|
+
*
|
|
12
|
+
* • Explicit allowlist — `WEB_ORIGINS` is set in `wrangler.test.toml`
|
|
13
|
+
* `[vars]` as `https://example.com`. Only listed origins proceed.
|
|
14
|
+
*
|
|
15
|
+
* • Auto-derive (same-origin) — per-test override blanks `WEB_ORIGINS`;
|
|
16
|
+
* the Worker compares the browser's `Origin` against the request's own
|
|
17
|
+
* host. Same-origin upgrades proceed; cross-origin are rejected.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { env } from 'cloudflare:test';
|
|
21
|
+
import { describe, expect, it } from 'vitest';
|
|
22
|
+
import worker from '../src/worker';
|
|
23
|
+
|
|
24
|
+
declare global {
|
|
25
|
+
namespace Cloudflare {
|
|
26
|
+
interface Env {
|
|
27
|
+
WEB_ORIGINS: string;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const UPGRADE_URL = 'https://irc.example.com/';
|
|
33
|
+
/** The request's own origin — what auto-derive compares Origin against. */
|
|
34
|
+
const SELF_ORIGIN = 'https://irc.example.com';
|
|
35
|
+
|
|
36
|
+
function upgradeRequest(origin?: string): Request {
|
|
37
|
+
const headers: Record<string, string> = { Upgrade: 'websocket' };
|
|
38
|
+
if (origin !== undefined) headers.Origin = origin;
|
|
39
|
+
return new Request(UPGRADE_URL, { headers });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('WS Origin policy — explicit allowlist (WEB_ORIGINS set)', () => {
|
|
43
|
+
it('proceeds (101) when the Origin is in WEB_ORIGINS', async () => {
|
|
44
|
+
const response = await worker.fetch(upgradeRequest('https://example.com'), env);
|
|
45
|
+
expect(response.status).toBe(101);
|
|
46
|
+
response.webSocket?.accept();
|
|
47
|
+
response.webSocket?.close();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('rejects with 403 when the Origin is not in WEB_ORIGINS', async () => {
|
|
51
|
+
const response = await worker.fetch(upgradeRequest('https://evil.example.net'), env);
|
|
52
|
+
expect(response.status).toBe(403);
|
|
53
|
+
expect(response.webSocket).toBeNull();
|
|
54
|
+
const body = await response.text();
|
|
55
|
+
expect(body.toLowerCase()).toContain('origin');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('proceeds (101) when the Origin header is missing (non-browser client)', async () => {
|
|
59
|
+
const response = await worker.fetch(upgradeRequest(), env);
|
|
60
|
+
expect(response.status).toBe(101);
|
|
61
|
+
response.webSocket?.accept();
|
|
62
|
+
response.webSocket?.close();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('WS Origin policy — auto-derive (WEB_ORIGINS unset → same-origin)', () => {
|
|
67
|
+
/** Env with WEB_ORIGINS blanked so the Worker falls back to auto-derive. */
|
|
68
|
+
const autoEnv = { ...env, WEB_ORIGINS: '' };
|
|
69
|
+
|
|
70
|
+
it('proceeds (101) for a same-origin upgrade', async () => {
|
|
71
|
+
const response = await worker.fetch(upgradeRequest(SELF_ORIGIN), autoEnv);
|
|
72
|
+
expect(response.status).toBe(101);
|
|
73
|
+
response.webSocket?.accept();
|
|
74
|
+
response.webSocket?.close();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('rejects with 403 for a cross-origin upgrade', async () => {
|
|
78
|
+
const response = await worker.fetch(upgradeRequest('https://evil.example.net'), autoEnv);
|
|
79
|
+
expect(response.status).toBe(403);
|
|
80
|
+
expect(response.webSocket).toBeNull();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('proceeds (101) when the Origin header is missing (non-browser client)', async () => {
|
|
84
|
+
const response = await worker.fetch(upgradeRequest(), autoEnv);
|
|
85
|
+
expect(response.status).toBe(101);
|
|
86
|
+
response.webSocket?.accept();
|
|
87
|
+
response.webSocket?.close();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -11,10 +11,33 @@ main = "src/worker.ts"
|
|
|
11
11
|
compatibility_date = "2024-11-01"
|
|
12
12
|
compatibility_flags = ["nodejs_compat"]
|
|
13
13
|
|
|
14
|
+
# Static-assets binding. Production points at `../../apps/web/dist` (the Kiwi
|
|
15
|
+
# SPA + landing page built by `apps/web`); the test config points at a fixture
|
|
16
|
+
# so the smoke suite does not depend on the (slow) Kiwi upstream build. The
|
|
17
|
+
# fixture mirrors the `app/index.html` + `index.html` layout; the worker's
|
|
18
|
+
# fall-through routing (`/health` → plaintext, else → ASSETS) is what's under
|
|
19
|
+
# test, not the SPA contents.
|
|
20
|
+
[assets]
|
|
21
|
+
directory = "./tests/fixtures/web-dist"
|
|
22
|
+
binding = "ASSETS"
|
|
23
|
+
# `none` returns 404 for unmatched paths — the SPA lives at `/app/` and the
|
|
24
|
+
# landing page at `/`, both with real `index.html` files, so no SPA fallback
|
|
25
|
+
# is required.
|
|
26
|
+
not_found_handling = "none"
|
|
27
|
+
# Mirror production: route every request through the Worker first so WS
|
|
28
|
+
# upgrades at `/` aren't intercepted by the asset platform (which would
|
|
29
|
+
# serve `index.html` with 200 instead of a 101 upgrade).
|
|
30
|
+
run_worker_first = true
|
|
31
|
+
|
|
14
32
|
[vars]
|
|
15
33
|
SERVER_NAME = "irc.example.com"
|
|
16
34
|
NETWORK_NAME = "ServerlessIRCd"
|
|
17
35
|
MOTD_LINES = "Welcome to the cf-worker smoke test."
|
|
36
|
+
# CSWSH allowlist. The ws-origin integration suite asserts an upgrade from
|
|
37
|
+
# `https://example.com` proceeds and `https://evil.example.net` is rejected
|
|
38
|
+
# (the fail-closed branch overrides this per request via `{ ...env,
|
|
39
|
+
# WEB_ORIGINS: '' }`).
|
|
40
|
+
WEB_ORIGINS = "https://example.com"
|
|
18
41
|
|
|
19
42
|
[[durable_objects.bindings]]
|
|
20
43
|
name = "CONNECTION_DO"
|
|
@@ -45,16 +45,62 @@ enabled = true
|
|
|
45
45
|
invocation_logs = true
|
|
46
46
|
|
|
47
47
|
## Observable per-deployment knobs. Override per environment below.
|
|
48
|
-
##
|
|
49
|
-
##
|
|
48
|
+
## `SERVER_NAME` is REQUIRED — the CF config loader
|
|
49
|
+
## (`loadServerConfigFromCfEnv`) fails fast at boot when it is unset.
|
|
50
|
+
## Production deployments MUST override it to the public hostname
|
|
51
|
+
## clients see in numerics (001, 005, etc.).
|
|
52
|
+
##
|
|
53
|
+
## `WEB_ORIGINS` is the OPTIONAL comma-separated allowlist of web origins
|
|
54
|
+
## permitted to open WebSocket upgrades (CSWSH defense). When set, only
|
|
55
|
+
## listed origins proceed. When unset/empty (the default), the Worker
|
|
56
|
+
## auto-derives the expected origin from the request's own `Host` header
|
|
57
|
+
## (same-origin enforcement) — no per-env config needed. Set this only for
|
|
58
|
+
## cross-origin deployments where the SPA is served from a different domain
|
|
59
|
+
## than the Worker. Non-browser clients (curl, WeeChat, tcp-ws-forwarder)
|
|
60
|
+
## omit `Origin` and pass through unchanged regardless.
|
|
50
61
|
[vars]
|
|
51
|
-
SERVER_NAME = "irc.
|
|
62
|
+
SERVER_NAME = "irc.your-domain.invalid"
|
|
52
63
|
NETWORK_NAME = "ServerlessIRCd"
|
|
53
64
|
MOTD_LINES = """
|
|
54
65
|
Welcome to ServerlessIRCd.
|
|
55
66
|
This is a deployed Cloudflare Worker.
|
|
56
67
|
"""
|
|
57
68
|
|
|
69
|
+
## ---------------------------------------------------------------------------
|
|
70
|
+
## Static assets — Kiwi SPA + landing page.
|
|
71
|
+
## ---------------------------------------------------------------------------
|
|
72
|
+
## The `[assets]` binding serves `apps/web/dist` (built by `pnpm --filter web
|
|
73
|
+
## build`). The non-WS, non-`/health` branch of `worker.ts`'s `fetch` falls
|
|
74
|
+
## through to this binding, so:
|
|
75
|
+
## • `/app/` → the Kiwi IRC SPA (`dist/app/index.html` + its
|
|
76
|
+
## hashed assets under `/app/assets/...`).
|
|
77
|
+
## • `/` → the landing page (`dist/index.html`, shipped by
|
|
78
|
+
## the `apps/web` build).
|
|
79
|
+
## • `/health` → plaintext liveness (handled by the worker, not
|
|
80
|
+
## assets).
|
|
81
|
+
## The deploy workflow (`deploy-cf.yml`) runs `pnpm build` before `wrangler
|
|
82
|
+
## deploy` so `apps/web/dist` is freshly populated. `wrangler dev` serves the
|
|
83
|
+
## same asset layer against the locally-built `dist`.
|
|
84
|
+
[assets]
|
|
85
|
+
## Relative to this file (`apps/cf-worker/`), so `../../apps/web/dist`.
|
|
86
|
+
directory = "../../apps/web/dist"
|
|
87
|
+
## Expose the asset `Fetcher` on `env.ASSETS` (see `WorkerEnv` in `worker.ts`).
|
|
88
|
+
binding = "ASSETS"
|
|
89
|
+
## Return 404 for unmatched paths. The SPA lives at `/app/` and the landing
|
|
90
|
+
## page at `/`; both are concrete `index.html` files, so no SPA fallback is
|
|
91
|
+
## required. Kiwi uses query/hash-based client routing, not path routing.
|
|
92
|
+
not_found_handling = "none"
|
|
93
|
+
## Route EVERY request through the Worker before the asset platform serves a
|
|
94
|
+
## matching static file. Without this, the default (`run_worker_first = false`)
|
|
95
|
+
## causes the asset platform to serve `dist/index.html` at `/` with 200 OK for
|
|
96
|
+
## WebSocket upgrade requests — the Worker's `Upgrade: websocket` check never
|
|
97
|
+
## fires and the client sees "HTTP error: 200 OK" instead of a 101 upgrade.
|
|
98
|
+
## The WS endpoint and the landing page share path `/`, so a path-pattern
|
|
99
|
+
## exclusion can't tell them apart; the Worker must run first for everything.
|
|
100
|
+
## The Worker falls through to `env.ASSETS.fetch(request)` for non-WS,
|
|
101
|
+
## non-`/health` requests, so static content is unaffected.
|
|
102
|
+
run_worker_first = true
|
|
103
|
+
|
|
58
104
|
## ---------------------------------------------------------------------------
|
|
59
105
|
## Staging environment.
|
|
60
106
|
## ---------------------------------------------------------------------------
|
|
@@ -77,6 +123,16 @@ This is a deployed Cloudflare Worker.
|
|
|
77
123
|
This Worker was deployed by CI from the main branch.
|
|
78
124
|
"""
|
|
79
125
|
|
|
126
|
+
# Staging serves the same `apps/web/dist` asset layer. Wrangler requires the
|
|
127
|
+
# per-env assets block so `--env staging` deploys both the Worker and its
|
|
128
|
+
# assets in a single `wrangler deploy --env staging` (the same `pnpm build`
|
|
129
|
+
# populates `dist/` ahead of the deploy step).
|
|
130
|
+
[env.staging.assets]
|
|
131
|
+
directory = "../../apps/web/dist"
|
|
132
|
+
binding = "ASSETS"
|
|
133
|
+
not_found_handling = "none"
|
|
134
|
+
run_worker_first = true
|
|
135
|
+
|
|
80
136
|
## ---------------------------------------------------------------------------
|
|
81
137
|
## D1 database — SASL account persistence.
|
|
82
138
|
## ---------------------------------------------------------------------------
|
|
@@ -13,14 +13,14 @@ import { type ParsedServerConfig, parseServerConfig } from '@serverless-ircd/irc
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* CLI / programmatic input for `loadServerConfigFromCliArgs`. Every
|
|
16
|
-
* field is optional
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* numbers, etc.) so callers don't need to coerce strings.
|
|
16
|
+
* field is optional; the schema rejects a missing `serverName` or
|
|
17
|
+
* `networkName` with a readable error so the caller learns at boot
|
|
18
|
+
* rather than mid-session. Field types match the schema's input shape
|
|
19
|
+
* (numbers stay numbers, etc.) so callers don't need to coerce strings.
|
|
20
20
|
*/
|
|
21
21
|
export interface CliConfigInput {
|
|
22
|
-
serverName
|
|
23
|
-
networkName
|
|
22
|
+
serverName?: string | undefined;
|
|
23
|
+
networkName?: string | undefined;
|
|
24
24
|
/**
|
|
25
25
|
* Server version surfaced in `002`/`004`/`351`/`371`. When omitted the
|
|
26
26
|
* schema default ({@link DEFAULT_SERVER_VERSION}) applies.
|
|
@@ -31,9 +31,15 @@ interface CliArgs {
|
|
|
31
31
|
tcpPort: number | undefined;
|
|
32
32
|
/** undefined when --motd-file is not given; built-in default MOTD applies. */
|
|
33
33
|
motdFile: string | undefined;
|
|
34
|
+
/** Server identity shown in 001/005 numerics. */
|
|
35
|
+
serverName: string;
|
|
36
|
+
/** Network name advertised in 005 NETWORK=... */
|
|
37
|
+
networkName: string;
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
const DEFAULT_PORT = 6667;
|
|
41
|
+
const DEFAULT_SERVER_NAME = 'irc.localhost';
|
|
42
|
+
const DEFAULT_NETWORK_NAME = 'LocalNet';
|
|
37
43
|
|
|
38
44
|
function parseArgs(argv: string[]): CliArgs {
|
|
39
45
|
const out: CliArgs = {
|
|
@@ -41,6 +47,8 @@ function parseArgs(argv: string[]): CliArgs {
|
|
|
41
47
|
hostname: '127.0.0.1',
|
|
42
48
|
tcpPort: undefined,
|
|
43
49
|
motdFile: undefined,
|
|
50
|
+
serverName: DEFAULT_SERVER_NAME,
|
|
51
|
+
networkName: DEFAULT_NETWORK_NAME,
|
|
44
52
|
};
|
|
45
53
|
let tcpDisabled = false;
|
|
46
54
|
let tcpPortOverride: number | undefined;
|
|
@@ -59,6 +67,12 @@ function parseArgs(argv: string[]): CliArgs {
|
|
|
59
67
|
} else if (a === '--motd-file' && next !== undefined) {
|
|
60
68
|
out.motdFile = next;
|
|
61
69
|
i++;
|
|
70
|
+
} else if (a === '--server-name' && next !== undefined) {
|
|
71
|
+
out.serverName = next;
|
|
72
|
+
i++;
|
|
73
|
+
} else if (a === '--network-name' && next !== undefined) {
|
|
74
|
+
out.networkName = next;
|
|
75
|
+
i++;
|
|
62
76
|
} else if (a === '--no-tcp') {
|
|
63
77
|
tcpDisabled = true;
|
|
64
78
|
} else if (a === '--help' || a === '-h') {
|
|
@@ -74,6 +88,10 @@ function parseArgs(argv: string[]): CliArgs {
|
|
|
74
88
|
' --no-tcp Disable the TCP listener (WebSocket only).',
|
|
75
89
|
' --motd-file <path> Read MOTD lines from this file (one per line).',
|
|
76
90
|
' Optional; overrides the built-in default MOTD.',
|
|
91
|
+
' --server-name <h> Server hostname in 001/005 numerics.',
|
|
92
|
+
' Optional; defaults to irc.localhost for the local CLI.',
|
|
93
|
+
' --network-name <n> Network name in 005 NETWORK=...',
|
|
94
|
+
' Optional; defaults to LocalNet.',
|
|
77
95
|
' -h, --help Show this help and exit.',
|
|
78
96
|
'',
|
|
79
97
|
].join('\n'),
|
|
@@ -99,6 +117,8 @@ async function main(): Promise<void> {
|
|
|
99
117
|
port: args.port,
|
|
100
118
|
hostname: args.hostname,
|
|
101
119
|
tcpPort: args.tcpPort,
|
|
120
|
+
serverName: args.serverName,
|
|
121
|
+
networkName: args.networkName,
|
|
102
122
|
...(motdLines !== undefined ? { motdLines } : {}),
|
|
103
123
|
});
|
|
104
124
|
|
|
@@ -235,9 +235,9 @@ const DEFAULT_CONFIG: Omit<
|
|
|
235
235
|
| 'perIpConnectionRate'
|
|
236
236
|
| 'historyMaxPerChannel'
|
|
237
237
|
| 'saslAccounts'
|
|
238
|
+
| 'serverName'
|
|
239
|
+
| 'networkName'
|
|
238
240
|
> = {
|
|
239
|
-
serverName: 'irc.example.com',
|
|
240
|
-
networkName: 'ExampleNet',
|
|
241
241
|
motdLines: ['Welcome to the local-cli ServerlessIRCd instance.'],
|
|
242
242
|
maxChannelsPerUser: 30,
|
|
243
243
|
maxTargetsPerCommand: 10,
|
|
@@ -279,14 +279,16 @@ export function startLocalServer(opts: StartServerOptions): Promise<LocalServer>
|
|
|
279
279
|
// Validate the user-supplied config through the shared ServerConfigSchema
|
|
280
280
|
// (sourced from `irc-core`). Both adapters consume the same schema; here
|
|
281
281
|
// we discard the parsed result and only retain the side-effect of
|
|
282
|
-
// throwing on a malformed config at boot.
|
|
283
|
-
//
|
|
284
|
-
//
|
|
282
|
+
// throwing on a malformed config at boot. `serverName` and `networkName`
|
|
283
|
+
// are required by the schema — passing them through as `undefined` when
|
|
284
|
+
// the caller omits them lets the loader fail fast with a readable error
|
|
285
|
+
// pointing at the missing knob, so the server never boots advertising a
|
|
286
|
+
// placeholder identity.
|
|
285
287
|
let parsed: ReturnType<typeof loadServerConfigFromCliArgs>;
|
|
286
288
|
try {
|
|
287
289
|
parsed = loadServerConfigFromCliArgs({
|
|
288
|
-
serverName: opts.serverName
|
|
289
|
-
networkName: opts.networkName
|
|
290
|
+
serverName: opts.serverName,
|
|
291
|
+
networkName: opts.networkName,
|
|
290
292
|
...(opts.motdLines !== undefined ? { motdLines: opts.motdLines } : {}),
|
|
291
293
|
...(opts.maxChannelsPerUser !== undefined
|
|
292
294
|
? { maxChannelsPerUser: opts.maxChannelsPerUser }
|