diff --git a/shared/src/collab/collab.schema.ts b/shared/src/collab/collab.schema.ts index be2a083bf..7d9a2a497 100644 --- a/shared/src/collab/collab.schema.ts +++ b/shared/src/collab/collab.schema.ts @@ -48,14 +48,11 @@ export const collabPollVoteRequestSchema = z.object({ }); export type CollabPollVoteRequest = z.infer; -const httpUrl = z.string().trim().min(1).refine((value) => { - try { - const parsed = new URL(value); - return parsed.protocol === 'http:' || parsed.protocol === 'https:'; - } catch { - return false; - } -}, 'A valid http(s) URL is required'); +// `z.url()` rather than `new URL(...)`: this package compiles against lib +// ES2022 only, deliberately, so it stays free of both DOM and Node globals and +// `URL` is not one of the names it has. The protocol check stays, because +// z.url() alone would accept mailto: and javascript:. +const httpUrl = z.url().refine((value) => /^https?:\/\//i.test(value.trim()), 'A valid http(s) URL is required'); export const collabLinkCreateRequestSchema = z.object({ title: z.string().trim().min(1), diff --git a/shared/src/realtime/events.schema.spec.ts b/shared/src/realtime/events.schema.spec.ts index 27884ece2..826c5373b 100644 --- a/shared/src/realtime/events.schema.spec.ts +++ b/shared/src/realtime/events.schema.spec.ts @@ -73,6 +73,9 @@ const FIXTURES: Record> = { 'collab:note:created': { note: { id: 3 } }, 'collab:note:updated': { note: { id: 3 } }, 'collab:note:deleted': { noteId: 3 }, + 'collab:link:created': { link: { id: 4, title: 'Ferry', url: 'https://example.com' } }, + 'collab:link:updated': { link: { id: 4, title: 'Ferry', url: 'https://example.com' } }, + 'collab:link:deleted': { linkId: 4 }, 'collab:poll:created': { poll: { id: 2 } }, 'collab:poll:voted': { poll: { id: 2 } }, 'collab:poll:closed': { poll: { id: 2 } }, @@ -135,11 +138,11 @@ const DRIFT_VARIANTS: Partial[]> }; describe('@trek/shared realtime event registry', () => { - it('WSEVT-REG-001: pins the authoritative inventory counts (66 trip + 32 user = 98)', () => { - // 66th trip event: packing:bag-totals (#2191). - expect(TREK_WS_TRIP_EVENT_NAMES).toHaveLength(66); + it('WSEVT-REG-001: pins the authoritative inventory counts (69 trip + 32 user = 101)', () => { + // 67th to 69th trip event: the three collab:link:* a shared link emits. + expect(TREK_WS_TRIP_EVENT_NAMES).toHaveLength(69); expect(TREK_WS_USER_EVENT_NAMES).toHaveLength(32); - expect(TREK_WS_EVENT_NAMES).toHaveLength(98); + expect(TREK_WS_EVENT_NAMES).toHaveLength(101); }); it('WSEVT-REG-002: every name is domain:action shaped and outside the reserved plugin: namespace', () => {