@openora/core 0.4.1-canary.111 → 0.4.1-canary.112

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 (97) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/admin-console/plugin.d.ts +3 -0
  3. package/dist/admin-console/plugin.d.ts.map +1 -1
  4. package/dist/analytics/plugin.d.ts +3 -0
  5. package/dist/analytics/plugin.d.ts.map +1 -1
  6. package/dist/audit/plugin.d.ts +3 -0
  7. package/dist/audit/plugin.d.ts.map +1 -1
  8. package/dist/casino/gaming/plugin.d.ts +3 -0
  9. package/dist/casino/gaming/plugin.d.ts.map +1 -1
  10. package/dist/casino/lobby/plugin.d.ts +3 -0
  11. package/dist/casino/lobby/plugin.d.ts.map +1 -1
  12. package/dist/cms/plugin.d.ts +3 -0
  13. package/dist/cms/plugin.d.ts.map +1 -1
  14. package/dist/compliance/plugin.d.ts +3 -0
  15. package/dist/compliance/plugin.d.ts.map +1 -1
  16. package/dist/contracts/schemas/__tests__/platform-config.test.d.ts +2 -0
  17. package/dist/contracts/schemas/__tests__/platform-config.test.d.ts.map +1 -0
  18. package/dist/contracts/schemas/__tests__/platform-config.test.js +16 -0
  19. package/dist/contracts/schemas/__tests__/platform-config.test.js.map +1 -0
  20. package/dist/contracts/schemas/chat-attachment.d.ts +20 -0
  21. package/dist/contracts/schemas/chat-attachment.d.ts.map +1 -0
  22. package/dist/contracts/schemas/chat-attachment.js +14 -0
  23. package/dist/contracts/schemas/chat-attachment.js.map +1 -0
  24. package/dist/contracts/schemas/index.d.ts +1 -0
  25. package/dist/contracts/schemas/index.d.ts.map +1 -1
  26. package/dist/contracts/schemas/index.js +1 -0
  27. package/dist/contracts/schemas/index.js.map +1 -1
  28. package/dist/contracts/schemas/platform-config.d.ts +10 -0
  29. package/dist/contracts/schemas/platform-config.d.ts.map +1 -1
  30. package/dist/contracts/schemas/platform-config.js +28 -0
  31. package/dist/contracts/schemas/platform-config.js.map +1 -1
  32. package/dist/engagement/chat/__tests__/chat.service.int.test.js +138 -14
  33. package/dist/engagement/chat/__tests__/chat.service.int.test.js.map +1 -1
  34. package/dist/engagement/chat/__tests__/contract.test.d.ts +2 -0
  35. package/dist/engagement/chat/__tests__/contract.test.d.ts.map +1 -0
  36. package/dist/engagement/chat/__tests__/contract.test.js +44 -0
  37. package/dist/engagement/chat/__tests__/contract.test.js.map +1 -0
  38. package/dist/engagement/chat/__tests__/global-chat-room.int.test.js +10 -1
  39. package/dist/engagement/chat/__tests__/global-chat-room.int.test.js.map +1 -1
  40. package/dist/engagement/chat/__tests__/validate-attachment.test.d.ts +2 -0
  41. package/dist/engagement/chat/__tests__/validate-attachment.test.d.ts.map +1 -0
  42. package/dist/engagement/chat/__tests__/validate-attachment.test.js +38 -0
  43. package/dist/engagement/chat/__tests__/validate-attachment.test.js.map +1 -0
  44. package/dist/engagement/chat/contract/index.d.ts +185 -2
  45. package/dist/engagement/chat/contract/index.d.ts.map +1 -1
  46. package/dist/engagement/chat/contract/index.js +21 -3
  47. package/dist/engagement/chat/contract/index.js.map +1 -1
  48. package/dist/engagement/chat/drizzle/migrations/0008_robust_korvac.sql +1 -0
  49. package/dist/engagement/chat/drizzle/migrations/meta/0008_snapshot.json +1462 -0
  50. package/dist/engagement/chat/drizzle/migrations/meta/_journal.json +7 -0
  51. package/dist/engagement/chat/moderation/index.d.ts +1 -0
  52. package/dist/engagement/chat/moderation/index.d.ts.map +1 -1
  53. package/dist/engagement/chat/moderation/index.js +1 -0
  54. package/dist/engagement/chat/moderation/index.js.map +1 -1
  55. package/dist/engagement/chat/moderation/validate-attachment.d.ts +9 -0
  56. package/dist/engagement/chat/moderation/validate-attachment.d.ts.map +1 -0
  57. package/dist/engagement/chat/moderation/validate-attachment.js +27 -0
  58. package/dist/engagement/chat/moderation/validate-attachment.js.map +1 -0
  59. package/dist/engagement/chat/plugin.d.ts +3 -0
  60. package/dist/engagement/chat/plugin.d.ts.map +1 -1
  61. package/dist/engagement/chat/plugin.js +14 -2
  62. package/dist/engagement/chat/plugin.js.map +1 -1
  63. package/dist/engagement/chat/router/index.d.ts +118 -2
  64. package/dist/engagement/chat/router/index.d.ts.map +1 -1
  65. package/dist/engagement/chat/router/index.js +10 -4
  66. package/dist/engagement/chat/router/index.js.map +1 -1
  67. package/dist/engagement/chat/schema/index.d.ts +37 -0
  68. package/dist/engagement/chat/schema/index.d.ts.map +1 -1
  69. package/dist/engagement/chat/schema/index.js +1 -0
  70. package/dist/engagement/chat/schema/index.js.map +1 -1
  71. package/dist/engagement/chat/service/chat.service.d.ts +83 -4
  72. package/dist/engagement/chat/service/chat.service.d.ts.map +1 -1
  73. package/dist/engagement/chat/service/chat.service.js +22 -4
  74. package/dist/engagement/chat/service/chat.service.js.map +1 -1
  75. package/dist/engagement/chat-commands/plugin.d.ts +3 -0
  76. package/dist/engagement/chat-commands/plugin.d.ts.map +1 -1
  77. package/dist/engagement/notifications/plugin.d.ts +3 -0
  78. package/dist/engagement/notifications/plugin.d.ts.map +1 -1
  79. package/dist/engagement/social/plugin.d.ts +3 -0
  80. package/dist/engagement/social/plugin.d.ts.map +1 -1
  81. package/dist/iam/plugin.d.ts +3 -0
  82. package/dist/iam/plugin.d.ts.map +1 -1
  83. package/dist/pam/identity/plugin.d.ts +3 -0
  84. package/dist/pam/identity/plugin.d.ts.map +1 -1
  85. package/dist/pam/player-management/plugin.d.ts +3 -0
  86. package/dist/pam/player-management/plugin.d.ts.map +1 -1
  87. package/dist/pam/player-note/plugin.d.ts +3 -0
  88. package/dist/pam/player-note/plugin.d.ts.map +1 -1
  89. package/dist/pam/profile/plugin.d.ts +3 -0
  90. package/dist/pam/profile/plugin.d.ts.map +1 -1
  91. package/dist/pam/tag/plugin.d.ts +3 -0
  92. package/dist/pam/tag/plugin.d.ts.map +1 -1
  93. package/dist/server/runtime/core-token-catalog.d.ts +3 -0
  94. package/dist/server/runtime/core-token-catalog.d.ts.map +1 -1
  95. package/dist/wallet/plugin.d.ts +3 -0
  96. package/dist/wallet/plugin.d.ts.map +1 -1
  97. package/package.json +1 -1
@@ -78,6 +78,9 @@ declare const _default: {
78
78
  maxStakePerBet?: number | undefined;
79
79
  sessionReminderMinutes?: number | undefined;
80
80
  }>;
81
+ chat: {
82
+ allowedAttachmentHosts: string[];
83
+ };
81
84
  activeBrand?: string | undefined;
82
85
  kyc?: {
83
86
  gateWithdrawals: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/admin-console/plugin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAaA,wBAkB8C"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/admin-console/plugin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAaA,wBAkB8C"}
@@ -78,6 +78,9 @@ declare const _default: {
78
78
  maxStakePerBet?: number | undefined;
79
79
  sessionReminderMinutes?: number | undefined;
80
80
  }>;
81
+ chat: {
82
+ allowedAttachmentHosts: string[];
83
+ };
81
84
  activeBrand?: string | undefined;
82
85
  kyc?: {
83
86
  gateWithdrawals: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/analytics/plugin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,wBAY8C"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/analytics/plugin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,wBAY8C"}
@@ -79,6 +79,9 @@ declare const _default: {
79
79
  maxStakePerBet?: number | undefined;
80
80
  sessionReminderMinutes?: number | undefined;
81
81
  }>;
82
+ chat: {
83
+ allowedAttachmentHosts: string[];
84
+ };
82
85
  activeBrand?: string | undefined;
83
86
  kyc?: {
84
87
  gateWithdrawals: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/audit/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAS5E,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,MAAM,EACb,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACzB,OAAO,CAAC,WAAW,CAAC,CA2nBtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0FD,wBAoC8C"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/audit/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAS5E,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,MAAM,EACb,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACzB,OAAO,CAAC,WAAW,CAAC,CA2nBtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0FD,wBAoC8C"}
@@ -79,6 +79,9 @@ declare const _default: {
79
79
  maxStakePerBet?: number | undefined;
80
80
  sessionReminderMinutes?: number | undefined;
81
81
  }>;
82
+ chat: {
83
+ allowedAttachmentHosts: string[];
84
+ };
82
85
  activeBrand?: string | undefined;
83
86
  kyc?: {
84
87
  gateWithdrawals: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/casino/gaming/plugin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgBA,wBAqB8C"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/casino/gaming/plugin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgBA,wBAqB8C"}
@@ -77,6 +77,9 @@ declare const _default: {
77
77
  maxStakePerBet?: number | undefined;
78
78
  sessionReminderMinutes?: number | undefined;
79
79
  }>;
80
+ chat: {
81
+ allowedAttachmentHosts: string[];
82
+ };
80
83
  activeBrand?: string | undefined;
81
84
  kyc?: {
82
85
  gateWithdrawals: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/casino/lobby/plugin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAMA,wBAO8C"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/casino/lobby/plugin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAMA,wBAO8C"}
@@ -77,6 +77,9 @@ declare const _default: {
77
77
  maxStakePerBet?: number | undefined;
78
78
  sessionReminderMinutes?: number | undefined;
79
79
  }>;
80
+ chat: {
81
+ allowedAttachmentHosts: string[];
82
+ };
80
83
  activeBrand?: string | undefined;
81
84
  kyc?: {
82
85
  gateWithdrawals: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/cms/plugin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAMA,wBAU8C"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/cms/plugin.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAMA,wBAU8C"}
@@ -80,6 +80,9 @@ declare const _default: {
80
80
  maxStakePerBet?: number | undefined;
81
81
  sessionReminderMinutes?: number | undefined;
82
82
  }>;
83
+ chat: {
84
+ allowedAttachmentHosts: string[];
85
+ };
83
86
  activeBrand?: string | undefined;
84
87
  kyc?: {
85
88
  gateWithdrawals: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/compliance/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,EAoBL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACvB,MAAM,yBAAyB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCjC,wBA6K8C"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/compliance/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,EAoBL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACvB,MAAM,yBAAyB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCjC,wBA6K8C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=platform-config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-config.test.d.ts","sourceRoot":"","sources":["../../../../src/contracts/schemas/__tests__/platform-config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { definePlatformConfig } from '../platform-config.js';
3
+ describe('definePlatformConfig', () => {
4
+ it('canonicalizes attachment hosts before services consume the config', () => {
5
+ const config = definePlatformConfig({
6
+ chat: { allowedAttachmentHosts: ['MEDIA.EXAMPLE.COM'] },
7
+ });
8
+ expect(config.chat.allowedAttachmentHosts).toEqual(['media.example.com']);
9
+ });
10
+ it('rejects attachment hosts that include URL components', () => {
11
+ expect(() => definePlatformConfig({
12
+ chat: { allowedAttachmentHosts: ['https://media.example.com/path'] },
13
+ })).toThrow(/chat\.allowedAttachmentHosts\.0/);
14
+ });
15
+ });
16
+ //# sourceMappingURL=platform-config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-config.test.js","sourceRoot":"","sources":["../../../../src/contracts/schemas/__tests__/platform-config.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,MAAM,MAAM,GAAG,oBAAoB,CAAC;YAClC,IAAI,EAAE,EAAE,sBAAsB,EAAE,CAAC,mBAAmB,CAAC,EAAE;SACxD,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,OAAO,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,CAAC,GAAG,EAAE,CACV,oBAAoB,CAAC;YACnB,IAAI,EAAE,EAAE,sBAAsB,EAAE,CAAC,gCAAgC,CAAC,EAAE;SACrE,CAAC,CACH,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import * as z from 'zod';
2
+ export declare const CHAT_ATTACHMENT_KINDS: readonly ["gif"];
3
+ export declare const ChatAttachmentKindSchema: z.ZodEnum<{
4
+ gif: "gif";
5
+ }>;
6
+ export type ChatAttachmentKind = z.infer<typeof ChatAttachmentKindSchema>;
7
+ export declare const ChatAttachmentSchema: z.ZodObject<{
8
+ kind: z.ZodEnum<{
9
+ gif: "gif";
10
+ }>;
11
+ provider: z.ZodString;
12
+ externalId: z.ZodString;
13
+ url: z.ZodURL;
14
+ previewUrl: z.ZodURL;
15
+ width: z.ZodNumber;
16
+ height: z.ZodNumber;
17
+ title: z.ZodString;
18
+ }, z.core.$strip>;
19
+ export type ChatAttachment = z.infer<typeof ChatAttachmentSchema>;
20
+ //# sourceMappingURL=chat-attachment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-attachment.d.ts","sourceRoot":"","sources":["../../../src/contracts/schemas/chat-attachment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,eAAO,MAAM,qBAAqB,kBAAmB,CAAC;AACtD,eAAO,MAAM,wBAAwB;;EAAgC,CAAC;AACtE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,eAAO,MAAM,oBAAoB;;;;;;;;;;;iBAS/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
@@ -0,0 +1,14 @@
1
+ import * as z from 'zod';
2
+ export const CHAT_ATTACHMENT_KINDS = ['gif'];
3
+ export const ChatAttachmentKindSchema = z.enum(CHAT_ATTACHMENT_KINDS);
4
+ export const ChatAttachmentSchema = z.object({
5
+ kind: ChatAttachmentKindSchema,
6
+ provider: z.string().trim().min(1).max(32),
7
+ externalId: z.string().trim().min(1).max(128),
8
+ url: z.url().max(2048),
9
+ previewUrl: z.url().max(2048),
10
+ width: z.number().int().positive().max(4096),
11
+ height: z.number().int().positive().max(4096),
12
+ title: z.string().trim().max(200),
13
+ });
14
+ //# sourceMappingURL=chat-attachment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-attachment.js","sourceRoot":"","sources":["../../../src/contracts/schemas/chat-attachment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAEzB,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,KAAK,CAAU,CAAC;AACtD,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;AAGtE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,IAAI,EAAE,wBAAwB;IAC9B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAC1C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAC7C,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC;IACtB,UAAU,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC;IAC7B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC;IAC5C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC;IAC7C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC;CAClC,CAAC,CAAC"}
@@ -12,4 +12,5 @@ export * from './events.js';
12
12
  export * from './reporting.js';
13
13
  export * from './chat-command-metadata.js';
14
14
  export * from './chat-command.js';
15
+ export * from './chat-attachment.js';
15
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/contracts/schemas/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,UAAU,CAAC;AACzB,cAAc,sBAAsB,CAAC;AACrC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/contracts/schemas/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,UAAU,CAAC;AACzB,cAAc,sBAAsB,CAAC;AACrC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC"}
@@ -12,4 +12,5 @@ export * from './events.js';
12
12
  export * from './reporting.js';
13
13
  export * from './chat-command-metadata.js';
14
14
  export * from './chat-command.js';
15
+ export * from './chat-attachment.js';
15
16
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/contracts/schemas/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,UAAU,CAAC;AACzB,cAAc,sBAAsB,CAAC;AACrC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/contracts/schemas/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,UAAU,CAAC;AACzB,cAAc,sBAAsB,CAAC;AACrC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC"}
@@ -56,6 +56,10 @@ export declare const RegistrationConfigSchema: z.ZodObject<{
56
56
  termsVersion: z.ZodString;
57
57
  requireEmailVerification: z.ZodDefault<z.ZodBoolean>;
58
58
  }, z.core.$strict>;
59
+ export declare const ChatConfigSchema: z.ZodObject<{
60
+ allowedAttachmentHosts: z.ZodDefault<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
61
+ }, z.core.$strict>;
62
+ export type ChatConfig = z.infer<typeof ChatConfigSchema>;
59
63
  export declare const PlatformConfigSchema: z.ZodObject<{
60
64
  features: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
61
65
  brands: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -108,6 +112,9 @@ export declare const PlatformConfigSchema: z.ZodObject<{
108
112
  requireEmailVerification: z.ZodDefault<z.ZodBoolean>;
109
113
  }, z.core.$strict>>;
110
114
  supportedLanguages: z.ZodOptional<z.ZodArray<z.ZodString>>;
115
+ chat: z.ZodDefault<z.ZodObject<{
116
+ allowedAttachmentHosts: z.ZodDefault<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
117
+ }, z.core.$strict>>;
111
118
  }, z.core.$strict>;
112
119
  export type PlatformConfig = z.infer<typeof PlatformConfigSchema>;
113
120
  export type PlatformConfigInput = z.input<typeof PlatformConfigSchema>;
@@ -134,6 +141,9 @@ export declare const PLATFORM_CONFIG: import("../adapters/token.js").Token<{
134
141
  maxStakePerBet?: number | undefined;
135
142
  sessionReminderMinutes?: number | undefined;
136
143
  }>;
144
+ chat: {
145
+ allowedAttachmentHosts: string[];
146
+ };
137
147
  activeBrand?: string | undefined;
138
148
  kyc?: {
139
149
  gateWithdrawals: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"platform-config.d.ts","sourceRoot":"","sources":["../../../src/contracts/schemas/platform-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,kBAAkB,wCAAoC,CAAC;AAEpE,eAAO,MAAM,WAAW;;;;;kBAUb,CAAC;AAEZ,eAAO,MAAM,eAAe;;;;;kBAcjB,CAAC;AAEZ,eAAO,MAAM,0BAA0B;;;;kBAQ5B,CAAC;AAEZ,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;kBAkEpB,CAAC;AAEZ,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,wBAAwB;;;kBAU1B,CAAC;AAEZ,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAkD7B,CAAC;AAEL,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAEvE;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,mBAAmB,GAAG,cAAc,CAShF;AAED,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB,EAAE,cAAyC,CAAC;AAE9E,+EAA+E;AAC/E,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAAiD,CAAC"}
1
+ {"version":3,"file":"platform-config.d.ts","sourceRoot":"","sources":["../../../src/contracts/schemas/platform-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,kBAAkB,wCAAoC,CAAC;AAEpE,eAAO,MAAM,WAAW;;;;;kBAUb,CAAC;AAEZ,eAAO,MAAM,eAAe;;;;;kBAcjB,CAAC;AAEZ,eAAO,MAAM,0BAA0B;;;;kBAQ5B,CAAC;AAEZ,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;kBAkEpB,CAAC;AAEZ,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,wBAAwB;;;kBAU1B,CAAC;AA2BZ,eAAO,MAAM,gBAAgB;;kBAKlB,CAAC;AACZ,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAoD7B,CAAC;AAEL,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAEvE;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,mBAAmB,GAAG,cAAc,CAShF;AAED,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB,EAAE,cAAyC,CAAC;AAE9E,+EAA+E;AAC/E,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAAiD,CAAC"}
@@ -129,6 +129,32 @@ export const RegistrationConfigSchema = z
129
129
  requireEmailVerification: z.boolean().default(false),
130
130
  })
131
131
  .strict();
132
+ const AttachmentHostSchema = z
133
+ .string()
134
+ .trim()
135
+ .min(1)
136
+ .refine((hostname) => {
137
+ try {
138
+ const url = new URL(`https://${hostname}`);
139
+ return (url.hostname === hostname.toLowerCase() &&
140
+ url.pathname === '/' &&
141
+ url.port === '' &&
142
+ url.username === '' &&
143
+ url.password === '' &&
144
+ url.search === '' &&
145
+ url.hash === '');
146
+ }
147
+ catch {
148
+ return false;
149
+ }
150
+ }, { message: 'must be a hostname without a protocol, port, credentials, or path' })
151
+ .transform((hostname) => hostname.toLowerCase());
152
+ export const ChatConfigSchema = z
153
+ .object({
154
+ /** Hostnames a chat message attachment may be served from. Empty = attachments disabled. */
155
+ allowedAttachmentHosts: z.array(AttachmentHostSchema).default([]),
156
+ })
157
+ .strict();
132
158
  export const PlatformConfigSchema = z
133
159
  .object({
134
160
  /**
@@ -169,6 +195,8 @@ export const PlatformConfigSchema = z
169
195
  * Undefined or empty means no restriction - any value is accepted.
170
196
  */
171
197
  supportedLanguages: z.array(z.string().min(1)).optional(),
198
+ /** Chat attachment host allow-list. Absent = built-in default (empty = disabled). */
199
+ chat: ChatConfigSchema.default({ allowedAttachmentHosts: [] }),
172
200
  })
173
201
  .strict()
174
202
  .superRefine((cfg, ctx) => {
@@ -1 +1 @@
1
- {"version":3,"file":"platform-config.js","sourceRoot":"","sources":["../../../src/contracts/schemas/platform-config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"platform-config.js","sourceRoot":"","sources":["../../../src/contracts/schemas/platform-config.ts"],"names":[],"mappings":"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"}
@@ -18,7 +18,7 @@ import { migrate } from '../migrate.js';
18
18
  // NO_CLIENT_META,
19
19
  // } from '../../../testing/mock.js';
20
20
  import { chatChannel } from '@openora/core/contracts';
21
- import { ChatService, ChatMessageBlockedError, ChatSelfBlockError, ChatSelfIgnoreError, ChatRoomSlugConflictError, ChatRoomJoinCodeNotFoundError, ChatRoomBannedError, ChatRoomLimitReachedError, ChatRoomLastModeratorError, ChatRoomNotMemberError, ChatRoomNotModeratorError, ChatRoomSelfModerationError, ChatRoomOwnershipError, } from '../service/chat.service.js';
21
+ import { ChatService, ChatMessageBlockedError, ChatAttachmentRejectedError, ChatSelfBlockError, ChatSelfIgnoreError, ChatRoomSlugConflictError, ChatRoomJoinCodeNotFoundError, ChatRoomBannedError, ChatRoomLimitReachedError, ChatRoomLastModeratorError, ChatRoomNotMemberError, ChatRoomNotModeratorError, ChatRoomSelfModerationError, ChatRoomOwnershipError, } from '../service/chat.service.js';
22
22
  import { ChatModerationService, ChatRoomNotFoundError, ChatMessageNotFoundError, ChatPlayerMutedError, ChatPlayerBannedError, ChatAdminPrivateRoomModerationError, } from '../service/chat-moderation.service.js';
23
23
  import { chatMessage, chatRoom, chatRoomMember, chatRoomBan, chatRoomRule, chatRoomConfiguration, chatRoomMute, chatRoomRemove, chatUserBlock, chatUserIgnore, chatMute, chatPlatformBan, } from '../schema/index.js';
24
24
  import { ChatRoomMembershipService } from '../service/chat-room-membership.service.js';
@@ -35,7 +35,7 @@ function makeService(directory = mock({
35
35
  isActive: true,
36
36
  role: 'player',
37
37
  })),
38
- }), socialCommands) {
38
+ }), socialCommands, allowedAttachmentHosts = []) {
39
39
  const transport = new InProcessRealtimeTransport();
40
40
  const events = makeEventBus();
41
41
  const audit = mock({
@@ -44,7 +44,17 @@ function makeService(directory = mock({
44
44
  });
45
45
  const moderation = new ChatModerationService(db.drizzle, transport, audit);
46
46
  const identityReader = makeIdentityReader();
47
- const chatService = new ChatService(db.drizzle, events, transport, directory, audit, moderation, identityReader, socialCommands);
47
+ const chatService = new ChatService({
48
+ drizzle: db.drizzle,
49
+ events,
50
+ transport,
51
+ directory,
52
+ audit,
53
+ moderation,
54
+ identityReader,
55
+ allowedAttachmentHosts,
56
+ socialCommands,
57
+ });
48
58
  const membership = new ChatRoomMembershipService(db.drizzle, events, audit, transport, identityReader);
49
59
  const roomBan = new ChatRoomBanService(db.drizzle, events, audit, transport, identityReader);
50
60
  const roomMute = new ChatRoomMuteService(db.drizzle, audit);
@@ -128,6 +138,7 @@ describe('ChatService realtime wiring', () => {
128
138
  content: 'hi',
129
139
  type: 'user',
130
140
  metadata: null,
141
+ attachment: null,
131
142
  isDeleted: false,
132
143
  createdAt: '2026-05-29T00:00:00.000Z',
133
144
  };
@@ -188,6 +199,7 @@ describe('ChatService.subscribeMessages per-viewer block filtering (real PG)', (
188
199
  content: 'hi',
189
200
  type: 'user',
190
201
  metadata: null,
202
+ attachment: null,
191
203
  isDeleted: false,
192
204
  createdAt: '2026-05-29T00:00:00.000Z',
193
205
  };
@@ -240,7 +252,11 @@ describe('ChatService.sendGlobalMessage (real PG)', () => {
240
252
  it('stores the username from the verified user, ignoring the header fallback', async () => {
241
253
  const { svc, events } = makeService();
242
254
  const account = await seedUser(db, { name: 'Platform Admin', username: 'platform_admin' });
243
- const msg = await svc.sendGlobalMessage(account.id, 'spoofed-header-name', 'hi');
255
+ const msg = await svc.sendGlobalMessage({
256
+ userId: account.id,
257
+ username: 'spoofed-header-name',
258
+ content: 'hi',
259
+ });
244
260
  expect(msg.username).toBe('platform_admin');
245
261
  const [stored] = await db.drizzle.db.select().from(chatMessage);
246
262
  expect(stored).toMatchObject({ roomId: null, username: 'platform_admin' });
@@ -253,20 +269,24 @@ describe('ChatService.sendGlobalMessage (real PG)', () => {
253
269
  it('falls back to the passed name, then anonymous, when no user row exists', async () => {
254
270
  const { svc } = makeService();
255
271
  const userId = randomUUID();
256
- expect((await svc.sendGlobalMessage(userId, 'fallback', 'hi')).username).toBe('fallback');
257
- expect((await svc.sendGlobalMessage(userId, '', 'hi')).username).toBe('anonymous');
272
+ expect((await svc.sendGlobalMessage({ userId, username: 'fallback', content: 'hi' })).username).toBe('fallback');
273
+ expect((await svc.sendGlobalMessage({ userId, username: '', content: 'hi' })).username).toBe('anonymous');
258
274
  });
259
275
  it('blocks profane messages before they are stored or published', async () => {
260
276
  const { svc, transport } = makeService();
261
277
  const delivered = [];
262
278
  transport.subscribe('chat:global', (m) => delivered.push(m));
263
- await expect(svc.sendGlobalMessage(randomUUID(), 'Alice', 'this is shit')).rejects.toThrow(ChatMessageBlockedError);
279
+ await expect(svc.sendGlobalMessage({ userId: randomUUID(), username: 'Alice', content: 'this is shit' })).rejects.toThrow(ChatMessageBlockedError);
264
280
  expect(delivered).toHaveLength(0);
265
281
  expect(await db.drizzle.db.select().from(chatMessage)).toHaveLength(0);
266
282
  });
267
283
  it('persists URL-sanitized content', async () => {
268
284
  const { svc } = makeService();
269
- const msg = await svc.sendGlobalMessage(randomUUID(), 'Alice', 'click javascript:alert(1)');
285
+ const msg = await svc.sendGlobalMessage({
286
+ userId: randomUUID(),
287
+ username: 'Alice',
288
+ content: 'click javascript:alert(1)',
289
+ });
270
290
  expect(msg.content).toBe('click javascript alert(1)');
271
291
  const [stored] = await db.drizzle.db.select().from(chatMessage);
272
292
  expect(stored?.content).toBe('click javascript alert(1)');
@@ -275,9 +295,60 @@ describe('ChatService.sendGlobalMessage (real PG)', () => {
275
295
  const { svc, transport } = makeService();
276
296
  const delivered = [];
277
297
  transport.subscribe('chat:global', (m) => delivered.push(m));
278
- const msg = await svc.sendGlobalMessage(randomUUID(), 'Alice', 'hello');
298
+ const msg = await svc.sendGlobalMessage({
299
+ userId: randomUUID(),
300
+ username: 'Alice',
301
+ content: 'hello',
302
+ });
279
303
  expect(delivered.map((m) => m.id)).toEqual([msg.id]);
280
304
  });
305
+ it('persists an attachment whose host is on the allow-list and publishes it', async () => {
306
+ const { svc, transport } = makeService(undefined, undefined, ['media.example.com']);
307
+ const delivered = [];
308
+ transport.subscribe('chat:global', (m) => delivered.push(m));
309
+ const attachment = {
310
+ kind: 'gif',
311
+ provider: 'example',
312
+ externalId: 'abc123',
313
+ url: 'https://media.example.com/abc123.gif',
314
+ previewUrl: 'https://media.example.com/abc123-preview.gif',
315
+ width: 320,
316
+ height: 240,
317
+ title: 'a gif',
318
+ };
319
+ const msg = await svc.sendGlobalMessage({
320
+ userId: randomUUID(),
321
+ username: 'Alice',
322
+ content: '',
323
+ attachment,
324
+ });
325
+ expect(msg.type).toBe('user');
326
+ if (msg.type === 'user') {
327
+ expect(msg.attachment).toEqual(attachment);
328
+ }
329
+ const [stored] = await db.drizzle.db.select().from(chatMessage);
330
+ expect(stored?.attachment).toEqual(attachment);
331
+ expect(delivered.map((m) => m.id)).toEqual([msg.id]);
332
+ });
333
+ it('rejects an attachment whose host is not on the allow-list', async () => {
334
+ const { svc } = makeService(undefined, undefined, ['media.example.com']);
335
+ await expect(svc.sendGlobalMessage({
336
+ userId: randomUUID(),
337
+ username: 'Alice',
338
+ content: '',
339
+ attachment: {
340
+ kind: 'gif',
341
+ provider: 'example',
342
+ externalId: 'abc123',
343
+ url: 'https://evil.example.net/abc123.gif',
344
+ previewUrl: 'https://evil.example.net/abc123-preview.gif',
345
+ width: 320,
346
+ height: 240,
347
+ title: 'a gif',
348
+ },
349
+ })).rejects.toThrow(ChatAttachmentRejectedError);
350
+ expect(await db.drizzle.db.select().from(chatMessage)).toHaveLength(0);
351
+ });
281
352
  });
282
353
  describe('ChatService message reads (real PG)', () => {
283
354
  it('returns global messages newest-first, excluding soft-deleted ones', async () => {
@@ -362,6 +433,59 @@ describe('ChatService.sendRoomMessage (real PG)', () => {
362
433
  })).rejects.toBeInstanceOf(ChatRoomNotMemberError);
363
434
  expect(await db.drizzle.db.select().from(chatMessage)).toHaveLength(0);
364
435
  });
436
+ it('persists an attachment whose host is on the allow-list and publishes it', async () => {
437
+ const { svc, transport } = makeService(undefined, undefined, ['media.example.com']);
438
+ const room = await seedRoom();
439
+ const account = await seedUser(db, { name: 'Alice', username: 'alice' });
440
+ const delivered = [];
441
+ transport.subscribe(chatChannel(room.id), (m) => delivered.push(m));
442
+ const attachment = {
443
+ kind: 'gif',
444
+ provider: 'example',
445
+ externalId: 'abc123',
446
+ url: 'https://media.example.com/abc123.gif',
447
+ previewUrl: 'https://media.example.com/abc123-preview.gif',
448
+ width: 320,
449
+ height: 240,
450
+ title: 'a gif',
451
+ };
452
+ const msg = await svc.sendRoomMessage({
453
+ userId: account.id,
454
+ username: 'ignored',
455
+ roomId: room.id,
456
+ content: '',
457
+ attachment,
458
+ });
459
+ expect(msg.type).toBe('user');
460
+ if (msg.type === 'user') {
461
+ expect(msg.attachment).toEqual(attachment);
462
+ }
463
+ const [stored] = await db.drizzle.db.select().from(chatMessage);
464
+ expect(stored?.attachment).toEqual(attachment);
465
+ expect(delivered.map((m) => m.id)).toEqual([msg.id]);
466
+ });
467
+ it('rejects an attachment whose host is not on the allow-list', async () => {
468
+ const { svc } = makeService(undefined, undefined, ['media.example.com']);
469
+ const room = await seedRoom();
470
+ const account = await seedUser(db, { name: 'Alice', username: 'alice' });
471
+ await expect(svc.sendRoomMessage({
472
+ userId: account.id,
473
+ username: 'ignored',
474
+ roomId: room.id,
475
+ content: '',
476
+ attachment: {
477
+ kind: 'gif',
478
+ provider: 'example',
479
+ externalId: 'abc123',
480
+ url: 'https://evil.example.net/abc123.gif',
481
+ previewUrl: 'https://evil.example.net/abc123-preview.gif',
482
+ width: 320,
483
+ height: 240,
484
+ title: 'a gif',
485
+ },
486
+ })).rejects.toThrow(ChatAttachmentRejectedError);
487
+ expect(await db.drizzle.db.select().from(chatMessage)).toHaveLength(0);
488
+ });
365
489
  });
366
490
  describe('ChatService.deleteMessage (real PG)', () => {
367
491
  it('soft-deletes a message for a room owner and publishes a tombstone', async () => {
@@ -1200,7 +1324,7 @@ describe('ChatService moderation (real PG)', () => {
1200
1324
  roomId: globalRoom.id,
1201
1325
  readOnlyMode: true,
1202
1326
  });
1203
- await expect(svc.sendGlobalMessage(randomUUID(), 'ReadOnly', 'hello')).rejects.toBeInstanceOf(ChatPlayerMutedError);
1327
+ await expect(svc.sendGlobalMessage({ userId: randomUUID(), username: 'ReadOnly', content: 'hello' })).rejects.toBeInstanceOf(ChatPlayerMutedError);
1204
1328
  });
1205
1329
  it.each(['__all_public', '__all'])('enforces %s mutes on global sends', async (scope) => {
1206
1330
  const { svc, moderation } = makeService();
@@ -1213,7 +1337,7 @@ describe('ChatService moderation (real PG)', () => {
1213
1337
  ...NO_CLIENT_META,
1214
1338
  });
1215
1339
  const [mute] = await db.drizzle.db.select().from(chatMute).limit(1);
1216
- await expect(svc.sendGlobalMessage(mute.userId, 'Muted', 'hello')).rejects.toBeInstanceOf(ChatPlayerMutedError);
1340
+ await expect(svc.sendGlobalMessage({ userId: mute.userId, username: 'Muted', content: 'hello' })).rejects.toBeInstanceOf(ChatPlayerMutedError);
1217
1341
  });
1218
1342
  it('supports a global mute when the virtual global room has no row', async () => {
1219
1343
  const { svc, moderation } = makeService();
@@ -1226,7 +1350,7 @@ describe('ChatService moderation (real PG)', () => {
1226
1350
  actorId: randomUUID(),
1227
1351
  ...NO_CLIENT_META,
1228
1352
  })).resolves.toEqual({ success: true });
1229
- await expect(svc.sendGlobalMessage(userId, 'Muted', 'hello')).rejects.toBeInstanceOf(ChatPlayerMutedError);
1353
+ await expect(svc.sendGlobalMessage({ userId, username: 'Muted', content: 'hello' })).rejects.toBeInstanceOf(ChatPlayerMutedError);
1230
1354
  });
1231
1355
  it('enforces an all-chat mute in private rooms', async () => {
1232
1356
  const { svc, moderation } = makeService();
@@ -1278,7 +1402,7 @@ describe('ChatService moderation (real PG)', () => {
1278
1402
  actorId: randomUUID(),
1279
1403
  ...NO_CLIENT_META,
1280
1404
  });
1281
- await expect(svc.sendGlobalMessage(userId, 'Banned', 'hello')).rejects.toBeInstanceOf(ChatPlayerBannedError);
1405
+ await expect(svc.sendGlobalMessage({ userId, username: 'Banned', content: 'hello' })).rejects.toBeInstanceOf(ChatPlayerBannedError);
1282
1406
  await expect(svc.sendRoomMessage({ userId, username: 'Banned', roomId: room.id, content: 'hello' })).resolves.toMatchObject({ userId });
1283
1407
  await moderation.unban({
1284
1408
  userId,
@@ -1286,7 +1410,7 @@ describe('ChatService moderation (real PG)', () => {
1286
1410
  actorId: randomUUID(),
1287
1411
  ...NO_CLIENT_META,
1288
1412
  });
1289
- await expect(svc.sendGlobalMessage(userId, 'Banned', 'hello')).resolves.toMatchObject({
1413
+ await expect(svc.sendGlobalMessage({ userId, username: 'Banned', content: 'hello' })).resolves.toMatchObject({
1290
1414
  userId,
1291
1415
  });
1292
1416
  });