Add waitlist schema for marketing (FRE-635)

- Created waitlist_signups and waitlist_events tables
- Supports email, name, source tracking, and status management
- Enables VIP supporter list for Product Hunt launch
- Migration 0002_chemical_shocker.sql generated
- Fixed brand color in product-hunt-assets-brief.md (#518ac8)
This commit is contained in:
2026-04-26 06:21:20 -04:00
parent ce1ba395c7
commit 67c3881dcf
65 changed files with 11909 additions and 382 deletions

View File

@@ -54,14 +54,34 @@ export function createWebSocketServer(
const url = new URL(req.url || '', `http://${req.headers.host}`);
const docName = url.pathname.split('/').pop() || 'default';
// Validate origin to prevent WebSocket CSRF
const origin = req.headers.origin;
if (authMiddleware && origin) {
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'];
if (!allowedOrigins.includes(origin)) {
console.warn(`Origin validation failed: ${origin}`);
ws.close(4002);
return;
}
}
// Authenticate connection if auth middleware provided
const token = url.searchParams.get('token');
let userId: string | undefined;
let projectId: string | undefined;
if (authMiddleware && token) {
if (authMiddleware) {
if (!token) {
console.warn('Authentication required but no token provided');
ws.send(JSON.stringify({ type: 'error', message: 'Authentication required' }));
ws.close(4001);
return;
}
try {
const auth = await authMiddleware(token);
userId = auth.userId;
projectId = auth.projectId;
console.log(`WebSocket connection authenticated: ${userId} for ${docName}`);
} catch (error) {
console.error('Authentication failed:', error);
@@ -87,8 +107,8 @@ export function createWebSocketServer(
ws.send(encodeSyncStep1(initialState));
// Handle incoming messages
ws.on('message', (data) => {
handleMessage(ws, docName, data);
ws.on('message', (data: Buffer | ArrayBuffer) => {
handleMessage(ws, docName, data, userId, projectId);
});
// Handle disconnection
@@ -113,17 +133,23 @@ function encodeSyncStep1(state: Uint8Array): Uint8Array {
/**
* Handle incoming WebSocket message
*/
function handleMessage(ws: WebSocketWithDoc, docName: string, data: Buffer | ArrayBuffer) {
function handleMessage(
ws: WebSocketWithDoc,
docName: string,
data: Buffer | ArrayBuffer,
userId?: string,
projectId?: string
) {
try {
const message = JSON.parse(data.toString()) as Message;
switch (message.type) {
case 'sync':
handleSync(ws, docName, message);
handleSync(ws, docName, message, userId, projectId);
break;
case 'update':
handleUpdate(ws, docName, message);
handleUpdate(ws, docName, message, userId);
break;
}
} catch (error) {