/** * A notification link is a relative app path: it starts with "/", not with * "//" or "/\", carries no scheme and is at most 300 characters long. Anything * else is a programming error, never user input (the code that records the * notification builds the link). */ const APP_LINK_PATTERN = /^\/(?![/\\])[A-Za-z0-9\-._~/?=&%]{0,299}$/; export function assertAppLink(link: string): void { if (!APP_LINK_PATTERN.test(link)) { throw new Error(`Notification link is not a relative app path: ${link}`); } } /** One key per recipient, so a fan-out repeated for the same event writes nothing new. */ export function notificationDedupeKey(prefix: string, userId: string): string { return `${prefix}:${userId}`; }