serverless-ircd 0.2.0 → 0.3.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.
Files changed (134) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +28 -19
  3. package/apps/cf-worker/package.json +1 -1
  4. package/apps/cf-worker/scripts/smoke.mjs +0 -0
  5. package/apps/cf-worker/src/worker.ts +8 -2
  6. package/apps/cf-worker/tests/smoke.test.ts +1 -0
  7. package/apps/cf-worker/wrangler.test.toml +5 -1
  8. package/apps/cf-worker/wrangler.toml +41 -17
  9. package/apps/local-cli/package.json +1 -1
  10. package/apps/local-cli/src/config-loader.ts +1 -0
  11. package/apps/local-cli/src/main.ts +1 -1
  12. package/apps/local-cli/src/server.ts +28 -2
  13. package/apps/local-cli/tests/e2e.test.ts +52 -0
  14. package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
  15. package/docs/ADR-002-location-of-authority.md +82 -0
  16. package/docs/ADR-003-durable-object-sharding.md +93 -0
  17. package/docs/ADR-004-dynamodb-schema.md +96 -0
  18. package/docs/ADR-005-wss-only-transport-v1.md +83 -0
  19. package/docs/ADR-006-sasl-mechanism-scope.md +86 -0
  20. package/docs/ADR-007-deterministic-ports.md +82 -0
  21. package/docs/ADR-008-monorepo-tooling.md +60 -0
  22. package/docs/AWS-Adapter-Architecture.md +6 -4
  23. package/docs/AWS-Deployment.md +86 -7
  24. package/docs/Cloudflare-Deployment-Guide.md +5 -5
  25. package/docs/Home.md +11 -1
  26. package/docs/Release-Process.md +9 -6
  27. package/package.json +14 -15
  28. package/packages/aws-adapter/package.json +1 -1
  29. package/packages/aws-adapter/src/account-store.ts +121 -0
  30. package/packages/aws-adapter/src/aws-runtime.ts +118 -30
  31. package/packages/aws-adapter/src/cdk-table-defs.ts +5 -1
  32. package/packages/aws-adapter/src/config-loader.ts +30 -0
  33. package/packages/aws-adapter/src/dynamo-account-store.ts +156 -0
  34. package/packages/aws-adapter/src/handlers/default.ts +8 -0
  35. package/packages/aws-adapter/src/handlers/disconnect.ts +27 -8
  36. package/packages/aws-adapter/src/handlers/index.ts +51 -6
  37. package/packages/aws-adapter/src/index.ts +7 -0
  38. package/packages/aws-adapter/tests/account-store-dynamo.test.ts +171 -0
  39. package/packages/aws-adapter/tests/account-store.test.ts +280 -0
  40. package/packages/aws-adapter/tests/config-loader.test.ts +55 -0
  41. package/packages/aws-adapter/tests/disconnect-fanout.test.ts +336 -0
  42. package/packages/aws-adapter/tests/handlers.test.ts +46 -0
  43. package/packages/cf-adapter/package.json +1 -1
  44. package/packages/cf-adapter/src/cf-runtime.ts +42 -22
  45. package/packages/cf-adapter/src/channel-do.ts +40 -1
  46. package/packages/cf-adapter/src/channel-registry-do.ts +58 -0
  47. package/packages/cf-adapter/src/config-loader.ts +29 -0
  48. package/packages/cf-adapter/src/connection-do.ts +85 -0
  49. package/packages/cf-adapter/src/env.ts +27 -1
  50. package/packages/cf-adapter/src/index.ts +2 -1
  51. package/packages/cf-adapter/tests/cf-runtime.test.ts +91 -0
  52. package/packages/cf-adapter/tests/connection-do.test.ts +40 -0
  53. package/packages/cf-adapter/tests/worker/main.ts +2 -1
  54. package/packages/cf-adapter/tests/worker/stubs/channel-stub.ts +7 -1
  55. package/packages/cf-adapter/wrangler.test.toml +10 -1
  56. package/packages/in-memory-runtime/package.json +1 -1
  57. package/packages/in-memory-runtime/src/in-memory-runtime.ts +14 -28
  58. package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +19 -0
  59. package/packages/irc-core/package.json +3 -2
  60. package/packages/irc-core/src/case-fold.ts +64 -0
  61. package/packages/irc-core/src/commands/index.ts +2 -0
  62. package/packages/irc-core/src/commands/invite.ts +6 -9
  63. package/packages/irc-core/src/commands/isupport.ts +1 -1
  64. package/packages/irc-core/src/commands/join.ts +4 -3
  65. package/packages/irc-core/src/commands/kick.ts +4 -11
  66. package/packages/irc-core/src/commands/list.ts +5 -4
  67. package/packages/irc-core/src/commands/mode.ts +5 -9
  68. package/packages/irc-core/src/commands/motd-lines.ts +127 -0
  69. package/packages/irc-core/src/commands/motd.ts +6 -110
  70. package/packages/irc-core/src/commands/oper.ts +151 -0
  71. package/packages/irc-core/src/commands/registration.ts +8 -7
  72. package/packages/irc-core/src/commands/tagmsg.ts +205 -0
  73. package/packages/irc-core/src/config.ts +26 -3
  74. package/packages/irc-core/src/index.ts +2 -0
  75. package/packages/irc-core/src/ports.ts +68 -4
  76. package/packages/irc-core/src/types.ts +22 -0
  77. package/packages/irc-core/tests/account-store.test.ts +88 -0
  78. package/packages/irc-core/tests/case-fold.test.ts +80 -0
  79. package/packages/irc-core/tests/commands/kick.test.ts +15 -0
  80. package/packages/irc-core/tests/commands/oper.test.ts +257 -0
  81. package/packages/irc-core/tests/commands/registration.test.ts +106 -14
  82. package/packages/irc-core/tests/commands/tagmsg.test.ts +688 -0
  83. package/packages/irc-core/tests/commands/who.test.ts +22 -4
  84. package/packages/irc-core/tests/commands/whois.test.ts +8 -1
  85. package/packages/irc-core/tests/config.test.ts +75 -0
  86. package/packages/irc-server/package.json +1 -1
  87. package/packages/irc-server/src/actor.ts +24 -0
  88. package/packages/irc-server/src/routing.ts +9 -0
  89. package/packages/irc-server/tests/actor.test.ts +220 -1
  90. package/packages/irc-server/tests/routing.test.ts +3 -0
  91. package/packages/irc-test-support/package.json +1 -1
  92. package/packages/irc-test-support/src/in-memory-harness.ts +5 -4
  93. package/pnpm-workspace.yaml +1 -0
  94. package/tools/seed-aws-accounts.ts +80 -0
  95. package/AGENTS.md +0 -5
  96. package/MOTD.txt +0 -3
  97. package/PLAN-FIXES.md +0 -358
  98. package/PLAN.md +0 -420
  99. package/dashboards/cloudwatch-irc.json +0 -139
  100. package/progress.md +0 -107
  101. package/tickets.md +0 -2485
  102. package/webircgateway/LICENSE +0 -201
  103. package/webircgateway/Makefile +0 -44
  104. package/webircgateway/README.md +0 -134
  105. package/webircgateway/config.conf.example +0 -135
  106. package/webircgateway/go.mod +0 -16
  107. package/webircgateway/go.sum +0 -89
  108. package/webircgateway/main.go +0 -118
  109. package/webircgateway/pkg/dnsbl/dnsbl.go +0 -121
  110. package/webircgateway/pkg/identd/identd.go +0 -86
  111. package/webircgateway/pkg/identd/rpcclient.go +0 -59
  112. package/webircgateway/pkg/irc/isupport.go +0 -56
  113. package/webircgateway/pkg/irc/message.go +0 -217
  114. package/webircgateway/pkg/irc/state.go +0 -79
  115. package/webircgateway/pkg/proxy/proxy.go +0 -129
  116. package/webircgateway/pkg/proxy/server.go +0 -237
  117. package/webircgateway/pkg/recaptcha/recaptcha.go +0 -59
  118. package/webircgateway/pkg/webircgateway/client.go +0 -741
  119. package/webircgateway/pkg/webircgateway/client_command_handlers.go +0 -495
  120. package/webircgateway/pkg/webircgateway/config.go +0 -385
  121. package/webircgateway/pkg/webircgateway/gateway.go +0 -278
  122. package/webircgateway/pkg/webircgateway/gateway_utils.go +0 -133
  123. package/webircgateway/pkg/webircgateway/hooks.go +0 -152
  124. package/webircgateway/pkg/webircgateway/letsencrypt.go +0 -41
  125. package/webircgateway/pkg/webircgateway/messagetags.go +0 -103
  126. package/webircgateway/pkg/webircgateway/transport_kiwiirc.go +0 -206
  127. package/webircgateway/pkg/webircgateway/transport_sockjs.go +0 -107
  128. package/webircgateway/pkg/webircgateway/transport_tcp.go +0 -113
  129. package/webircgateway/pkg/webircgateway/transport_websocket.go +0 -126
  130. package/webircgateway/pkg/webircgateway/utils.go +0 -147
  131. package/webircgateway/plugins/example/plugin.go +0 -11
  132. package/webircgateway/plugins/stats/plugin.go +0 -52
  133. package/webircgateway/staticcheck.conf +0 -1
  134. package/webircgateway/webircgateway.svg +0 -3
@@ -11,7 +11,13 @@ import {
11
11
  } from '../../src/commands/registration';
12
12
  import { Effect } from '../../src/effects';
13
13
  import type { Effect as EffectType, RawLine } from '../../src/effects';
14
- import { EmptyMotdProvider, FakeClock, SequentialIdFactory } from '../../src/ports';
14
+ import {
15
+ EmptyMotdProvider,
16
+ FakeClock,
17
+ SequentialIdFactory,
18
+ StaticMotdProvider,
19
+ } from '../../src/ports';
20
+ import type { MotdProvider } from '../../src/ports';
15
21
  import { type ConnectionState, createConnection } from '../../src/state/connection';
16
22
  import { type Ctx, type ServerConfig, buildCtx } from '../../src/types';
17
23
 
@@ -27,12 +33,16 @@ const serverConfig: ServerConfig = {
27
33
  quitMessage: 'Client Quit',
28
34
  };
29
35
 
30
- function makeCtx(state: ConnectionState, clock = new FakeClock(1_000)): Ctx {
36
+ function makeCtx(
37
+ state: ConnectionState,
38
+ clock = new FakeClock(1_000),
39
+ motd: MotdProvider = EmptyMotdProvider,
40
+ ): Ctx {
31
41
  return buildCtx({
32
42
  serverConfig,
33
43
  clock,
34
44
  ids: new SequentialIdFactory(),
35
- motd: EmptyMotdProvider,
45
+ motd,
36
46
  connection: state,
37
47
  });
38
48
  }
@@ -80,9 +90,7 @@ function expectedWelcome(nick: string, user: string, host: string | undefined):
80
90
  L(`${prefix} 003 ${nick} :This server was created in Phase 1`),
81
91
  L(`${prefix} 004 ${nick} ${sv} 0.2.0 iosw biklmnstp`),
82
92
  ...isupport,
83
- L(`${prefix} 375 ${nick} :- ${sv} Message of the day -`),
84
- L(`${prefix} 372 ${nick} :- MOTD placeholder`),
85
- L(`${prefix} 376 ${nick} :End of MOTD command`),
93
+ L(`${prefix} 422 ${nick} :MOTD File is missing`),
86
94
  ];
87
95
  }
88
96
 
@@ -489,19 +497,22 @@ describe('passReducer', () => {
489
497
  // ============================================================================
490
498
 
491
499
  describe('buildWelcomeLines', () => {
492
- it('produces the welcome block in the correct numeric order', () => {
500
+ it('produces the welcome block in the correct numeric order with MOTD content', () => {
493
501
  const state = makeState();
494
502
  state.nick = 'alice';
495
503
  state.user = 'alice';
496
504
  state.host = 'example.com';
497
- const lines = buildWelcomeLines(state, makeCtx(state));
498
- // 8 base lines (001-004, one or more 005, 375, 372, 376).
505
+ const lines = buildWelcomeLines(
506
+ state,
507
+ makeCtx(state, new FakeClock(1_000), new StaticMotdProvider(['line one', 'line two'])),
508
+ );
499
509
  expect(lines.length).toBeGreaterThanOrEqual(8);
500
510
  const codes = lines.map((l) => l.text.split(' ')[1]);
501
- // First four numerics in fixed order; 005 (>=1); final three in order.
511
+ // First four numerics in fixed order; 005 (>=1); MOTD block last.
502
512
  expect(codes.slice(0, 4)).toEqual(['001', '002', '003', '004']);
503
513
  expect(codes[4]).toBe('005');
504
- expect(codes.at(-3)).toBe('375');
514
+ expect(codes.at(-4)).toBe('375');
515
+ expect(codes.at(-3)).toBe('372');
505
516
  expect(codes.at(-2)).toBe('372');
506
517
  expect(codes.at(-1)).toBe('376');
507
518
  });
@@ -557,6 +568,89 @@ describe('buildWelcomeLines', () => {
557
568
  });
558
569
  });
559
570
 
571
+ // ============================================================================
572
+ // buildWelcomeLines — MOTD driven from MotdProvider
573
+ // ============================================================================
574
+
575
+ describe('buildWelcomeLines — MOTD content', () => {
576
+ it('emits one 372 per MOTD line sourced from the MotdProvider', () => {
577
+ const state = makeState();
578
+ state.nick = 'alice';
579
+ state.user = 'alice';
580
+ state.host = 'example.com';
581
+ const lines = buildWelcomeLines(
582
+ state,
583
+ makeCtx(state, new FakeClock(1_000), new StaticMotdProvider(['line A', 'line B'])),
584
+ );
585
+ const rpl372 = lines.filter((l) => l.text.split(' ')[1] === '372');
586
+ expect(rpl372).toHaveLength(2);
587
+ expect(rpl372[0]).toEqual(L(':irc.example.com 372 alice :- line A'));
588
+ expect(rpl372[1]).toEqual(L(':irc.example.com 372 alice :- line B'));
589
+ });
590
+
591
+ it('frames the MOTD content with 375 start and 376 end', () => {
592
+ const state = makeState();
593
+ state.nick = 'alice';
594
+ state.user = 'alice';
595
+ const lines = buildWelcomeLines(
596
+ state,
597
+ makeCtx(state, new FakeClock(1_000), new StaticMotdProvider(['hi'])),
598
+ );
599
+ expect(lines).toContainEqual(
600
+ L(':irc.example.com 375 alice :- irc.example.com Message of the day -'),
601
+ );
602
+ expect(lines).toContainEqual(L(':irc.example.com 372 alice :- hi'));
603
+ expect(lines).toContainEqual(L(':irc.example.com 376 alice :End of MOTD command'));
604
+ });
605
+
606
+ it('reads MOTD lines fresh from the provider on each call', () => {
607
+ const state = makeState();
608
+ state.nick = 'alice';
609
+ state.user = 'alice';
610
+ const provider = new StaticMotdProvider(['first']);
611
+ const ctx = makeCtx(state, new FakeClock(1_000), provider);
612
+ const first = buildWelcomeLines(state, ctx).filter((l) => l.text.split(' ')[1] === '372');
613
+ provider.setLines(['first', 'second']);
614
+ const second = buildWelcomeLines(state, ctx).filter((l) => l.text.split(' ')[1] === '372');
615
+ expect(first).toHaveLength(1);
616
+ expect(second).toHaveLength(2);
617
+ });
618
+
619
+ it('splits over-long MOTD lines so every emitted line fits the 510-byte budget', () => {
620
+ const state = makeState();
621
+ state.nick = 'alice';
622
+ state.user = 'alice';
623
+ state.host = 'example.com';
624
+ const long = 'A'.repeat(2_000);
625
+ const lines = buildWelcomeLines(
626
+ state,
627
+ makeCtx(state, new FakeClock(1_000), new StaticMotdProvider([long])),
628
+ );
629
+ for (const line of lines) {
630
+ expect(line.text.length).toBeLessThanOrEqual(510);
631
+ }
632
+ // The single over-long line is hard-wrapped into many 372 chunks.
633
+ const rpl372 = lines.filter((l) => l.text.split(' ')[1] === '372');
634
+ expect(rpl372.length).toBeGreaterThan(1);
635
+ });
636
+ });
637
+
638
+ describe('buildWelcomeLines — empty MOTD', () => {
639
+ it('emits 422 ERR_NOMOTD instead of a placeholder when no MOTD is configured', () => {
640
+ const state = makeState();
641
+ state.nick = 'alice';
642
+ state.user = 'alice';
643
+ const lines = buildWelcomeLines(state, makeCtx(state));
644
+ const codes = lines.map((l) => l.text.split(' ')[1]);
645
+ expect(codes.at(-1)).toBe('422');
646
+ expect(lines).toContainEqual(L(':irc.example.com 422 alice :MOTD File is missing'));
647
+ // No MOTD framing numerics leak through on the empty path.
648
+ expect(codes).not.toContain('375');
649
+ expect(codes).not.toContain('372');
650
+ expect(codes).not.toContain('376');
651
+ });
652
+ });
653
+
560
654
  // ============================================================================
561
655
  // emitWelcomeIfReady
562
656
  // ============================================================================
@@ -860,8 +954,6 @@ function expectedWelcomeWithHost(nick: string, hostmask: string): RawLine[] {
860
954
  L(`${prefix} 003 ${nick} :This server was created in Phase 1`),
861
955
  L(`${prefix} 004 ${nick} ${sv} 0.2.0 iosw biklmnstp`),
862
956
  ...isupport,
863
- L(`${prefix} 375 ${nick} :- ${sv} Message of the day -`),
864
- L(`${prefix} 372 ${nick} :- MOTD placeholder`),
865
- L(`${prefix} 376 ${nick} :End of MOTD command`),
957
+ L(`${prefix} 422 ${nick} :MOTD File is missing`),
866
958
  ];
867
959
  }