mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-29 08:52:25 +00:00
Add generic S3 client loader and update tests
This commit is contained in:
1 parent
f634b5a929
commit
9412afb1af
3 files changed
+197
-95
No files matched your search
@@ -34,10 +34,74 @@ export const PROVIDERS = Object.freeze({
|
||||
GENERIC: "generic_s3",
|
||||
});
|
||||
|
||||
function resolveConnectionMeta(config) {
|
||||
return config?.meta || {};
|
||||
}
|
||||
|
||||
function resolveBucket(config) {
|
||||
return config.bucket || resolveConnectionMeta(config).bucket;
|
||||
}
|
||||
|
||||
function resolveRegion(config) {
|
||||
return config.region || resolveConnectionMeta(config).region;
|
||||
}
|
||||
|
||||
function resolveEndpoint(config) {
|
||||
return config.endpoint || resolveConnectionMeta(config).endpoint;
|
||||
}
|
||||
|
||||
function resolveR2Endpoint(config) {
|
||||
const accountId =
|
||||
config.accountId || resolveConnectionMeta(config).accountId || "";
|
||||
if (!accountId) {
|
||||
return resolveEndpoint(config);
|
||||
}
|
||||
return `https://${accountId}.r2.cloudflarestorage.com`;
|
||||
}
|
||||
|
||||
function resolveForcePathStyle(config, fallback = false) {
|
||||
const meta = resolveConnectionMeta(config);
|
||||
if (typeof config.forcePathStyle === "boolean") {
|
||||
return config.forcePathStyle;
|
||||
}
|
||||
if (typeof meta.forcePathStyle === "boolean") {
|
||||
return meta.forcePathStyle;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function buildS3TestConfig(config, overrides = {}) {
|
||||
return {
|
||||
accessKeyId: config.accessKeyId,
|
||||
secretAccessKey: config.secretAccessKey,
|
||||
bucket: resolveBucket(config),
|
||||
region: resolveRegion(config),
|
||||
endpoint: resolveEndpoint(config),
|
||||
forcePathStyle: resolveForcePathStyle(config),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
const PROVIDER_TESTS = {
|
||||
[PROVIDERS.R2]: testS3Connection,
|
||||
[PROVIDERS.S3]: testS3Connection,
|
||||
[PROVIDERS.GENERIC]: testS3Connection,
|
||||
[PROVIDERS.R2]: (config) =>
|
||||
testS3Connection(
|
||||
buildS3TestConfig(config, {
|
||||
endpoint: resolveR2Endpoint(config),
|
||||
forcePathStyle: true,
|
||||
})
|
||||
),
|
||||
[PROVIDERS.S3]: (config) =>
|
||||
testS3Connection(
|
||||
buildS3TestConfig(config, {
|
||||
forcePathStyle: resolveForcePathStyle(config, false),
|
||||
})
|
||||
),
|
||||
[PROVIDERS.GENERIC]: (config) =>
|
||||
testS3Connection(
|
||||
buildS3TestConfig(config, {
|
||||
forcePathStyle: resolveForcePathStyle(config, true),
|
||||
})
|
||||
),
|
||||
};
|
||||
|
||||
function hexToBytes(hex) {
|
||||
|
||||
+18
-92
@@ -1,81 +1,28 @@
|
||||
// js/storage/r2-s3.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,
|
||||
};
|
||||
}
|
||||
|
||||
async function loadAwsSdkModule() {
|
||||
if (awsSdk || awsSdkLoadError) {
|
||||
return awsSdk;
|
||||
}
|
||||
|
||||
if (!awsSdkLoadPromise) {
|
||||
if (disableNetworkImports) {
|
||||
awsSdkLoadError = new Error(
|
||||
"Cloudflare R2 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 R2 SDK module");
|
||||
throw awsSdkLoadError;
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
return await awsSdkLoadPromise;
|
||||
} catch (error) {
|
||||
awsSdkLoadError = error;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
import {
|
||||
ensureS3SdkLoaded,
|
||||
getS3SdkLoadError,
|
||||
isS3SdkAvailable,
|
||||
makeS3Client,
|
||||
requireAwsSdk,
|
||||
} from "./s3-client.js";
|
||||
|
||||
export async function testS3Connection(config) {
|
||||
if (!config) {
|
||||
throw new Error("Configuration required for testing S3 connection.");
|
||||
}
|
||||
|
||||
await ensureR2SdkLoaded();
|
||||
await ensureS3SdkLoaded();
|
||||
|
||||
const { ListBucketsCommand, HeadBucketCommand } = requireAwsSdk();
|
||||
|
||||
const s3 = makeR2Client({
|
||||
accountId: config.accountId,
|
||||
accessKeyId: config.accessKeyId,
|
||||
secretAccessKey: config.secretAccessKey,
|
||||
const s3 = makeS3Client({
|
||||
endpoint: config.endpoint,
|
||||
region: config.region,
|
||||
accessKeyId: config.accessKeyId,
|
||||
secretAccessKey: config.secretAccessKey,
|
||||
forcePathStyle: config.forcePathStyle,
|
||||
});
|
||||
|
||||
// If bucket is specified, we check that specific bucket (permissions + existence).
|
||||
@@ -108,37 +55,16 @@ export async function testS3Connection(config) {
|
||||
}
|
||||
}
|
||||
|
||||
function requireAwsSdk() {
|
||||
if (awsSdk) {
|
||||
return awsSdk;
|
||||
}
|
||||
|
||||
if (awsSdkLoadError) {
|
||||
throw awsSdkLoadError;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"Cloudflare R2 SDK has not finished loading. Call ensureR2SdkLoaded() first."
|
||||
);
|
||||
}
|
||||
|
||||
export function ensureR2SdkLoaded() {
|
||||
return loadAwsSdkModule();
|
||||
return ensureS3SdkLoaded();
|
||||
}
|
||||
|
||||
export function getR2SdkLoadError() {
|
||||
return awsSdkLoadError;
|
||||
return getS3SdkLoadError();
|
||||
}
|
||||
|
||||
export function isR2SdkAvailable() {
|
||||
return Boolean(awsSdk);
|
||||
}
|
||||
|
||||
if (!disableNetworkImports) {
|
||||
// Begin loading immediately in environments that support network imports.
|
||||
loadAwsSdkModule().catch(() => {
|
||||
// Errors are captured for later inspection by getR2SdkLoadError().
|
||||
});
|
||||
return isS3SdkAvailable();
|
||||
}
|
||||
|
||||
function computeCacheControl(key) {
|
||||
@@ -173,14 +99,14 @@ export function makeR2Client({
|
||||
throw new Error("Missing S3/R2 credentials");
|
||||
}
|
||||
|
||||
const { S3Client } = requireAwsSdk();
|
||||
const finalEndpoint =
|
||||
endpoint || `https://${accountId}.r2.cloudflarestorage.com`;
|
||||
|
||||
return new S3Client({
|
||||
return makeS3Client({
|
||||
region: region || "auto",
|
||||
endpoint: finalEndpoint,
|
||||
credentials: { accessKeyId, secretAccessKey },
|
||||
accessKeyId,
|
||||
secretAccessKey,
|
||||
forcePathStyle: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
// 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,
|
||||
};
|
||||
}
|
||||
|
||||
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().
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user