mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Adds the bucket-enumeration primitive the My Videos tab needs to find orphan and
missing files:
- s3 layer: ListObjectsV2Command wired into the SDK wrapper + a paginated
listObjects({ s3, bucket, prefix, maxKeys }) helper (follows continuation
tokens, bounded by maxKeys).
- r2Service.listVideoStorageObjects({ npub|pubkey }) — unlock-gated, lists under
the user's own u/<npub>/ prefix; returns { ok, reason, keys }. Body lives in
r2ServiceHelpers (with a shared buildProviderClient) so r2Service stays under
its file-size budget; ensureS3SdkLoaded is injectable via ctx for testing.
Tests + mutation-verified: never lists while locked (storage-locked, no call),
no-connection / missing-bucket gating, and unlocked listing is scoped to the
user's own u/<npub>/ prefix (empty-prefix mutation fails). Removing the lock gate
or widening the prefix each fail a scenario.
Build + full lint green (r2Service back under limit).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
116 lines
2.7 KiB
JavaScript
116 lines
2.7 KiB
JavaScript
// js/storage/s3-client.js
|
|
|
|
const AWS_SDK_SPECIFIER = "https://esm.sh/@aws-sdk/[email protected]?bundle";
|
|
|
|
const disableNetworkImports = Boolean(
|
|
globalThis?.__BITVID_DISABLE_NETWORK_IMPORTS__
|
|
);
|
|
|
|
let awsSdk = null;
|
|
let awsSdkLoadError = null;
|
|
let awsSdkLoadPromise = null;
|
|
|
|
function assignAwsSdk(module) {
|
|
if (!module) {
|
|
return;
|
|
}
|
|
|
|
awsSdk = {
|
|
S3Client: module.S3Client,
|
|
CreateBucketCommand: module.CreateBucketCommand,
|
|
CreateMultipartUploadCommand: module.CreateMultipartUploadCommand,
|
|
UploadPartCommand: module.UploadPartCommand,
|
|
CompleteMultipartUploadCommand: module.CompleteMultipartUploadCommand,
|
|
AbortMultipartUploadCommand: module.AbortMultipartUploadCommand,
|
|
ListBucketsCommand: module.ListBucketsCommand,
|
|
HeadBucketCommand: module.HeadBucketCommand,
|
|
PutBucketCorsCommand: module.PutBucketCorsCommand,
|
|
DeleteObjectCommand: module.DeleteObjectCommand,
|
|
PutObjectCommand: module.PutObjectCommand,
|
|
GetObjectCommand: module.GetObjectCommand,
|
|
ListObjectsV2Command: module.ListObjectsV2Command,
|
|
};
|
|
}
|
|
|
|
async function loadAwsSdkModule() {
|
|
if (awsSdk || awsSdkLoadError) {
|
|
return awsSdk;
|
|
}
|
|
|
|
if (!awsSdkLoadPromise) {
|
|
if (disableNetworkImports) {
|
|
awsSdkLoadError = new Error("S3 SDK network import has been disabled.");
|
|
return null;
|
|
}
|
|
|
|
awsSdkLoadPromise = import(AWS_SDK_SPECIFIER)
|
|
.then((module) => {
|
|
assignAwsSdk(module);
|
|
return awsSdk;
|
|
})
|
|
.catch((error) => {
|
|
awsSdkLoadError = error || new Error("Failed to load S3 SDK module");
|
|
throw awsSdkLoadError;
|
|
});
|
|
}
|
|
|
|
try {
|
|
return await awsSdkLoadPromise;
|
|
} catch (error) {
|
|
awsSdkLoadError = error;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export function requireAwsSdk() {
|
|
if (awsSdk) {
|
|
return awsSdk;
|
|
}
|
|
|
|
if (awsSdkLoadError) {
|
|
throw awsSdkLoadError;
|
|
}
|
|
|
|
throw new Error("S3 SDK has not finished loading. Call ensureS3SdkLoaded() first.");
|
|
}
|
|
|
|
export function ensureS3SdkLoaded() {
|
|
return loadAwsSdkModule();
|
|
}
|
|
|
|
export function getS3SdkLoadError() {
|
|
return awsSdkLoadError;
|
|
}
|
|
|
|
export function isS3SdkAvailable() {
|
|
return Boolean(awsSdk);
|
|
}
|
|
|
|
export function makeS3Client({
|
|
endpoint,
|
|
region,
|
|
accessKeyId,
|
|
secretAccessKey,
|
|
forcePathStyle,
|
|
}) {
|
|
if (!accessKeyId || !secretAccessKey) {
|
|
throw new Error("Missing S3 credentials");
|
|
}
|
|
|
|
const { S3Client } = requireAwsSdk();
|
|
|
|
return new S3Client({
|
|
region: region || "auto",
|
|
endpoint,
|
|
credentials: { accessKeyId, secretAccessKey },
|
|
forcePathStyle: Boolean(forcePathStyle),
|
|
});
|
|
}
|
|
|
|
if (!disableNetworkImports) {
|
|
// Begin loading immediately in environments that support network imports.
|
|
loadAwsSdkModule().catch(() => {
|
|
// Errors are captured for later inspection by getS3SdkLoadError().
|
|
});
|
|
}
|