File size: 2,666 Bytes
87cb242
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import WebSocket from 'ws';
import { ClientMessage, safeSend } from '../utils/protocol';
import {
  generateUploadUrl,
  validateFileRequest,
  validateFileCount,
  FileUploadRequest,
} from '../services/supabaseService';
import { logger } from '../utils/logger';

/**
 * Handles type: 'get-upload-url'
 *
 * Client sends:
 * {
 *   files: Array<{ fileName: string, mimeType: string, sizeBytes: number }>
 * }
 *
 * Server responds with:
 * {
 *   urls: Array<{ signedUrl, objectPath, publicUrl, fileName }>
 * }
 *
 * The client then PUTs each file directly to Supabase using the signedUrl.
 * HF server never sees any file bytes β€” memory usage stays near 0.
 *
 * After upload, the client sends a type: 'message' with type: 'media' and
 * the publicUrl as the content field.
 */
export async function handleFileRequest(
  ws: WebSocket,
  uid: string,
  msg: ClientMessage
): Promise<void> {
  const files = msg.payload.files as FileUploadRequest[] | undefined;

  // ── Validate count ────────────────────────────────────────────────────────
  if (!Array.isArray(files)) {
    safeSend(ws, 'error', { message: 'files must be an array' }, msg.requestId);
    return;
  }

  const countError = validateFileCount(files.length);
  if (countError) {
    safeSend(ws, 'error', { message: countError }, msg.requestId);
    return;
  }

  // ── Validate each file ────────────────────────────────────────────────────
  for (const f of files) {
    const fileError = validateFileRequest(f);
    if (fileError) {
      safeSend(ws, 'error', { message: fileError }, msg.requestId);
      return;
    }
  }

  // ── Generate signed URLs ──────────────────────────────────────────────────
  try {
    const results = await Promise.all(
      files.map(async (f) => {
        const result = await generateUploadUrl(uid, f);
        return {
          fileName: f.fileName,
          signedUrl: result.signedUrl,
          objectPath: result.objectPath,
          publicUrl: result.publicUrl,
        };
      })
    );

    safeSend(ws, 'upload-url', { urls: results }, msg.requestId);
    logger.info('Signed URLs generated', { uid, count: results.length });
  } catch (err) {
    logger.error('generateUploadUrl failed', { uid, error: String(err) });
    safeSend(ws, 'error', { message: 'Failed to generate upload URL' }, msg.requestId);
  }
}