mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-04 11:52:25 +00:00
feat(s3): bring generic S3 to parity with R2 (cleanup + CORS guidance)
Two gaps where generic S3 trailed Cloudflare R2: 1. Object cleanup on delete/edit didn't work for generic S3. deleteVideoStorage built its client via makeR2Client, which hard-forces path-style and ignores the connection's forcePathStyle -- so vhost-style S3 deletes were misaddressed and cleanup silently failed (S3 users' deleted/edited files orphaned). It is now provider-aware (mirrors uploadFile): R2 -> makeR2Client (path-style), generic S3 -> makeS3Client honoring forcePathStyle. makeS3Client is now injectable for testing. 2. CORS guidance on upload failure was R2-only. Extracted isLikelyCorsError + a generic-S3 guidance builder into js/utils/uploadErrorHints.js (re-exported from r2Service for back-compat); s3UploadService now appends actionable CORS guidance on an opaque "Failed to fetch" too. Generic S3 is now at functional parity with R2 for upload -> publish -> cleanup. Tests: r2-delete-video-storage (adds generic-S3 forcePathStyle routing), s3-upload-cors-guidance (new). Full suite green. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
a7803c810b
commit
fdebf05e1a
6 files changed
+207
-35
No files matched your search
+29
-25
@@ -45,6 +45,7 @@ import {
|
||||
collectVideoStorageKeys,
|
||||
} from "../utils/storagePointer.js";
|
||||
import { safeEncodeNpub } from "../utils/nostrHelpers.js";
|
||||
import { isLikelyCorsError } from "../utils/uploadErrorHints.js";
|
||||
import {
|
||||
getVideoNoteErrorMessage,
|
||||
normalizeVideoNotePayload,
|
||||
@@ -103,24 +104,9 @@ function buildCorsGuidance({ accountId } = {}) {
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
/**
|
||||
* A browser CORS preflight/rejection surfaces as an opaque network error
|
||||
* ("Failed to fetch" / "NetworkError" / Safari's "Load failed") with no HTTP
|
||||
* status — indistinguishable from a real network drop. We treat these as
|
||||
* likely-CORS so the upload path can attach actionable guidance instead of a
|
||||
* bare "Failed to fetch".
|
||||
*/
|
||||
export function isLikelyCorsError(err) {
|
||||
const message = (
|
||||
(err && (err.message || err.toString && err.toString())) ||
|
||||
""
|
||||
).toLowerCase();
|
||||
return (
|
||||
message.includes("failed to fetch") ||
|
||||
message.includes("networkerror") ||
|
||||
message.includes("load failed")
|
||||
);
|
||||
}
|
||||
// Shared CORS-error classifier (also used by s3UploadService). Re-exported here
|
||||
// for backward compatibility with existing imports/tests.
|
||||
export { isLikelyCorsError };
|
||||
|
||||
function createDefaultSettings() {
|
||||
return {
|
||||
@@ -163,6 +149,7 @@ function safeDecodeNpub(npub) {
|
||||
export class R2Service {
|
||||
constructor({
|
||||
makeR2Client: makeR2ClientOverride,
|
||||
makeS3Client: makeS3ClientOverride,
|
||||
multipartUpload: multipartUploadOverride,
|
||||
ensureBucketExists: ensureBucketExistsOverride,
|
||||
ensureBucketCors: ensureBucketCorsOverride,
|
||||
@@ -171,6 +158,7 @@ export class R2Service {
|
||||
this.listeners = new Map();
|
||||
this.cloudflareSettings = null;
|
||||
this.makeR2Client = makeR2ClientOverride || makeR2Client;
|
||||
this.makeS3Client = makeS3ClientOverride || makeS3Client;
|
||||
this.multipartUpload = multipartUploadOverride || multipartUpload;
|
||||
this.ensureBucketExists = ensureBucketExistsOverride || ensureBucketExists;
|
||||
this.ensureBucketCors = ensureBucketCorsOverride || ensureBucketCors;
|
||||
@@ -260,13 +248,29 @@ export class R2Service {
|
||||
let s3;
|
||||
try {
|
||||
await ensureS3SdkLoaded();
|
||||
s3 = this.makeR2Client({
|
||||
accountId: settings.accountId,
|
||||
endpoint: settings.endpoint,
|
||||
accessKeyId,
|
||||
secretAccessKey,
|
||||
region: settings.region,
|
||||
});
|
||||
// Provider-aware client: Cloudflare R2 is always path-style; a generic S3
|
||||
// bucket may be virtual-hosted-style, so honor the connection's
|
||||
// forcePathStyle (mirrors uploadFile). Using makeR2Client unconditionally
|
||||
// here forced path-style and broke cleanup for vhost-style S3 buckets.
|
||||
const useR2 =
|
||||
settings.provider === "cloudflare_r2" || Boolean(settings.accountId);
|
||||
if (useR2) {
|
||||
s3 = this.makeR2Client({
|
||||
accountId: settings.accountId,
|
||||
endpoint: settings.endpoint,
|
||||
accessKeyId,
|
||||
secretAccessKey,
|
||||
region: settings.region,
|
||||
});
|
||||
} else {
|
||||
s3 = this.makeS3Client({
|
||||
endpoint: settings.endpoint,
|
||||
region: settings.region,
|
||||
accessKeyId,
|
||||
secretAccessKey,
|
||||
forcePathStyle: Boolean(settings.forcePathStyle),
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
devLogger.warn(
|
||||
"[R2Service] deleteVideoStorage: failed to build storage client:",
|
||||
|
||||
Reference in new issue
Block a user