@cassets/cloud
v0.1.1
Published
Signed-upload URL adapters for @cassets/http-client across S3-compatible storage, Azure Blob Storage, and Google Cloud Storage.
Maintainers
Readme
@cassets/cloud
Cloud signed-upload URL adapters for @cassets/http-client.
The package does not contain AWS, Azure, or Google cloud credentials and does not sign requests in the browser. Instead, it standardizes the browser-to-backend contract used to obtain short-lived signed URLs for chunked uploads.
Supported adapter families in 0.1.x:
- Amazon S3 and S3-compatible providers such as Wasabi and MinIO
- Azure Blob Storage
- Google Cloud Storage (GCS)
Installation
npm install @cassets/cloud @cassets/http-clientYou can also use the adapters without @cassets/http-client; they simply return a function matching the documented GetUploadUrlsFn contract.
Architecture
Browser
|
| file metadata only
v
Your signing endpoint
|
| server-side cloud credentials / identity
v
Cloud provider
|
| short-lived signed part URLs
v
Browser -> direct chunk uploads -> cloud storageCloud credentials stay on the server. The browser receives only scoped upload URLs.
Quick start: S3
import { UploadManager } from '@cassets/http-client/upload';
import { createS3UploadUrlFetcher } from '@cassets/cloud/s3';
const getUploadUrls = createS3UploadUrlFetcher({
signEndpoint: '/api/uploads/s3/sign',
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
await UploadManager.getInstance().upload({
file,
getUploadUrls,
chunkSize: 10 * 1024 * 1024,
parallelParts: 3,
});The root import is also supported:
import {
createS3UploadUrlFetcher,
createAzureUploadUrlFetcher,
createGCSUploadUrlFetcher,
} from '@cassets/cloud';Adapter contract
interface UploadUrl {
partNumber: number;
url: string;
uploadId?: string;
}
type GetUploadUrlsFn = (
file: File,
chunkSize: number,
totalParts: number,
) => Promise<UploadUrl[]>;
interface AdapterConfig {
signEndpoint: string;
headers?: Record<string, string>;
fetchFn?: typeof fetch;
}Every adapter sends the same request body to your signing endpoint:
{
"fileName": "video.mp4",
"fileSize": 73400320,
"chunkSize": 10485760,
"totalParts": 7,
"contentType": "video/mp4"
}Your backend returns:
{
"urls": [
{
"partNumber": 1,
"url": "https://provider.example/signed-part-1",
"uploadId": "optional-provider-session-id"
},
{
"partNumber": 2,
"url": "https://provider.example/signed-part-2",
"uploadId": "optional-provider-session-id"
}
]
}partNumber is 1-based and should cover every requested part.
S3 / S3-compatible storage
import { createS3UploadUrlFetcher } from '@cassets/cloud/s3';
const getUploadUrls = createS3UploadUrlFetcher({
signEndpoint: 'https://api.example.com/uploads/s3/sign',
});The adapter is provider-neutral on the client. Your backend signer determines whether the target is AWS S3, Wasabi, MinIO, Backblaze B2 S3-compatible API, or another compatible service.
Example signing-endpoint pseudocode
POST /uploads/s3/sign
authenticate user
authorize tenant/bucket/object
validate file metadata
create multipart upload
create one signed PUT URL per part
return { urls }The backend should persist the multipart uploadId and complete/abort the multipart session after the client reports uploaded part metadata. The browser package does not hold AWS secret keys.
Azure Blob Storage
import { createAzureUploadUrlFetcher } from '@cassets/cloud/azure';
const getUploadUrls = createAzureUploadUrlFetcher({
signEndpoint: '/api/uploads/azure/sign',
headers: {
'X-Tenant-ID': tenantId,
},
});Your server may generate per-block SAS URLs or another upload URL strategy. Provider-specific block IDs and commit behavior remain a backend/application responsibility.
Google Cloud Storage
import { createGCSUploadUrlFetcher } from '@cassets/cloud/gcs';
const getUploadUrls = createGCSUploadUrlFetcher({
signEndpoint: '/api/uploads/gcs/sign',
});Your server chooses the actual GCS signed-upload strategy. The adapter only standardizes the metadata request and returned URL list.
Custom authentication headers
const getUploadUrls = createS3UploadUrlFetcher({
signEndpoint: '/api/uploads/sign',
headers: {
Authorization: `Bearer ${token}`,
'X-Workspace-ID': workspaceId,
},
});Do not put cloud provider secret credentials in these headers. They are browser-visible request headers to your API.
Custom fetch
Useful for tests, tracing, or a wrapper around the browser Fetch API:
const getUploadUrls = createGCSUploadUrlFetcher({
signEndpoint: '/api/uploads/gcs/sign',
fetchFn: async (input, init) => {
console.debug('sign request', input);
return fetch(input, init);
},
});Direct use without @cassets/http-client
const getUrls = createS3UploadUrlFetcher({
signEndpoint: '/api/uploads/s3/sign',
});
const urls = await getUrls(file, 10 * 1024 * 1024, 4);
console.log(urls);You can then use the URLs with your own upload engine.
Error handling
A non-2xx signing response throws an error:
try {
const urls = await getUploadUrls(file, chunkSize, totalParts);
} catch (error) {
console.error('Could not obtain upload URLs', error);
}Treat signing failures separately from failures that happen while PUTing chunks to cloud storage.
Security guidance
The safest design is capability-based upload:
- Authenticate the user to your backend.
- Authorize the exact tenant/workspace/object operation.
- Validate file name, expected size, MIME type, number of parts, and allowed storage route.
- Generate short-lived, operation-scoped signed URLs.
- Return only the minimum capability required to upload the requested parts.
- Verify/complete the upload server-side.
- Expire or abort abandoned multipart sessions.
Additional recommendations:
- Never send AWS secret keys, Azure account keys, GCP service-account keys, or long-lived provider tokens to the browser.
- Keep signing endpoints rate-limited and audited.
- Do not trust
fileName,contentType, orfileSizemerely because the browser supplied them. - Bind object keys to server-authorized tenant/user context; do not allow arbitrary bucket/key selection from the browser.
- Configure cloud CORS narrowly.
What this package does not do
@cassets/cloud is deliberately not a full cloud SDK. It does not:
- authenticate directly to cloud providers with secret credentials;
- create buckets/containers;
- finalize provider-specific multipart/block sessions;
- persist upload state;
- retry chunks;
- download or delete objects;
- proxy file bytes through your API.
Those boundaries keep the client adapter lightweight and reduce credential exposure.
Package exports
import { createS3UploadUrlFetcher } from '@cassets/cloud/s3';
import { createAzureUploadUrlFetcher } from '@cassets/cloud/azure';
import { createGCSUploadUrlFetcher } from '@cassets/cloud/gcs';All adapters are also exported from @cassets/cloud.
Both ESM and CommonJS builds plus TypeScript declarations are published.
Project links
- ClusterAssets GitHub: https://github.com/clusterassets
- ClusterAssets LinkedIn: https://www.linkedin.com/company/clusterassets
- Creator GitHub: https://github.com/diskhacker
- Creator LinkedIn: https://www.linkedin.com/in/kp-vivek-rao-bhosale/
Contributing and security
See CONTRIBUTING.md and SECURITY.md.
License
MIT © Vivek Rao Bhosale / ClusterAssets.
