New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@vercel/blob

Package Overview
Dependencies
Maintainers
9
Versions
108
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vercel/blob - npm Package Compare versions

Comparing version 0.11.0 to 0.12.0

dist/chunk-OZWI34O7.cjs

94

dist/index.d.ts

@@ -1,90 +0,6 @@

import { Readable } from 'node:stream';
import { IncomingMessage } from 'node:http';
import { P as PutCommandOptions, a as PutBlobResult, B as BlobCommandOptions } from './put-6f84b94d.js';
export { b as BlobAccessError, c as BlobError, d as BlobUnknownError } from './put-6f84b94d.js';
import * as stream from 'stream';
declare class BlobError extends Error {
constructor(message: string);
}
declare class BlobAccessError extends Error {
constructor();
}
declare class BlobUnknownError extends Error {
constructor();
}
interface GenerateClientTokenOptions extends BlobCommandOptions {
pathname: string;
onUploadCompleted?: {
callbackUrl: string;
metadata?: string | null;
};
maximumSizeInBytes?: number;
allowedContentTypes?: string[];
validUntil?: number;
addRandomSuffix?: boolean;
cacheControlMaxAge?: number;
}
declare function generateClientTokenFromReadWriteToken({ token, ...args }: GenerateClientTokenOptions): Promise<string>;
declare function verifyCallbackSignature({ token, signature, body, }: {
token?: string;
signature: string;
body: string;
}): Promise<boolean>;
type DecodedClientTokenPayload = Omit<GenerateClientTokenOptions, 'token'> & {
validUntil: number;
};
declare function getPayloadFromClientToken(clientToken: string): DecodedClientTokenPayload;
declare const EventTypes: {
readonly generateClientToken: "blob.generate-client-token";
readonly uploadCompleted: "blob.upload-completed";
};
interface GenerateClientTokenEvent {
type: (typeof EventTypes)['generateClientToken'];
payload: {
pathname: string;
callbackUrl: string;
};
}
interface BlobUploadCompletedEvent {
type: (typeof EventTypes)['uploadCompleted'];
payload: {
blob: HeadBlobResult;
metadata?: string;
};
}
type HandleBlobUploadBody = GenerateClientTokenEvent | BlobUploadCompletedEvent;
type RequestType = IncomingMessage | Request;
interface HandleBlobUploadOptions {
body: HandleBlobUploadBody;
onBeforeGenerateToken: (pathname: string) => Promise<Pick<GenerateClientTokenOptions, 'allowedContentTypes' | 'maximumSizeInBytes' | 'validUntil' | 'addRandomSuffix' | 'cacheControlMaxAge'> & {
metadata?: string;
}>;
onUploadCompleted: (body: BlobUploadCompletedEvent['payload']) => Promise<void>;
token?: string;
request: RequestType;
}
declare function handleBlobUpload({ token, request, body, onBeforeGenerateToken, onUploadCompleted, }: HandleBlobUploadOptions): Promise<{
type: GenerateClientTokenEvent['type'];
clientToken: string;
} | {
type: BlobUploadCompletedEvent['type'];
response: 'ok';
}>;
interface BlobCommandOptions {
token?: string;
}
interface PutCommandOptions extends BlobCommandOptions {
access: 'public';
contentType?: string;
handleBlobUploadUrl?: string;
addRandomSuffix?: boolean;
cacheControlMaxAge?: number;
}
interface PutBlobResult {
url: string;
pathname: string;
contentType: string;
contentDisposition: string;
}
declare function put(pathname: string, body: string | Readable | Blob | ArrayBuffer | FormData | ReadableStream | File, options: PutCommandOptions): Promise<PutBlobResult>;
declare const put: (pathname: string, body: string | stream.Readable | Blob | ArrayBuffer | FormData | ReadableStream<any> | File, options?: PutCommandOptions | undefined) => Promise<PutBlobResult>;
declare function del(url: string[] | string, options?: BlobCommandOptions): Promise<void>;

@@ -118,2 +34,2 @@ interface HeadBlobResult {

export { BlobAccessError, BlobCommandOptions, BlobError, BlobUnknownError, BlobUploadCompletedEvent, GenerateClientTokenOptions, HandleBlobUploadBody, HandleBlobUploadOptions, HeadBlobResult, ListBlobResult, ListCommandOptions, PutBlobResult, PutCommandOptions, del, generateClientTokenFromReadWriteToken, getPayloadFromClientToken, handleBlobUpload, head, list, put, verifyCallbackSignature };
export { HeadBlobResult, ListBlobResult, ListCommandOptions, PutBlobResult, del, head, list, put };

@@ -0,231 +1,16 @@

import {
BlobAccessError,
BlobError,
BlobUnknownError,
createPutMethod,
getApiUrl,
getApiVersionHeader,
getTokenFromOptionsOrEnv
} from "./chunk-YDXL6NRJ.js";
// src/index.ts
import { fetch } from "undici";
// src/helpers.ts
function getToken(options) {
if (typeof window !== "undefined") {
if (!(options == null ? void 0 : options.token)) {
throw new BlobError('"token" is required');
}
if (!options.token.startsWith("vercel_blob_client")) {
throw new BlobError("client upload only supports client tokens");
}
}
if (options == null ? void 0 : options.token) {
return options.token;
}
if (!process.env.BLOB_READ_WRITE_TOKEN) {
throw new Error(
"BLOB_READ_WRITE_TOKEN environment variable is not set. Please set it to your write token."
);
}
return process.env.BLOB_READ_WRITE_TOKEN;
}
var BlobError = class extends Error {
constructor(message) {
super(`Vercel Blob: ${message}`);
}
};
var BlobAccessError = class extends Error {
constructor() {
super(
"Vercel Blob: Access denied, please provide a valid token for this resource"
);
}
};
var BlobUnknownError = class extends Error {
constructor() {
super("Vercel Blob: Unknown error, please visit https://vercel.com/help");
}
};
// src/client-upload.ts
import * as crypto from "crypto";
async function generateClientTokenFromReadWriteToken({
token,
...args
}) {
var _a;
if (typeof window !== "undefined") {
throw new Error(
'"generateClientTokenFromReadWriteToken" must be called from a server environment'
);
}
const timestamp = /* @__PURE__ */ new Date();
timestamp.setSeconds(timestamp.getSeconds() + 30);
const blobToken = getToken({ token });
const [, , , storeId = null] = blobToken.split("_");
if (!storeId) {
throw new Error(
token ? 'Invalid "token" parameter' : "Invalid BLOB_READ_WRITE_TOKEN"
);
}
const payload = Buffer.from(
JSON.stringify({
...args,
validUntil: (_a = args.validUntil) != null ? _a : timestamp.getTime()
})
).toString("base64");
const securedKey = await signPayload(payload, blobToken);
if (!securedKey) {
throw new Error("Unable to sign client token");
}
return `vercel_blob_client_${storeId}_${Buffer.from(
`${securedKey}.${payload}`
).toString("base64")}`;
}
async function importKey(token) {
return globalThis.crypto.subtle.importKey(
"raw",
new TextEncoder().encode(getToken({ token })),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign", "verify"]
);
}
async function signPayload(payload, token) {
if (!globalThis.crypto) {
return crypto.createHmac("sha256", token).update(payload).digest("hex");
}
const signature = await globalThis.crypto.subtle.sign(
"HMAC",
await importKey(token),
new TextEncoder().encode(payload)
);
return Buffer.from(new Uint8Array(signature)).toString("hex");
}
async function verifyCallbackSignature({
token,
signature,
body
}) {
const secret = getToken({ token });
if (!globalThis.crypto) {
const digest = crypto.createHmac("sha256", secret).update(body).digest("hex");
const digestBuffer = Buffer.from(digest);
const signatureBuffer = Buffer.from(signature);
return digestBuffer.length === signatureBuffer.length && crypto.timingSafeEqual(digestBuffer, signatureBuffer);
}
const verified = await globalThis.crypto.subtle.verify(
"HMAC",
await importKey(token),
hexToArrayByte(signature),
new TextEncoder().encode(body)
);
return verified;
}
function hexToArrayByte(input) {
if (input.length % 2 !== 0) {
throw new RangeError("Expected string to be an even number of characters");
}
const view = new Uint8Array(input.length / 2);
for (let i = 0; i < input.length; i += 2) {
view[i / 2] = parseInt(input.substring(i, i + 2), 16);
}
return Buffer.from(view);
}
function getPayloadFromClientToken(clientToken) {
const [, , , , encodedToken] = clientToken.split("_");
const encodedPayload = Buffer.from(encodedToken != null ? encodedToken : "", "base64").toString().split(".")[1];
const decodedPayload = Buffer.from(encodedPayload != null ? encodedPayload : "", "base64").toString();
return JSON.parse(decodedPayload);
}
var EventTypes = {
generateClientToken: "blob.generate-client-token",
uploadCompleted: "blob.upload-completed"
};
async function handleBlobUpload({
token,
request,
body,
onBeforeGenerateToken,
onUploadCompleted
}) {
var _a, _b, _c;
const type = body.type;
switch (type) {
case "blob.generate-client-token": {
const { pathname, callbackUrl } = body.payload;
const payload = await onBeforeGenerateToken(pathname);
return {
type,
clientToken: await generateClientTokenFromReadWriteToken({
...payload,
token,
pathname,
onUploadCompleted: {
callbackUrl,
metadata: (_a = payload.metadata) != null ? _a : null
}
})
};
}
case "blob.upload-completed": {
const signatureHeader = "x-vercel-signature";
const signature = "credentials" in request ? (_b = request.headers.get(signatureHeader)) != null ? _b : "" : (_c = request.headers[signatureHeader]) != null ? _c : "";
if (!signature) {
throw new Error("Invalid callback signature");
}
const isVerified = await verifyCallbackSignature({
signature,
body: JSON.stringify(body)
});
if (!isVerified) {
throw new Error("Invalid callback signature");
}
await onUploadCompleted(body.payload);
return { type, response: "ok" };
}
default:
throw new Error("Invalid event type");
}
}
// src/index.ts
var BLOB_API_VERSION = 2;
async function put(pathname, body, options) {
if (!pathname) {
throw new BlobError("pathname is required");
}
if (!body) {
throw new BlobError("body is required");
}
if (!options || options.access !== "public") {
throw new BlobError('access must be "public"');
}
const token = shouldFetchClientToken(options) ? await retrieveClientToken({
handleBlobUploadUrl: options.handleBlobUploadUrl,
pathname
}) : getToken(options);
const headers = {
...getApiVersionHeader(),
authorization: `Bearer ${token}`
};
if (options.contentType) {
headers["x-content-type"] = options.contentType;
}
if (options.addRandomSuffix !== void 0) {
headers["x-add-random-suffix"] = options.addRandomSuffix ? "1" : "0";
}
if (options.cacheControlMaxAge !== void 0) {
headers["x-cache-control-max-age"] = options.cacheControlMaxAge.toString();
}
const blobApiResponse = await fetch(getApiUrl(`/${pathname}`), {
method: "PUT",
body,
headers,
// required in order to stream some body types to Cloudflare
// currently only supported in Node.js, we may have to feature detect this
duplex: "half"
});
if (blobApiResponse.status !== 200) {
if (blobApiResponse.status === 403) {
throw new BlobAccessError();
} else {
throw new BlobUnknownError();
}
}
const blobResult = await blobApiResponse.json();
return blobResult;
}
var put = createPutMethod({
allowedOptions: ["cacheControlMaxAge", "addRandomSuffix", "contentType"]
});
async function del(url, options) {

@@ -236,3 +21,3 @@ const blobApiResponse = await fetch(getApiUrl("/delete"), {

...getApiVersionHeader(),
authorization: `Bearer ${getToken(options)}`,
authorization: `Bearer ${getTokenFromOptionsOrEnv(options)}`,
"content-type": "application/json"

@@ -259,3 +44,3 @@ },

...getApiVersionHeader(),
authorization: `Bearer ${getToken(options)}`
authorization: `Bearer ${getTokenFromOptionsOrEnv(options)}`
}

@@ -291,3 +76,3 @@ });

...getApiVersionHeader(),
authorization: `Bearer ${getToken(options)}`
authorization: `Bearer ${getTokenFromOptionsOrEnv(options)}`
}

@@ -308,10 +93,2 @@ });

}
function getApiUrl(pathname = "") {
let baseUrl = null;
try {
baseUrl = process.env.VERCEL_BLOB_API_URL || process.env.NEXT_PUBLIC_VERCEL_BLOB_API_URL;
} catch {
}
return `${baseUrl || "https://blob.vercel-storage.com"}${pathname}`;
}
function mapBlobResult(blobResult) {

@@ -323,42 +100,2 @@ return {

}
function isAbsoluteUrl(url) {
try {
return Boolean(new URL(url));
} catch (e) {
return false;
}
}
async function retrieveClientToken(options) {
const { handleBlobUploadUrl, pathname } = options;
const url = isAbsoluteUrl(handleBlobUploadUrl) ? handleBlobUploadUrl : `${window.location.origin}${handleBlobUploadUrl}`;
const res = await fetch(url, {
method: "POST",
body: JSON.stringify({
type: EventTypes.generateClientToken,
payload: { pathname, callbackUrl: url }
})
});
if (!res.ok) {
throw new BlobError("Failed to retrieve the client token");
}
try {
const { clientToken } = await res.json();
return clientToken;
} catch (e) {
throw new BlobError("Failed to retrieve the client token");
}
}
function shouldFetchClientToken(options) {
return Boolean(!options.token && options.handleBlobUploadUrl);
}
function getApiVersionHeader() {
let versionOverride = null;
try {
versionOverride = process.env.VERCEL_BLOB_API_VERSION_OVERRIDE || process.env.NEXT_PUBLIC_VERCEL_BLOB_API_VERSION_OVERRIDE;
} catch {
}
return {
"x-api-version": `${versionOverride != null ? versionOverride : BLOB_API_VERSION}`
};
}
export {

@@ -369,10 +106,6 @@ BlobAccessError,

del,
generateClientTokenFromReadWriteToken,
getPayloadFromClientToken,
handleBlobUpload,
head,
list,
put,
verifyCallbackSignature
put
};
//# sourceMappingURL=index.js.map
{
"name": "@vercel/blob",
"version": "0.11.0",
"version": "0.12.0",
"description": "The Vercel Blob JavaScript API client",

@@ -14,2 +14,14 @@ "homepage": "https://vercel.com/storage/blob",

"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./client": {
"types": "./dist/client.d.ts",
"import": "./dist/client.js",
"require": "./dist/client.cjs"
}
},
"main": "./dist/index.cjs",

@@ -16,0 +28,0 @@ "module": "./dist/index.js",

@@ -16,3 +16,3 @@ # 🍙 @vercel/blob

1. [Server uploads](https://vercel.com/docs/storage/vercel-blob/quickstart#server-uploads): This is the most common way to upload files. The file is first sent to your server and then to Vercel Blob. It's straightforward to implement, but you are limited to the request body your server can handle. Which in case of a Vercel-hosted website is 4.5 MB. **This means you can't upload files larger than 4.5 MB on Vercel when using this method.**
2. [Browser uploads](https://vercel.com/docs/storage/vercel-blob/quickstart#browser-uploads): This is a more advanced solution for when you need to upload larger files. The file is securely sent directly from the browser to Vercel Blob. This requires a bit more work to implement, but it allows you to upload files up to 500 MB.
2. [Client uploads](https://vercel.com/docs/storage/vercel-blob/quickstart#client-uploads): This is a more advanced solution for when you need to upload larger files. The file is securely sent directly from the client (a browser for example) to Vercel Blob. This requires a bit more work to implement, but it allows you to upload files up to 500 MB.

@@ -35,7 +35,5 @@ ## API

// or using Vercel Blob outside of Vercel
// During browser uploads, a token is automatically generated by calling the route from the `handleBlobUploadUrl` option
token?: string,
// Browser uploads: this is the URL that will be called to generate a secure token before sending the file to Vercel Blob
handleBlobUploadUrl?: string,
cacheControlMaxAge?: number, // optional, a duration in seconds to configure the edge and browser caches. Defaults to one year. Can only be configured server side (either on server side put or during client token generation).
addRandomSuffix?: boolean; // optional, allows to disable or enable random suffixes (defaults to `true`)
cacheControlMaxAge?: number, // optional, a duration in seconds to configure the edge and browser caches. Defaults to one year for browsers and 5 minutes for edge cache. Can only be configured server side (either on server side put or during client token generation). The Edge cache maximum value is 5 minutes.
}): Promise<{

@@ -104,16 +102,51 @@ pathname: string;

### handleBlobUpload(options)
### client/`upload(pathname, body, options)`
Handles the requests to generate a client token and respond to the upload completed event. This is useful when [uploading from browsers](#browser-upload) to circumvent the 4.5 MB limitation of going through a Vercel-hosted route.
The `upload` method is dedicated to client uploads. It fetches a client token using the `handleUploadUrl` before uploading the blob.
Read the [client uploads](https://vercel.com/docs/storage/vercel-blob/quickstart#client-uploads) documentation to know more.
```ts
async function handleBlobUpload(options?: {
async function upload(
pathname: string,
body: ReadableStream | String | ArrayBuffer | Blob | File // All fetch body types are supported: https://developer.mozilla.org/en-US/docs/Web/API/fetch#body
options: {
access: 'public', // mandatory, as we will provide private blobs in the future
contentType?: string, // by default inferred from pathname
// `token` defaults to process.env.BLOB_READ_WRITE_TOKEN on Vercel
// and can be configured when you connect more stores to a project
// or using Vercel Blob outside of Vercel
handleUploadUrl?: string, // A string specifying the route to call for generating client tokens for client uploads
clientPayload?: string, // A string that will be passed to the `onUploadCompleted` callback as `tokenPayload`. It can be used to attach data to the upload, like `JSON.stringify({ postId: 123 })`.
}): Promise<{
pathname: string;
contentType: string;
contentDisposition: string;
url: string;
}> {}
```
### client/`handleUpload(options)`
This is a server-side route helper to manage client uploads, it has two responsibilities:
1. Generate tokens for client uploads
2. Listen for completed client uploads, so you can update your database with the URL of the uploaded file for example
Read the [client uploads](https://vercel.com/docs/storage/vercel-blob/quickstart#client-uploads) documentation to know more.
```ts
async function handleUpload(options?: {
token?: string; // default to process.env.BLOB_READ_WRITE_TOKEN
request: IncomingMessage | Request;
onBeforeGenerateToken: (pathname: string) => Promise<{
onBeforeGenerateToken: (
pathname: string,
clientPayload?: string
) => Promise<{
allowedContentTypes?: string[]; // optional, defaults to no restriction
maximumSizeInBytes?: number; // optional, defaults and maximum is 500MB (524,288,000 bytes)
validUntil?: number; // optional, timestamp in ms, by default now + 30s (30,000)
addRandomSuffix?: boolean; // optional, allows to disable or enable random suffixes
metadata?: string;
addRandomSuffix?: boolean; // see `put` options
cacheControlMaxAge?: number; // see `put` options
tokenPayload?: string; // optional, defaults to whatever the client sent as `clientPayload`
}>;

@@ -124,3 +157,3 @@ onUploadCompleted: (body: {

blob: PutBlobResult;
metadata?: string;
tokenPayload?: string;
};

@@ -133,3 +166,3 @@ }) => Promise<void>;

blob: PutBlobResult;
metadata?: string;
tokenPayload?: string;
};

@@ -139,3 +172,7 @@ }

type: 'blob.generate-client-token';
payload: { pathname: string; callbackUrl: string };
payload: {
pathname: string;
callbackUrl: string;
clientPayload: string;
};
};

@@ -148,32 +185,6 @@ }): Promise<

Note: This method should be called server-side, not client-side.
### generateClientTokenFromReadWriteToken(options)
Generates a single-use token that can be used from within the client. This method is called internally by `handleBlobUpload`.
Once created, a client token is valid by default for 30 seconds (can be customized by configuring the `validUntil` field). This means you have 30 seconds to initiate an upload with this token.
```ts
async function generateClientTokenFromReadWriteToken(options?: {
token?: string;
pathname?: string;
onUploadCompleted?: {
callbackUrl: string;
metadata?: string;
};
maximumSizeInBytes?: number;
allowedContentTypes?: string[];
validUntil?: number; // optional, timestamp in ms, by default now + 30s (30,000)
addRandomSuffix?: boolean; // see `put` options
cacheControlMaxAge?: number; // see `put` options
}): string {}
```
Note: This is a server-side method.
## Examples
- [Next.js App Router examples](../../test/next/src/app/vercel/blob/)
- [https.get, axios, and got](../../test/next/src/app/vercel/blob/script.ts)
- [https.get, axios, and got](../../test/next/src/app/vercel/blob/script.mts)

@@ -232,3 +243,3 @@ ## How to list all your blobs

When transferring a file to a Serverless or Edge Functions route on Vercel, then the request body is limited to 4.5 MB. If you need to send larger files then use the [browser-upload](#browser-upload) method.
When transferring a file to a Serverless or Edge Functions route on Vercel, then the request body is limited to 4.5 MB. If you need to send larger files then use the [client-upload](#client-upload) method.

@@ -235,0 +246,0 @@ ## Running examples locally

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc