Skip to content

Instantly share code, notes, and snippets.

@farzd
Last active September 16, 2025 14:25
Show Gist options
  • Select an option

  • Save farzd/017b706337c3b85f642940730a9bf005 to your computer and use it in GitHub Desktop.

Select an option

Save farzd/017b706337c3b85f642940730a9bf005 to your computer and use it in GitHub Desktop.
Uploadsfailing silently
// following https://supabase.com/docs/guides/storage/uploads/s3-uploads
// @supabase/supabase-js": "2.49.5-next.1",
// "expo": "^53.0.4"
// please see comment at bottom for more
import supabase, { supabaseAnonKey, supabaseUrl } from "./supabase";
import { File } from "expo-file-system";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
export const uploadAudioToSupabase = async ({
audioUri,
duration,
onProgress,
}: {
audioUri: string;
duration: any;
onProgress?: (percent: any) => void;
}) => {
try {
const {
data: { session },
error: sessionError,
} = await supabase.auth.getSession();
if (sessionError || !session) {
throw new Error("User not authenticated");
}
// Get file info
const file = new File(audioUri);
if (!file.exists) {
throw new Error("Audio file not found");
}
const fileSize = typeof file.size === "number" ? file.size : 0;
const fileName = audioUri.split("/").pop();
const contentType = "audio/mp4";
// Step 1: Prepare upload
const { data: prepareResult, error: prepareError } =
await supabase.functions.invoke("process-upload-v3", {
body: {
duration: duration,
fileSize: fileSize,
fileName: fileName,
},
});
if (prepareError || !prepareResult) {
throw new Error(prepareError?.message || "Failed to upload audio");
}
const { note_id } = prepareResult;
// Step 2: Use AWS S3 multipart upload against Supabase's S3-compatible endpoint
const s3 = new S3Client({
forcePathStyle: true,
region: "us-east-1",
endpoint: `${supabaseUrl}/storage/v1/s3`,
credentials: {
accessKeyId: "xyz",
secretAccessKey: supabaseAnonKey,
sessionToken: session.access_token,
},
});
const bucket = "recordings";
const key = `${session.user.id}/${fileName}`;
const MULTIPART_THRESHOLD = 8 * 1024 * 1024; // 8MB
let httpStatus: number | undefined = undefined;
if (fileSize > 0 && fileSize < MULTIPART_THRESHOLD) {
// Small file: direct upload with PutObject
onProgress?.(null);
const uint8 = await file.bytes();
const uploadCommand = new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: uint8,
ContentType: contentType,
});
const res = await s3.send(uploadCommand);
httpStatus = res.$metadata.httpStatusCode;
} else {
try {
// Larger file: multipart upload using lib-storage with progress
const stream = file.stream();
onProgress?.(10); // Start at 10% to indicate beginning
const uploader = new Upload({
client: s3,
params: {
Bucket: bucket,
Key: key,
Body: stream,
ContentType: contentType,
ContentLength: fileSize,
},
queueSize: 3,
partSize: 5 * 1024 * 1024, // 5MB parts
leavePartsOnError: false,
});
uploader.on("httpUploadProgress", ({ loaded = 0, total }) => {
const base =
typeof total === "number" && total > 0 ? total : fileSize;
if (base && base > 0) {
const percent = Math.min(
100,
Math.max(0, Math.round((loaded / base) * 100))
);
onProgress?.(percent);
}
});
const res = await uploader.done();
httpStatus = res.$metadata.httpStatusCode;
} catch (error) {
console.error("Multipart upload failed:", error);
throw new Error(`Multipart upload failed: ${error.message}`);
}
}
if (httpStatus !== 200) {
throw new Error("Failed to upload audio");
}
return {
success: true,
note_id: note_id,
};
} catch (error) {
console.error("Failed to upload audio: ", error);
throw error;
}
};
@farzd
Copy link
Author

farzd commented Sep 16, 2025

Just a POST request for this failing file. Smaller files that are uploaded directly, not as multi-part also fail at times. About 10% of uploads silently fail like this.

image
{
  "event_message": "xyz | POST | 200 | 10.109.15.110 | 97fe327ff24981d7-SIN | /s3/notes/9ff2fbc1-8ca6-4a9c-a37d-e2e77ff5fabe/31CFA9A1-409A-47D2-A211-22EE8D0C1038.m4a?uploads= | aws-sdk-js/3.886.0 ua/2.1 os/other lang/js md/rn api/s3#3.886.0 m/N,E,e",
  "id": "28f52fe5-a616-4b2d-b53c-fde34a82759c",
  "metadata": [
    {
      "appVersion": "1.26.7",
      "context": [
        {
          "host": "ip-10-109-16-224.ec2.internal",
          "pid": 1,
          "type": "request"
        }
      ],
      "err": [],
      "error": [],
      "event": null,
      "job": null,
      "jodId": null,
      "level": "info",
      "metadata": null,
      "objectPath": null,
      "objectVersion": null,
      "operation": "storage.s3.upload.create_multipart",
      "owner": "9ff2fbc1-8ca6-4a9c-a37d-e2e77ff5fabe",
      "payload": null,
      "project": "xyz",
      "rawError": [],
      "region": "us-east-1",
      "req": [
        {
          "headers": [
            {
              "host": "storage-api-canary-lb-us-east-1-ext.storage.supabase.com",
              "x_client_trace_id": null,
              "expires": null,
              "x_forwarded_prefix": "/storage/v1",
              "cf_connecting_ip": "119.56.77.59",
              "cf_ray": "97fe327ff24981d7-SIN",
              "location": null,
              "cf_ipcountry": null,
              "tus_resumable": null,
              "sb_gateway_mode": null,
              "x_forwarded_proto": "https",
              "x_forwarded_host": "xyz.supabase.co",
              "x_client_info": null,
              "transfer_encoding": null,
              "x_real_ip": null,
              "content_type": "audio/mp4",
              "accept": "*/*",
              "user_agent": "aws-sdk-js/3.886.0 ua/2.1 os/other lang/js md/rn api/s3#3.886.0 m/N,E,e",
              "cf_cache_status": null,
              "upload_metadata": null,
              "range": null,
              "referer": null,
              "x_forwarded_port": "443",
              "content_range": null,
              "content_length": "0",
              "cache_control": null,
              "sb_gateway_version": null,
              "content_disposition": null,
              "upload_length": null,
              "date": null,
              "x_upsert": null,
              "if_none_match": null,
              "if_modified_since": null,
              "upload_offset": null
            }
          ],
          "hostname": "storage-api-canary-lb-us-east-1-ext.storage.supabase.com",
          "method": "POST",
          "region": "us-east-1",
          "remoteAddress": "10.109.15.110",
          "remotePort": 51130,
          "traceId": "97fe327ff24981d7-SIN",
          "url": "/s3/notes/9ff2fbc1-8ca6-4a9c-a37d-e2e77ff5fabe/31CFA9A1-409A-47D2-A211-22EE8D0C1038.m4a?uploads="
        }
      ],
      "reqId": "97fe327ff24981d7-SIN",
      "res": [
        {
          "headers": [
            {
              "cache_control": null,
              "content_disposition": null,
              "content_length": "421",
              "content_range": null,
              "content_type": "application/xml; charset=utf-8",
              "etag": null,
              "expires": null,
              "last_modified": null,
              "location": null,
              "tus_resumable": null,
              "upload_length": null,
              "upload_metadata": null,
              "upload_offset": null,
              "x_transformations": null
            }
          ],
          "statusCode": 200
        }
      ],
      "resources": [
        "/notes/9ff2fbc1-8ca6-4a9c-a37d-e2e77ff5fabe/31CFA9A1-409A-47D2-A211-22EE8D0C1038.m4a"
      ],
      "responseTime": 70.65963099896908,
      "role": "authenticated",
      "serverTimes": [],
      "span_id": null,
      "tenantId": "xyz",
      "trace_flags": null,
      "trace_id": null
    }
  ],
  "timestamp": 1758002990749000
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment