🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@howells/stow-client

Package Overview
Dependencies
Maintainers
1
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@howells/stow-client - npm Package Compare versions

Comparing version
3.0.0
to
3.1.0
+4
-0
dist/index.d.mts

@@ -146,2 +146,6 @@ /** Error thrown when an SDK request fails. */

uploadFile(file: File, options?: UploadOptions): Promise<UploadResult>;
/** Request a presigned upload from your server, returning either a fresh presign or a dedupe hit. */
private fetchPresign;
/** Confirm a completed R2 upload through your server and return the stored file metadata. */
private confirmUpload;
/**

@@ -148,0 +152,0 @@ * Upload multiple files with bounded concurrency.

@@ -146,2 +146,6 @@ /** Error thrown when an SDK request fails. */

uploadFile(file: File, options?: UploadOptions): Promise<UploadResult>;
/** Request a presigned upload from your server, returning either a fresh presign or a dedupe hit. */
private fetchPresign;
/** Confirm a completed R2 upload through your server and return the stored file metadata. */
private confirmUpload;
/**

@@ -148,0 +152,0 @@ * Upload multiple files with bounded concurrency.

+78
-40

@@ -41,5 +41,23 @@ "use strict";

// src/index.ts
var uploadToR2 = (uploadUrl, file, onProgress) => (
// eslint-disable-next-line eslint-plugin-promise/avoid-new
new Promise((resolve, reject) => {
var DEFAULT_CONTENT_TYPE = "application/octet-stream";
var isRecord = (value) => typeof value === "object" && value !== null;
var readErrorBody = async (response) => {
try {
const body = await response.json();
if (!isRecord(body)) {
return {};
}
return {
code: typeof body.code === "string" ? body.code : void 0,
error: typeof body.error === "string" && body.error !== "" ? body.error : void 0
};
} catch {
return {};
}
};
var isPresignResult = (value) => isRecord(value);
var isPresignDedupeResponse = (value) => isRecord(value) && value.dedupe === true;
var isConfirmResponse = (value) => isRecord(value);
var uploadToR2 = async (uploadUrl, file, onProgress) => {
await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();

@@ -65,6 +83,6 @@ xhr.upload.addEventListener("progress", (event) => {

xhr.open("PUT", uploadUrl);
xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
xhr.setRequestHeader("Content-Type", file.type || DEFAULT_CONTENT_TYPE);
xhr.send(file);
})
);
});
};
var StowClient = class {

@@ -91,3 +109,3 @@ endpoint;

resolveConfirmUrl(confirmUrl) {
if (!confirmUrl) {
if (confirmUrl === void 0 || confirmUrl === "") {
return this.endpointUrl("/confirm");

@@ -114,3 +132,3 @@ }

async uploadFile(file, options) {
const { route, metadata, onProgress } = options || {};
const { route, metadata, onProgress } = options ?? {};
onProgress?.({

@@ -122,19 +140,4 @@ loaded: 0,

});
const presignResponse = await fetch(this.endpointUrl("/presign"), {
body: JSON.stringify({
contentType: file.type || "application/octet-stream",
filename: file.name,
route,
size: file.size,
...metadata ? { metadata } : {}
}),
headers: { "Content-Type": "application/json" },
method: "POST"
});
if (!presignResponse.ok) {
const error = await presignResponse.json().catch(() => ({ error: "Presign failed" }));
throw new StowError(error.error || "Presign failed", presignResponse.status, error.code);
}
const presign = await presignResponse.json();
if ("dedupe" in presign && presign.dedupe) {
const presign = await this.fetchPresign(file, route, metadata);
if (isPresignDedupeResponse(presign)) {
onProgress?.({

@@ -174,5 +177,32 @@ loaded: file.size,

});
return await this.confirmUpload(presign, file, metadata);
}
/** Request a presigned upload from your server, returning either a fresh presign or a dedupe hit. */
async fetchPresign(file, route, metadata) {
const presignResponse = await fetch(this.endpointUrl("/presign"), {
body: JSON.stringify({
contentType: file.type || DEFAULT_CONTENT_TYPE,
filename: file.name,
route,
size: file.size,
...metadata ? { metadata } : {}
}),
headers: { "Content-Type": "application/json" },
method: "POST"
});
if (!presignResponse.ok) {
const body = await readErrorBody(presignResponse);
throw new StowError(body.error ?? "Presign failed", presignResponse.status, body.code);
}
const presignBody = await presignResponse.json();
if (!isPresignResult(presignBody)) {
throw new StowError("Invalid presign response from server", presignResponse.status);
}
return presignBody;
}
/** Confirm a completed R2 upload through your server and return the stored file metadata. */
async confirmUpload(presign, file, metadata) {
const confirmResponse = await fetch(this.resolveConfirmUrl(presign.confirmUrl), {
body: JSON.stringify({
contentType: file.type || "application/octet-stream",
contentType: file.type || DEFAULT_CONTENT_TYPE,
fileKey: presign.fileKey,

@@ -186,11 +216,14 @@ size: file.size,

if (!confirmResponse.ok) {
const error = await confirmResponse.json().catch(() => ({ error: "Confirm failed" }));
throw new StowError(error.error || "Confirm failed", confirmResponse.status, error.code);
const body = await readErrorBody(confirmResponse);
throw new StowError(body.error ?? "Confirm failed", confirmResponse.status, body.code);
}
const result = await confirmResponse.json();
const confirmBody = await confirmResponse.json();
if (!isConfirmResponse(confirmBody)) {
throw new StowError("Invalid confirm response from server", confirmResponse.status);
}
return {
contentType: result.contentType,
key: result.key,
size: result.size,
url: result.url
contentType: confirmBody.contentType,
key: confirmBody.key,
size: confirmBody.size,
url: confirmBody.url
};

@@ -246,5 +279,5 @@ }

let nextIndex = 0;
let firstError = null;
const errors = [];
const worker = async () => {
while (nextIndex < fileArray.length && !firstError) {
while (nextIndex < fileArray.length && errors.length === 0) {
const i = nextIndex;

@@ -263,4 +296,4 @@ nextIndex += 1;

options?.onFileError?.(err, file, i);
if (!firstError) {
firstError = { error: err, file, index: i };
if (errors.length === 0) {
errors.push({ error: err, file, index: i });
}

@@ -270,8 +303,13 @@ }

};
const workers = Array.from({ length: Math.min(concurrency, fileArray.length) }, () => worker());
const workerCount = Math.min(concurrency, fileArray.length);
const workers = [];
for (let w = 0; w < workerCount; w += 1) {
workers.push(worker());
}
await Promise.all(workers);
if (firstError !== null) {
const [firstError] = errors;
if (firstError !== void 0) {
throw firstError.error;
}
return results;
return results.filter((result) => result !== void 0);
}

@@ -278,0 +316,0 @@ };

@@ -14,5 +14,23 @@ // src/stow-error.ts

// src/index.ts
var uploadToR2 = (uploadUrl, file, onProgress) => (
// eslint-disable-next-line eslint-plugin-promise/avoid-new
new Promise((resolve, reject) => {
var DEFAULT_CONTENT_TYPE = "application/octet-stream";
var isRecord = (value) => typeof value === "object" && value !== null;
var readErrorBody = async (response) => {
try {
const body = await response.json();
if (!isRecord(body)) {
return {};
}
return {
code: typeof body.code === "string" ? body.code : void 0,
error: typeof body.error === "string" && body.error !== "" ? body.error : void 0
};
} catch {
return {};
}
};
var isPresignResult = (value) => isRecord(value);
var isPresignDedupeResponse = (value) => isRecord(value) && value.dedupe === true;
var isConfirmResponse = (value) => isRecord(value);
var uploadToR2 = async (uploadUrl, file, onProgress) => {
await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();

@@ -38,6 +56,6 @@ xhr.upload.addEventListener("progress", (event) => {

xhr.open("PUT", uploadUrl);
xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
xhr.setRequestHeader("Content-Type", file.type || DEFAULT_CONTENT_TYPE);
xhr.send(file);
})
);
});
};
var StowClient = class {

@@ -64,3 +82,3 @@ endpoint;

resolveConfirmUrl(confirmUrl) {
if (!confirmUrl) {
if (confirmUrl === void 0 || confirmUrl === "") {
return this.endpointUrl("/confirm");

@@ -87,3 +105,3 @@ }

async uploadFile(file, options) {
const { route, metadata, onProgress } = options || {};
const { route, metadata, onProgress } = options ?? {};
onProgress?.({

@@ -95,19 +113,4 @@ loaded: 0,

});
const presignResponse = await fetch(this.endpointUrl("/presign"), {
body: JSON.stringify({
contentType: file.type || "application/octet-stream",
filename: file.name,
route,
size: file.size,
...metadata ? { metadata } : {}
}),
headers: { "Content-Type": "application/json" },
method: "POST"
});
if (!presignResponse.ok) {
const error = await presignResponse.json().catch(() => ({ error: "Presign failed" }));
throw new StowError(error.error || "Presign failed", presignResponse.status, error.code);
}
const presign = await presignResponse.json();
if ("dedupe" in presign && presign.dedupe) {
const presign = await this.fetchPresign(file, route, metadata);
if (isPresignDedupeResponse(presign)) {
onProgress?.({

@@ -147,5 +150,32 @@ loaded: file.size,

});
return await this.confirmUpload(presign, file, metadata);
}
/** Request a presigned upload from your server, returning either a fresh presign or a dedupe hit. */
async fetchPresign(file, route, metadata) {
const presignResponse = await fetch(this.endpointUrl("/presign"), {
body: JSON.stringify({
contentType: file.type || DEFAULT_CONTENT_TYPE,
filename: file.name,
route,
size: file.size,
...metadata ? { metadata } : {}
}),
headers: { "Content-Type": "application/json" },
method: "POST"
});
if (!presignResponse.ok) {
const body = await readErrorBody(presignResponse);
throw new StowError(body.error ?? "Presign failed", presignResponse.status, body.code);
}
const presignBody = await presignResponse.json();
if (!isPresignResult(presignBody)) {
throw new StowError("Invalid presign response from server", presignResponse.status);
}
return presignBody;
}
/** Confirm a completed R2 upload through your server and return the stored file metadata. */
async confirmUpload(presign, file, metadata) {
const confirmResponse = await fetch(this.resolveConfirmUrl(presign.confirmUrl), {
body: JSON.stringify({
contentType: file.type || "application/octet-stream",
contentType: file.type || DEFAULT_CONTENT_TYPE,
fileKey: presign.fileKey,

@@ -159,11 +189,14 @@ size: file.size,

if (!confirmResponse.ok) {
const error = await confirmResponse.json().catch(() => ({ error: "Confirm failed" }));
throw new StowError(error.error || "Confirm failed", confirmResponse.status, error.code);
const body = await readErrorBody(confirmResponse);
throw new StowError(body.error ?? "Confirm failed", confirmResponse.status, body.code);
}
const result = await confirmResponse.json();
const confirmBody = await confirmResponse.json();
if (!isConfirmResponse(confirmBody)) {
throw new StowError("Invalid confirm response from server", confirmResponse.status);
}
return {
contentType: result.contentType,
key: result.key,
size: result.size,
url: result.url
contentType: confirmBody.contentType,
key: confirmBody.key,
size: confirmBody.size,
url: confirmBody.url
};

@@ -219,5 +252,5 @@ }

let nextIndex = 0;
let firstError = null;
const errors = [];
const worker = async () => {
while (nextIndex < fileArray.length && !firstError) {
while (nextIndex < fileArray.length && errors.length === 0) {
const i = nextIndex;

@@ -236,4 +269,4 @@ nextIndex += 1;

options?.onFileError?.(err, file, i);
if (!firstError) {
firstError = { error: err, file, index: i };
if (errors.length === 0) {
errors.push({ error: err, file, index: i });
}

@@ -243,8 +276,13 @@ }

};
const workers = Array.from({ length: Math.min(concurrency, fileArray.length) }, () => worker());
const workerCount = Math.min(concurrency, fileArray.length);
const workers = [];
for (let w = 0; w < workerCount; w += 1) {
workers.push(worker());
}
await Promise.all(workers);
if (firstError !== null) {
const [firstError] = errors;
if (firstError !== void 0) {
throw firstError.error;
}
return results;
return results.filter((result) => result !== void 0);
}

@@ -251,0 +289,0 @@ };

{
"name": "@howells/stow-client",
"version": "3.0.0",
"version": "3.1.0",
"description": "Client-side SDK for Stow visual media infrastructure",

@@ -5,0 +5,0 @@ "keywords": [