@thirdweb-dev/storage
Advanced tools
Comparing version 0.0.0-dev-c5ab0db-20230801204042 to 0.0.0-dev-c5e5452-20231204124728
import { GatewayUrls } from "../types"; | ||
/** | ||
* @internal | ||
* @param url | ||
* @param url - the url to check | ||
* @returns | ||
@@ -27,3 +27,3 @@ */ | ||
*/ | ||
export declare function getGatewayUrlForCid(gatewayUrl: string, cid: string): string; | ||
export declare function getGatewayUrlForCid(gatewayUrl: string, cid: string, clientId?: string): string; | ||
/** | ||
@@ -30,0 +30,0 @@ * @internal |
@@ -31,3 +31,3 @@ /// <reference types="node" /> | ||
*/ | ||
export declare function replaceSchemeWithGatewayUrl(uri: string, gatewayUrls: GatewayUrls, index?: number): string | undefined; | ||
export declare function replaceSchemeWithGatewayUrl(uri: string, gatewayUrls: GatewayUrls, index?: number, clientId?: string): string | undefined; | ||
/** | ||
@@ -40,3 +40,3 @@ * @internal | ||
*/ | ||
export declare function replaceObjectSchemesWithGatewayUrls<TData = unknown>(data: TData, gatewayUrls: GatewayUrls): TData; | ||
export declare function replaceObjectSchemesWithGatewayUrls<TData = unknown>(data: TData, gatewayUrls: GatewayUrls, clientId?: string): TData; | ||
/** | ||
@@ -43,0 +43,0 @@ * @internal |
@@ -1,2 +0,2 @@ | ||
import { GatewayUrls, IStorageDownloader, IpfsDownloaderOptions } from "../../types"; | ||
import { GatewayUrls, IStorageDownloader, IpfsDownloaderOptions, SingleDownloadOptions } from "../../types"; | ||
/** | ||
@@ -22,8 +22,10 @@ * Default downloader used - handles downloading from all schemes specified in the gateway URLs configuration. | ||
export declare class StorageDownloader implements IStorageDownloader { | ||
DEFAULT_TIMEOUT_IN_SECONDS: number; | ||
DEFAULT_MAX_RETRIES: number; | ||
private secretKey?; | ||
private clientId?; | ||
private authToken; | ||
private defaultTimeout; | ||
constructor(options: IpfsDownloaderOptions); | ||
download(uri: string, gatewayUrls: GatewayUrls, attempts?: number): Promise<Response>; | ||
download(uri: string, gatewayUrls: GatewayUrls, options?: SingleDownloadOptions, attempts?: number): Promise<Response>; | ||
} | ||
//# sourceMappingURL=storage-downloader.d.ts.map |
@@ -1,2 +0,2 @@ | ||
import { GatewayUrls, IThirdwebStorage, IpfsUploadBatchOptions, ThirdwebStorageOptions, UploadOptions } from "../types"; | ||
import { GatewayUrls, IThirdwebStorage, IpfsUploadBatchOptions, SingleDownloadOptions, ThirdwebStorageOptions, UploadOptions } from "../types"; | ||
/** | ||
@@ -40,2 +40,3 @@ * Upload and download files from decentralized storage systems. | ||
private gatewayUrls; | ||
private clientId?; | ||
constructor(options?: ThirdwebStorageOptions<T>); | ||
@@ -68,3 +69,3 @@ /** | ||
*/ | ||
download(url: string): Promise<Response>; | ||
download(url: string, options?: SingleDownloadOptions): Promise<Response>; | ||
/** | ||
@@ -83,3 +84,3 @@ * Downloads JSON data from any URL scheme. | ||
*/ | ||
downloadJSON<TJSON = any>(url: string): Promise<TJSON>; | ||
downloadJSON<TJSON = any>(url: string, options?: SingleDownloadOptions): Promise<TJSON>; | ||
/** | ||
@@ -86,0 +87,0 @@ * Upload arbitrary file or JSON data using the configured decentralized storage system. |
@@ -31,5 +31,5 @@ import { FileOrBufferOrString, IpfsUploadBatchOptions, IpfsUploaderOptions, IStorageUploader } from "../../types"; | ||
uploadWithGatewayUrl: boolean; | ||
private uploadServerUrl; | ||
private clientId?; | ||
private secretKey?; | ||
private authToken; | ||
constructor(options?: IpfsUploaderOptions); | ||
@@ -36,0 +36,0 @@ uploadBatch(data: FileOrBufferOrString[], options?: IpfsUploadBatchOptions): Promise<string[]>; |
@@ -12,3 +12,3 @@ /** | ||
*/ | ||
download(url: string, gatewayUrls?: GatewayUrls): Promise<Response>; | ||
download(url: string, gatewayUrls?: GatewayUrls, options?: SingleDownloadOptions): Promise<Response>; | ||
} | ||
@@ -29,3 +29,17 @@ /** | ||
clientId?: string; | ||
/** | ||
* Optional timeout in seconds for the download request, overrides the default timeout | ||
*/ | ||
timeoutInSeconds?: number; | ||
}; | ||
export type SingleDownloadOptions = { | ||
/** | ||
* Optional timeout in seconds for the download request, overrides the default timeout | ||
*/ | ||
timeoutInSeconds?: number; | ||
/** | ||
* Number of different gateways to attempt on failure | ||
*/ | ||
maxRetries?: number; | ||
}; | ||
/** | ||
@@ -32,0 +46,0 @@ * @public |
@@ -1,2 +0,2 @@ | ||
import { GatewayUrls, IStorageDownloader } from "./download"; | ||
import { GatewayUrls, IStorageDownloader, SingleDownloadOptions } from "./download"; | ||
import { IStorageUploader, UploadOptions } from "./upload"; | ||
@@ -7,2 +7,3 @@ export type ThirdwebStorageOptions<T extends UploadOptions> = { | ||
gatewayUrls?: GatewayUrls | string[]; | ||
uploadServerUrl?: string; | ||
clientId?: string; | ||
@@ -13,3 +14,3 @@ secretKey?: string; | ||
resolveScheme(url: string): string; | ||
download(url: string): Promise<Response>; | ||
download(url: string, options?: SingleDownloadOptions): Promise<Response>; | ||
downloadJSON<TJSON = any>(url: string): Promise<TJSON>; | ||
@@ -16,0 +17,0 @@ upload(data: any, options?: { |
@@ -56,2 +56,6 @@ import { FileOrBufferOrString } from "./data"; | ||
secretKey?: string; | ||
/** | ||
* Optional upload server url to use instead of the default. (Advanced Usage) | ||
*/ | ||
uploadServerUrl?: string; | ||
}; | ||
@@ -58,0 +62,0 @@ /** |
@@ -6,3 +6,3 @@ 'use strict'; | ||
var CIDTool = require('cid-tool'); | ||
var fetch = require('cross-fetch'); | ||
var crypto = require('@thirdweb-dev/crypto'); | ||
var FormData = require('form-data'); | ||
@@ -14,3 +14,2 @@ var uuid = require('uuid'); | ||
var CIDTool__default = /*#__PURE__*/_interopDefault(CIDTool); | ||
var fetch__default = /*#__PURE__*/_interopDefault(fetch); | ||
var FormData__default = /*#__PURE__*/_interopDefault(FormData); | ||
@@ -29,2 +28,3 @@ | ||
const TW_HOSTNAME_SUFFIX = ".ipfscdn.io"; | ||
const TW_STAGINGHOSTNAME_SUFFIX = ".thirdwebstorage-staging.com"; | ||
const TW_GATEWAY_URLS = [`https://{clientId}${TW_HOSTNAME_SUFFIX}/ipfs/{cid}/{path}`]; | ||
@@ -34,7 +34,13 @@ | ||
* @internal | ||
* @param url | ||
* @param url - the url to check | ||
* @returns | ||
*/ | ||
function isTwGatewayUrl(url) { | ||
return new URL(url).hostname.endsWith(TW_HOSTNAME_SUFFIX); | ||
const hostname = new URL(url).hostname; | ||
const isProd = hostname.endsWith(TW_HOSTNAME_SUFFIX); | ||
if (isProd) { | ||
return true; | ||
} | ||
// fall back to also handle staging urls | ||
return hostname.endsWith(TW_STAGINGHOSTNAME_SUFFIX); | ||
} | ||
@@ -76,3 +82,3 @@ const PUBLIC_GATEWAY_URLS = ["https://{cid}.ipfs.cf-ipfs.com/{path}", "https://{cid}.ipfs.dweb.link/{path}", "https://ipfs.io/ipfs/{cid}/{path}", "https://cloudflare-ipfs.com/ipfs/{cid}/{path}", "https://{cid}.ipfs.w3s.link/{path}", "https://w3s.link/ipfs/{cid}/{path}", "https://nftstorage.link/ipfs/{cid}/{path}", "https://gateway.pinata.cloud/ipfs/{cid}/{path}"]; | ||
*/ | ||
function getGatewayUrlForCid(gatewayUrl, cid) { | ||
function getGatewayUrlForCid(gatewayUrl, cid, clientId) { | ||
const parts = cid.split("/"); | ||
@@ -96,2 +102,9 @@ const hash = convertCidToV1(parts[0]); | ||
} | ||
// if the URL contains the {clientId} token, replace it with the client ID | ||
if (gatewayUrl.includes("{clientId}")) { | ||
if (!clientId) { | ||
throw new Error("Cannot use {clientId} in gateway URL without providing a client ID"); | ||
} | ||
url = url.replace("{clientId}", clientId); | ||
} | ||
return url; | ||
@@ -118,6 +131,3 @@ } | ||
} | ||
// this is on purpose because we're using the crypto module only in node | ||
// eslint-disable-next-line @typescript-eslint/no-var-requires | ||
const crypto = require("crypto"); | ||
const hashedSecretKey = crypto.createHash("sha256").update(secretKey).digest("hex"); | ||
const hashedSecretKey = crypto.sha256HexSync(secretKey); | ||
const derivedClientId = hashedSecretKey.slice(0, 32); | ||
@@ -261,2 +271,3 @@ return url.replace("{clientId}", derivedClientId); | ||
let index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; | ||
let clientId = arguments.length > 3 ? arguments[3] : undefined; | ||
const scheme = Object.keys(gatewayUrls).find(s => uri.startsWith(s)); | ||
@@ -271,3 +282,9 @@ const schemeGatewayUrls = scheme ? gatewayUrls[scheme] : []; | ||
const path = uri.replace(scheme, ""); | ||
return getGatewayUrlForCid(schemeGatewayUrls[index], path); | ||
try { | ||
const gatewayUrl = getGatewayUrlForCid(schemeGatewayUrls[index], path, clientId); | ||
return gatewayUrl; | ||
} catch (err) { | ||
console.warn(`The IPFS uri: ${path} is not valid.`); | ||
return undefined; | ||
} | ||
} | ||
@@ -303,5 +320,5 @@ | ||
*/ | ||
function replaceObjectSchemesWithGatewayUrls(data, gatewayUrls) { | ||
function replaceObjectSchemesWithGatewayUrls(data, gatewayUrls, clientId) { | ||
if (typeof data === "string") { | ||
return replaceSchemeWithGatewayUrl(data, gatewayUrls); | ||
return replaceSchemeWithGatewayUrl(data, gatewayUrls, 0, clientId); | ||
} | ||
@@ -316,7 +333,7 @@ if (typeof data === "object") { | ||
if (Array.isArray(data)) { | ||
return data.map(entry => replaceObjectSchemesWithGatewayUrls(entry, gatewayUrls)); | ||
return data.map(entry => replaceObjectSchemesWithGatewayUrls(entry, gatewayUrls, clientId)); | ||
} | ||
return Object.fromEntries(Object.entries(data).map(_ref2 => { | ||
let [key, value] = _ref2; | ||
return [key, replaceObjectSchemesWithGatewayUrls(value, gatewayUrls)]; | ||
return [key, replaceObjectSchemesWithGatewayUrls(value, gatewayUrls, clientId)]; | ||
})); | ||
@@ -378,2 +395,72 @@ } | ||
var pkg = { | ||
name: "@thirdweb-dev/storage", | ||
version: "2.0.5", | ||
main: "dist/thirdweb-dev-storage.cjs.js", | ||
module: "dist/thirdweb-dev-storage.esm.js", | ||
exports: { | ||
".": { | ||
module: "./dist/thirdweb-dev-storage.esm.js", | ||
"default": "./dist/thirdweb-dev-storage.cjs.js" | ||
}, | ||
"./package.json": "./package.json" | ||
}, | ||
repository: "https://github.com/thirdweb-dev/js/tree/main/packages/storage", | ||
author: "thirdweb eng <eng@thirdweb.com>", | ||
license: "Apache-2.0", | ||
sideEffects: false, | ||
scripts: { | ||
format: "prettier --write 'src/**/*'", | ||
lint: "eslint src/ && bunx publint --strict --level warning", | ||
fix: "eslint src/ --fix", | ||
"generate-docs": "api-extractor run --local && api-documenter markdown -i ./temp -o ./docs", | ||
clean: "rm -rf dist/", | ||
build: "tsc && preconstruct build", | ||
"test:all": "NODE_ENV=test SWC_NODE_PROJECT=./tsconfig.test.json mocha --timeout 30000 --parallel './test/**/*.test.ts'", | ||
test: "pnpm test:all", | ||
"test:single": "NODE_ENV=test SWC_NODE_PROJECT=./tsconfig.test.json mocha --timeout 30000", | ||
push: "yalc push", | ||
typedoc: "rimraf typedoc && node typedoc.js" | ||
}, | ||
files: [ | ||
"dist/" | ||
], | ||
preconstruct: { | ||
exports: true | ||
}, | ||
devDependencies: { | ||
"@babel/preset-env": "^7.22.9", | ||
"@babel/preset-typescript": "^7.22.5", | ||
"@microsoft/api-documenter": "^7.22.30", | ||
"@microsoft/api-extractor": "^7.36.3", | ||
"@microsoft/tsdoc": "^0.14.1", | ||
"@preconstruct/cli": "2.7.0", | ||
"@swc-node/register": "^1.6.6", | ||
"@thirdweb-dev/tsconfig": "workspace:*", | ||
"@types/chai": "^4.3.5", | ||
"@types/mocha": "^10.0.0", | ||
"@types/uuid": "^9.0.5", | ||
"@typescript-eslint/eslint-plugin": "^6.2.0", | ||
"@typescript-eslint/parser": "^6.2.0", | ||
chai: "^4.3.6", | ||
eslint: "^8.45.0", | ||
"eslint-config-thirdweb": "workspace:*", | ||
"eslint-plugin-tsdoc": "^0.2.16", | ||
esm: "^3.2.25", | ||
mocha: "^10.2.0", | ||
rimraf: "^3.0.2", | ||
typedoc: "^0.25.2", | ||
typescript: "^5.1.6" | ||
}, | ||
dependencies: { | ||
"cid-tool": "^3.0.0", | ||
"form-data": "^4.0.0", | ||
uuid: "^9.0.1", | ||
"@thirdweb-dev/crypto": "workspace:*" | ||
}, | ||
engines: { | ||
node: ">=18" | ||
} | ||
}; | ||
/** | ||
@@ -399,17 +486,16 @@ * Default downloader used - handles downloading from all schemes specified in the gateway URLs configuration. | ||
class StorageDownloader { | ||
DEFAULT_TIMEOUT_IN_SECONDS = 60; | ||
DEFAULT_MAX_RETRIES = 3; | ||
constructor(options) { | ||
this.secretKey = options.secretKey; | ||
this.clientId = options.clientId; | ||
this.authToken = null; | ||
const authTokenExists = typeof globalThis !== "undefined" && "AUTH_TOKEN" in globalThis; | ||
if (authTokenExists) { | ||
this.authToken = globalThis.AUTH_TOKEN; | ||
} | ||
this.defaultTimeout = options.timeoutInSeconds || this.DEFAULT_TIMEOUT_IN_SECONDS; | ||
} | ||
async download(uri, gatewayUrls) { | ||
let attempts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; | ||
if (attempts > 3) { | ||
async download(uri, gatewayUrls, options) { | ||
let attempts = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; | ||
const maxRetries = options?.maxRetries || this.DEFAULT_MAX_RETRIES; | ||
if (attempts > maxRetries) { | ||
console.error("[FAILED_TO_DOWNLOAD_ERROR] Failed to download from URI - too many attempts failed."); | ||
// return a 404 response to avoid retrying | ||
return new fetch.Response(JSON.stringify({ | ||
return new Response(JSON.stringify({ | ||
error: "Not Found" | ||
@@ -425,7 +511,7 @@ }), { | ||
// Replace recognized scheme with the highest priority gateway URL that hasn't already been attempted | ||
let resolvedUri = replaceSchemeWithGatewayUrl(uri, gatewayUrls, attempts); | ||
let resolvedUri = replaceSchemeWithGatewayUrl(uri, gatewayUrls, attempts, this.clientId); | ||
// If every gateway URL we know about for the designated scheme has been tried (via recursion) and failed, throw an error | ||
if (!resolvedUri) { | ||
console.error("[FAILED_TO_DOWNLOAD_ERROR] Unable to download from URI - all gateway URLs failed to respond."); | ||
return new fetch.Response(JSON.stringify({ | ||
return new Response(JSON.stringify({ | ||
error: "Not Found" | ||
@@ -441,9 +527,5 @@ }), { | ||
} | ||
let headers; | ||
let headers = {}; | ||
if (isTwGatewayUrl(resolvedUri)) { | ||
if (this.authToken) { | ||
headers = { | ||
Authorization: `Bearer ${this.authToken}` | ||
}; | ||
} | ||
const bundleId = typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis ? globalThis.APP_BUNDLE_ID : undefined; | ||
if (this.secretKey) { | ||
@@ -454,17 +536,33 @@ headers = { | ||
} else if (this.clientId) { | ||
if (typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis) { | ||
resolvedUri = resolvedUri + `?bundleId=${globalThis.APP_BUNDLE_ID}`; | ||
if (!resolvedUri.includes("bundleId")) { | ||
resolvedUri = resolvedUri + (bundleId ? `?bundleId=${bundleId}` : ""); | ||
} | ||
headers["x-client-Id"] = this.clientId; | ||
} | ||
// if we have a authorization token on global context then add that to the headers | ||
if (typeof globalThis !== "undefined" && "TW_AUTH_TOKEN" in globalThis && typeof globalThis.TW_AUTH_TOKEN === "string") { | ||
headers = { | ||
"x-client-Id": this.clientId | ||
...headers, | ||
authorization: `Bearer ${globalThis.TW_AUTH_TOKEN}` | ||
}; | ||
} | ||
if (typeof globalThis !== "undefined" && "TW_CLI_AUTH_TOKEN" in globalThis && typeof globalThis.TW_CLI_AUTH_TOKEN === "string") { | ||
headers = { | ||
...headers, | ||
authorization: `Bearer ${globalThis.TW_CLI_AUTH_TOKEN}` | ||
}; | ||
headers["x-authorize-wallet"] = "true"; | ||
} | ||
headers["x-sdk-version"] = pkg.version; | ||
headers["x-sdk-name"] = pkg.name; | ||
headers["x-sdk-platform"] = bundleId ? "react-native" : isBrowser() ? window.bridge !== undefined ? "webGL" : "browser" : "node"; | ||
} | ||
if (isTooManyRequests(resolvedUri)) { | ||
// skip the request if we're getting too many request error from the gateway | ||
return this.download(uri, gatewayUrls, attempts + 1); | ||
return this.download(uri, gatewayUrls, options, attempts + 1); | ||
} | ||
const controller = new AbortController(); | ||
const timeout = setTimeout(() => controller.abort(), 5000); | ||
const resOrErr = await fetch__default["default"](resolvedUri, { | ||
const timeoutInSeconds = options?.timeoutInSeconds || this.defaultTimeout; | ||
const timeout = setTimeout(() => controller.abort(), timeoutInSeconds * 1000); | ||
const resOrErr = await fetch(resolvedUri, { | ||
headers, | ||
@@ -479,3 +577,3 @@ signal: controller.signal | ||
// early exit if we don't have a status code | ||
return this.download(uri, gatewayUrls, attempts + 1); | ||
throw new Error(`Request timed out after ${timeoutInSeconds} seconds. ${isTwGatewayUrl(resolvedUri) ? "You can update the timeoutInSeconds option to increase the timeout." : "You're using a public IPFS gateway, pass in a clientId or secretKey for a reliable IPFS gateway."}`); | ||
} | ||
@@ -491,3 +589,3 @@ | ||
// Since the current gateway failed, recursively try the next one we know about | ||
return this.download(uri, gatewayUrls, attempts + 1); | ||
return this.download(uri, gatewayUrls, options, attempts + 1); | ||
} | ||
@@ -510,6 +608,8 @@ if (resOrErr.status === 410) { | ||
// 5xx - Server Errors | ||
if (resOrErr.status !== 408 && resOrErr.status !== 429 && resOrErr.status < 500) ; | ||
if (resOrErr.status !== 408 && resOrErr.status !== 429 && resOrErr.status < 500) { | ||
return resOrErr; | ||
} | ||
// Since the current gateway failed, recursively try the next one we know about | ||
return this.download(uri, gatewayUrls, attempts + 1); | ||
return this.download(uri, gatewayUrls, options, attempts + 1); | ||
} | ||
@@ -571,9 +671,5 @@ } | ||
this.uploadWithGatewayUrl = options?.uploadWithGatewayUrl || false; | ||
this.uploadServerUrl = options?.uploadServerUrl || TW_UPLOAD_SERVER_URL; | ||
this.clientId = options?.clientId; | ||
this.secretKey = options?.secretKey; | ||
this.authToken = null; | ||
const authTokenExists = typeof globalThis !== "undefined" && "AUTH_TOKEN" in globalThis; | ||
if (authTokenExists) { | ||
this.authToken = globalThis.AUTH_TOKEN; | ||
} | ||
} | ||
@@ -721,3 +817,3 @@ async uploadBatch(data, options) { | ||
} else { | ||
return resolve(fileNames.map(name => `ipfs://${cid}/${name}`)); | ||
return resolve(fileNames.map(n => `ipfs://${cid}/${n}`)); | ||
} | ||
@@ -731,14 +827,7 @@ } | ||
if (xhr.readyState !== 0 && xhr.readyState !== 4 || xhr.status === 0) { | ||
return reject(new Error("This looks like a network error, the endpoint might be blocked by an internet provider or a firewall.")); | ||
return reject(new Error("Upload failed due to a network error.")); | ||
} | ||
return reject(new Error("Unknown upload error occured")); | ||
}); | ||
xhr.open("POST", `${TW_UPLOAD_SERVER_URL}/ipfs/upload`); | ||
if (this.secretKey && this.clientId) { | ||
throw new Error("Cannot use both secret key and client ID. Please use secretKey for server-side applications and clientId for client-side applications."); | ||
} | ||
console.log("AuthToken from globalThis", this.authToken); | ||
if (this.authToken) { | ||
xhr.setRequestHeader("Authorization", `Bearer ${this.authToken}`); | ||
} | ||
xhr.open("POST", `${this.uploadServerUrl}/ipfs/upload`); | ||
if (this.secretKey) { | ||
@@ -749,2 +838,20 @@ xhr.setRequestHeader("x-secret-key", this.secretKey); | ||
} | ||
const bundleId = typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis ? globalThis.APP_BUNDLE_ID : undefined; | ||
if (bundleId) { | ||
xhr.setRequestHeader("x-bundle-id", bundleId); | ||
} | ||
xhr.setRequestHeader("x-sdk-version", pkg.version); | ||
xhr.setRequestHeader("x-sdk-name", pkg.name); | ||
xhr.setRequestHeader("x-sdk-platform", bundleId ? "react-native" : isBrowser() ? window.bridge !== undefined ? "webGL" : "browser" : "node"); | ||
// if we have a authorization token on global context then add that to the headers, this is for the dashboard. | ||
if (typeof globalThis !== "undefined" && "TW_AUTH_TOKEN" in globalThis && typeof globalThis.TW_AUTH_TOKEN === "string") { | ||
xhr.setRequestHeader("authorization", `Bearer ${globalThis.TW_AUTH_TOKEN}`); | ||
} | ||
// CLI auth token | ||
if (typeof globalThis !== "undefined" && "TW_CLI_AUTH_TOKEN" in globalThis && typeof globalThis.TW_CLI_AUTH_TOKEN === "string") { | ||
xhr.setRequestHeader("authorization", `Bearer ${globalThis.TW_CLI_AUTH_TOKEN}`); | ||
xhr.setRequestHeader("x-authorize-wallet", `true`); | ||
} | ||
xhr.send(form); | ||
@@ -757,9 +864,3 @@ }); | ||
} | ||
if (this.secretKey && this.clientId) { | ||
throw new Error("Cannot use both secret key and client ID. Please use secretKey for server-side applications and clientId for client-side applications."); | ||
} | ||
const headers = {}; | ||
if (this.authToken) { | ||
headers["Authorization"] = `Bearer ${this.authToken}`; | ||
} | ||
if (this.secretKey) { | ||
@@ -770,3 +871,19 @@ headers["x-secret-key"] = this.secretKey; | ||
} | ||
const res = await fetch__default["default"](`${TW_UPLOAD_SERVER_URL}/ipfs/upload`, { | ||
// if we have a bundle id on global context then add that to the headers | ||
if (typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis) { | ||
headers["x-bundle-id"] = globalThis.APP_BUNDLE_ID; | ||
} | ||
// if we have a authorization token on global context then add that to the headers, this is for the dashboard. | ||
if (typeof globalThis !== "undefined" && "TW_AUTH_TOKEN" in globalThis && typeof globalThis.TW_AUTH_TOKEN === "string") { | ||
headers["authorization"] = `Bearer ${globalThis.TW_AUTH_TOKEN}`; | ||
} | ||
// CLI auth token | ||
if (typeof globalThis !== "undefined" && "TW_CLI_AUTH_TOKEN" in globalThis && typeof globalThis.TW_CLI_AUTH_TOKEN === "string") { | ||
headers["authorization"] = `Bearer ${globalThis.TW_CLI_AUTH_TOKEN}`; | ||
headers["x-authorize-wallet"] = "true"; | ||
} | ||
const res = await fetch(`${this.uploadServerUrl}/ipfs/upload`, { | ||
method: "POST", | ||
@@ -779,10 +896,12 @@ headers: { | ||
}); | ||
const body = await res.json(); | ||
if (!res.ok) { | ||
console.warn(body); | ||
throw new Error("Failed to upload files to IPFS"); | ||
if (res.status === 401) { | ||
throw new Error("Unauthorized - You don't have permission to use this service."); | ||
} | ||
throw new Error(`Failed to upload files to IPFS - ${res.status} - ${res.statusText} - ${await res.text()}`); | ||
} | ||
const body = await res.json(); | ||
const cid = body.IpfsHash; | ||
if (!cid) { | ||
throw new Error("Failed to upload files to IPFS"); | ||
throw new Error("Failed to upload files to IPFS - Bad CID"); | ||
} | ||
@@ -835,3 +954,4 @@ if (options?.uploadWithoutDirectory) { | ||
clientId: options?.clientId, | ||
secretKey: options?.secretKey | ||
secretKey: options?.secretKey, | ||
uploadServerUrl: options?.uploadServerUrl | ||
}); | ||
@@ -843,2 +963,3 @@ this.downloader = options?.downloader || new StorageDownloader({ | ||
this.gatewayUrls = prepareGatewayUrls(parseGatewayUrls(options?.gatewayUrls), options?.clientId, options?.secretKey); | ||
this.clientId = options?.clientId; | ||
} | ||
@@ -860,3 +981,3 @@ | ||
resolveScheme(url) { | ||
return replaceSchemeWithGatewayUrl(url, this.gatewayUrls); | ||
return replaceSchemeWithGatewayUrl(url, this.gatewayUrls, 0, this.clientId); | ||
} | ||
@@ -876,4 +997,4 @@ | ||
*/ | ||
async download(url) { | ||
return this.downloader.download(url, this.gatewayUrls); | ||
async download(url, options) { | ||
return this.downloader.download(url, this.gatewayUrls, options); | ||
} | ||
@@ -894,8 +1015,8 @@ | ||
*/ | ||
async downloadJSON(url) { | ||
const res = await this.download(url); | ||
async downloadJSON(url, options) { | ||
const res = await this.download(url, options); | ||
// If we get a JSON object, recursively replace any schemes with gatewayUrls | ||
const json = await res.json(); | ||
return replaceObjectSchemesWithGatewayUrls(json, this.gatewayUrls); | ||
return replaceObjectSchemesWithGatewayUrls(json, this.gatewayUrls, this.clientId); | ||
} | ||
@@ -999,3 +1120,3 @@ | ||
// Ex: used for Solana, where services don't resolve schemes for you, so URLs must be usable by default | ||
cleaned = replaceObjectSchemesWithGatewayUrls(cleaned, this.gatewayUrls); | ||
cleaned = replaceObjectSchemesWithGatewayUrls(cleaned, this.gatewayUrls, this.clientId); | ||
} | ||
@@ -1002,0 +1123,0 @@ return cleaned; |
@@ -6,3 +6,3 @@ 'use strict'; | ||
var CIDTool = require('cid-tool'); | ||
var fetch = require('cross-fetch'); | ||
var crypto = require('@thirdweb-dev/crypto'); | ||
var FormData = require('form-data'); | ||
@@ -14,3 +14,2 @@ var uuid = require('uuid'); | ||
var CIDTool__default = /*#__PURE__*/_interopDefault(CIDTool); | ||
var fetch__default = /*#__PURE__*/_interopDefault(fetch); | ||
var FormData__default = /*#__PURE__*/_interopDefault(FormData); | ||
@@ -29,2 +28,3 @@ | ||
const TW_HOSTNAME_SUFFIX = ".ipfscdn.io"; | ||
const TW_STAGINGHOSTNAME_SUFFIX = ".thirdwebstorage-staging.com"; | ||
const TW_GATEWAY_URLS = [`https://{clientId}${TW_HOSTNAME_SUFFIX}/ipfs/{cid}/{path}`]; | ||
@@ -34,7 +34,13 @@ | ||
* @internal | ||
* @param url | ||
* @param url - the url to check | ||
* @returns | ||
*/ | ||
function isTwGatewayUrl(url) { | ||
return new URL(url).hostname.endsWith(TW_HOSTNAME_SUFFIX); | ||
const hostname = new URL(url).hostname; | ||
const isProd = hostname.endsWith(TW_HOSTNAME_SUFFIX); | ||
if (isProd) { | ||
return true; | ||
} | ||
// fall back to also handle staging urls | ||
return hostname.endsWith(TW_STAGINGHOSTNAME_SUFFIX); | ||
} | ||
@@ -76,3 +82,3 @@ const PUBLIC_GATEWAY_URLS = ["https://{cid}.ipfs.cf-ipfs.com/{path}", "https://{cid}.ipfs.dweb.link/{path}", "https://ipfs.io/ipfs/{cid}/{path}", "https://cloudflare-ipfs.com/ipfs/{cid}/{path}", "https://{cid}.ipfs.w3s.link/{path}", "https://w3s.link/ipfs/{cid}/{path}", "https://nftstorage.link/ipfs/{cid}/{path}", "https://gateway.pinata.cloud/ipfs/{cid}/{path}"]; | ||
*/ | ||
function getGatewayUrlForCid(gatewayUrl, cid) { | ||
function getGatewayUrlForCid(gatewayUrl, cid, clientId) { | ||
const parts = cid.split("/"); | ||
@@ -96,2 +102,9 @@ const hash = convertCidToV1(parts[0]); | ||
} | ||
// if the URL contains the {clientId} token, replace it with the client ID | ||
if (gatewayUrl.includes("{clientId}")) { | ||
if (!clientId) { | ||
throw new Error("Cannot use {clientId} in gateway URL without providing a client ID"); | ||
} | ||
url = url.replace("{clientId}", clientId); | ||
} | ||
return url; | ||
@@ -118,6 +131,3 @@ } | ||
} | ||
// this is on purpose because we're using the crypto module only in node | ||
// eslint-disable-next-line @typescript-eslint/no-var-requires | ||
const crypto = require("crypto"); | ||
const hashedSecretKey = crypto.createHash("sha256").update(secretKey).digest("hex"); | ||
const hashedSecretKey = crypto.sha256HexSync(secretKey); | ||
const derivedClientId = hashedSecretKey.slice(0, 32); | ||
@@ -261,2 +271,3 @@ return url.replace("{clientId}", derivedClientId); | ||
let index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; | ||
let clientId = arguments.length > 3 ? arguments[3] : undefined; | ||
const scheme = Object.keys(gatewayUrls).find(s => uri.startsWith(s)); | ||
@@ -271,3 +282,9 @@ const schemeGatewayUrls = scheme ? gatewayUrls[scheme] : []; | ||
const path = uri.replace(scheme, ""); | ||
return getGatewayUrlForCid(schemeGatewayUrls[index], path); | ||
try { | ||
const gatewayUrl = getGatewayUrlForCid(schemeGatewayUrls[index], path, clientId); | ||
return gatewayUrl; | ||
} catch (err) { | ||
console.warn(`The IPFS uri: ${path} is not valid.`); | ||
return undefined; | ||
} | ||
} | ||
@@ -303,5 +320,5 @@ | ||
*/ | ||
function replaceObjectSchemesWithGatewayUrls(data, gatewayUrls) { | ||
function replaceObjectSchemesWithGatewayUrls(data, gatewayUrls, clientId) { | ||
if (typeof data === "string") { | ||
return replaceSchemeWithGatewayUrl(data, gatewayUrls); | ||
return replaceSchemeWithGatewayUrl(data, gatewayUrls, 0, clientId); | ||
} | ||
@@ -316,7 +333,7 @@ if (typeof data === "object") { | ||
if (Array.isArray(data)) { | ||
return data.map(entry => replaceObjectSchemesWithGatewayUrls(entry, gatewayUrls)); | ||
return data.map(entry => replaceObjectSchemesWithGatewayUrls(entry, gatewayUrls, clientId)); | ||
} | ||
return Object.fromEntries(Object.entries(data).map(_ref2 => { | ||
let [key, value] = _ref2; | ||
return [key, replaceObjectSchemesWithGatewayUrls(value, gatewayUrls)]; | ||
return [key, replaceObjectSchemesWithGatewayUrls(value, gatewayUrls, clientId)]; | ||
})); | ||
@@ -378,2 +395,72 @@ } | ||
var pkg = { | ||
name: "@thirdweb-dev/storage", | ||
version: "2.0.5", | ||
main: "dist/thirdweb-dev-storage.cjs.js", | ||
module: "dist/thirdweb-dev-storage.esm.js", | ||
exports: { | ||
".": { | ||
module: "./dist/thirdweb-dev-storage.esm.js", | ||
"default": "./dist/thirdweb-dev-storage.cjs.js" | ||
}, | ||
"./package.json": "./package.json" | ||
}, | ||
repository: "https://github.com/thirdweb-dev/js/tree/main/packages/storage", | ||
author: "thirdweb eng <eng@thirdweb.com>", | ||
license: "Apache-2.0", | ||
sideEffects: false, | ||
scripts: { | ||
format: "prettier --write 'src/**/*'", | ||
lint: "eslint src/ && bunx publint --strict --level warning", | ||
fix: "eslint src/ --fix", | ||
"generate-docs": "api-extractor run --local && api-documenter markdown -i ./temp -o ./docs", | ||
clean: "rm -rf dist/", | ||
build: "tsc && preconstruct build", | ||
"test:all": "NODE_ENV=test SWC_NODE_PROJECT=./tsconfig.test.json mocha --timeout 30000 --parallel './test/**/*.test.ts'", | ||
test: "pnpm test:all", | ||
"test:single": "NODE_ENV=test SWC_NODE_PROJECT=./tsconfig.test.json mocha --timeout 30000", | ||
push: "yalc push", | ||
typedoc: "rimraf typedoc && node typedoc.js" | ||
}, | ||
files: [ | ||
"dist/" | ||
], | ||
preconstruct: { | ||
exports: true | ||
}, | ||
devDependencies: { | ||
"@babel/preset-env": "^7.22.9", | ||
"@babel/preset-typescript": "^7.22.5", | ||
"@microsoft/api-documenter": "^7.22.30", | ||
"@microsoft/api-extractor": "^7.36.3", | ||
"@microsoft/tsdoc": "^0.14.1", | ||
"@preconstruct/cli": "2.7.0", | ||
"@swc-node/register": "^1.6.6", | ||
"@thirdweb-dev/tsconfig": "workspace:*", | ||
"@types/chai": "^4.3.5", | ||
"@types/mocha": "^10.0.0", | ||
"@types/uuid": "^9.0.5", | ||
"@typescript-eslint/eslint-plugin": "^6.2.0", | ||
"@typescript-eslint/parser": "^6.2.0", | ||
chai: "^4.3.6", | ||
eslint: "^8.45.0", | ||
"eslint-config-thirdweb": "workspace:*", | ||
"eslint-plugin-tsdoc": "^0.2.16", | ||
esm: "^3.2.25", | ||
mocha: "^10.2.0", | ||
rimraf: "^3.0.2", | ||
typedoc: "^0.25.2", | ||
typescript: "^5.1.6" | ||
}, | ||
dependencies: { | ||
"cid-tool": "^3.0.0", | ||
"form-data": "^4.0.0", | ||
uuid: "^9.0.1", | ||
"@thirdweb-dev/crypto": "workspace:*" | ||
}, | ||
engines: { | ||
node: ">=18" | ||
} | ||
}; | ||
/** | ||
@@ -399,17 +486,16 @@ * Default downloader used - handles downloading from all schemes specified in the gateway URLs configuration. | ||
class StorageDownloader { | ||
DEFAULT_TIMEOUT_IN_SECONDS = 60; | ||
DEFAULT_MAX_RETRIES = 3; | ||
constructor(options) { | ||
this.secretKey = options.secretKey; | ||
this.clientId = options.clientId; | ||
this.authToken = null; | ||
const authTokenExists = typeof globalThis !== "undefined" && "AUTH_TOKEN" in globalThis; | ||
if (authTokenExists) { | ||
this.authToken = globalThis.AUTH_TOKEN; | ||
} | ||
this.defaultTimeout = options.timeoutInSeconds || this.DEFAULT_TIMEOUT_IN_SECONDS; | ||
} | ||
async download(uri, gatewayUrls) { | ||
let attempts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; | ||
if (attempts > 3) { | ||
async download(uri, gatewayUrls, options) { | ||
let attempts = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; | ||
const maxRetries = options?.maxRetries || this.DEFAULT_MAX_RETRIES; | ||
if (attempts > maxRetries) { | ||
console.error("[FAILED_TO_DOWNLOAD_ERROR] Failed to download from URI - too many attempts failed."); | ||
// return a 404 response to avoid retrying | ||
return new fetch.Response(JSON.stringify({ | ||
return new Response(JSON.stringify({ | ||
error: "Not Found" | ||
@@ -425,7 +511,7 @@ }), { | ||
// Replace recognized scheme with the highest priority gateway URL that hasn't already been attempted | ||
let resolvedUri = replaceSchemeWithGatewayUrl(uri, gatewayUrls, attempts); | ||
let resolvedUri = replaceSchemeWithGatewayUrl(uri, gatewayUrls, attempts, this.clientId); | ||
// If every gateway URL we know about for the designated scheme has been tried (via recursion) and failed, throw an error | ||
if (!resolvedUri) { | ||
console.error("[FAILED_TO_DOWNLOAD_ERROR] Unable to download from URI - all gateway URLs failed to respond."); | ||
return new fetch.Response(JSON.stringify({ | ||
return new Response(JSON.stringify({ | ||
error: "Not Found" | ||
@@ -441,9 +527,5 @@ }), { | ||
} | ||
let headers; | ||
let headers = {}; | ||
if (isTwGatewayUrl(resolvedUri)) { | ||
if (this.authToken) { | ||
headers = { | ||
Authorization: `Bearer ${this.authToken}` | ||
}; | ||
} | ||
const bundleId = typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis ? globalThis.APP_BUNDLE_ID : undefined; | ||
if (this.secretKey) { | ||
@@ -454,17 +536,33 @@ headers = { | ||
} else if (this.clientId) { | ||
if (typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis) { | ||
resolvedUri = resolvedUri + `?bundleId=${globalThis.APP_BUNDLE_ID}`; | ||
if (!resolvedUri.includes("bundleId")) { | ||
resolvedUri = resolvedUri + (bundleId ? `?bundleId=${bundleId}` : ""); | ||
} | ||
headers["x-client-Id"] = this.clientId; | ||
} | ||
// if we have a authorization token on global context then add that to the headers | ||
if (typeof globalThis !== "undefined" && "TW_AUTH_TOKEN" in globalThis && typeof globalThis.TW_AUTH_TOKEN === "string") { | ||
headers = { | ||
"x-client-Id": this.clientId | ||
...headers, | ||
authorization: `Bearer ${globalThis.TW_AUTH_TOKEN}` | ||
}; | ||
} | ||
if (typeof globalThis !== "undefined" && "TW_CLI_AUTH_TOKEN" in globalThis && typeof globalThis.TW_CLI_AUTH_TOKEN === "string") { | ||
headers = { | ||
...headers, | ||
authorization: `Bearer ${globalThis.TW_CLI_AUTH_TOKEN}` | ||
}; | ||
headers["x-authorize-wallet"] = "true"; | ||
} | ||
headers["x-sdk-version"] = pkg.version; | ||
headers["x-sdk-name"] = pkg.name; | ||
headers["x-sdk-platform"] = bundleId ? "react-native" : isBrowser() ? window.bridge !== undefined ? "webGL" : "browser" : "node"; | ||
} | ||
if (isTooManyRequests(resolvedUri)) { | ||
// skip the request if we're getting too many request error from the gateway | ||
return this.download(uri, gatewayUrls, attempts + 1); | ||
return this.download(uri, gatewayUrls, options, attempts + 1); | ||
} | ||
const controller = new AbortController(); | ||
const timeout = setTimeout(() => controller.abort(), 5000); | ||
const resOrErr = await fetch__default["default"](resolvedUri, { | ||
const timeoutInSeconds = options?.timeoutInSeconds || this.defaultTimeout; | ||
const timeout = setTimeout(() => controller.abort(), timeoutInSeconds * 1000); | ||
const resOrErr = await fetch(resolvedUri, { | ||
headers, | ||
@@ -479,3 +577,3 @@ signal: controller.signal | ||
// early exit if we don't have a status code | ||
return this.download(uri, gatewayUrls, attempts + 1); | ||
throw new Error(`Request timed out after ${timeoutInSeconds} seconds. ${isTwGatewayUrl(resolvedUri) ? "You can update the timeoutInSeconds option to increase the timeout." : "You're using a public IPFS gateway, pass in a clientId or secretKey for a reliable IPFS gateway."}`); | ||
} | ||
@@ -491,3 +589,3 @@ | ||
// Since the current gateway failed, recursively try the next one we know about | ||
return this.download(uri, gatewayUrls, attempts + 1); | ||
return this.download(uri, gatewayUrls, options, attempts + 1); | ||
} | ||
@@ -510,6 +608,8 @@ if (resOrErr.status === 410) { | ||
// 5xx - Server Errors | ||
if (resOrErr.status !== 408 && resOrErr.status !== 429 && resOrErr.status < 500) ; | ||
if (resOrErr.status !== 408 && resOrErr.status !== 429 && resOrErr.status < 500) { | ||
return resOrErr; | ||
} | ||
// Since the current gateway failed, recursively try the next one we know about | ||
return this.download(uri, gatewayUrls, attempts + 1); | ||
return this.download(uri, gatewayUrls, options, attempts + 1); | ||
} | ||
@@ -571,9 +671,5 @@ } | ||
this.uploadWithGatewayUrl = options?.uploadWithGatewayUrl || false; | ||
this.uploadServerUrl = options?.uploadServerUrl || TW_UPLOAD_SERVER_URL; | ||
this.clientId = options?.clientId; | ||
this.secretKey = options?.secretKey; | ||
this.authToken = null; | ||
const authTokenExists = typeof globalThis !== "undefined" && "AUTH_TOKEN" in globalThis; | ||
if (authTokenExists) { | ||
this.authToken = globalThis.AUTH_TOKEN; | ||
} | ||
} | ||
@@ -721,3 +817,3 @@ async uploadBatch(data, options) { | ||
} else { | ||
return resolve(fileNames.map(name => `ipfs://${cid}/${name}`)); | ||
return resolve(fileNames.map(n => `ipfs://${cid}/${n}`)); | ||
} | ||
@@ -731,14 +827,7 @@ } | ||
if (xhr.readyState !== 0 && xhr.readyState !== 4 || xhr.status === 0) { | ||
return reject(new Error("This looks like a network error, the endpoint might be blocked by an internet provider or a firewall.")); | ||
return reject(new Error("Upload failed due to a network error.")); | ||
} | ||
return reject(new Error("Unknown upload error occured")); | ||
}); | ||
xhr.open("POST", `${TW_UPLOAD_SERVER_URL}/ipfs/upload`); | ||
if (this.secretKey && this.clientId) { | ||
throw new Error("Cannot use both secret key and client ID. Please use secretKey for server-side applications and clientId for client-side applications."); | ||
} | ||
console.log("AuthToken from globalThis", this.authToken); | ||
if (this.authToken) { | ||
xhr.setRequestHeader("Authorization", `Bearer ${this.authToken}`); | ||
} | ||
xhr.open("POST", `${this.uploadServerUrl}/ipfs/upload`); | ||
if (this.secretKey) { | ||
@@ -749,2 +838,20 @@ xhr.setRequestHeader("x-secret-key", this.secretKey); | ||
} | ||
const bundleId = typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis ? globalThis.APP_BUNDLE_ID : undefined; | ||
if (bundleId) { | ||
xhr.setRequestHeader("x-bundle-id", bundleId); | ||
} | ||
xhr.setRequestHeader("x-sdk-version", pkg.version); | ||
xhr.setRequestHeader("x-sdk-name", pkg.name); | ||
xhr.setRequestHeader("x-sdk-platform", bundleId ? "react-native" : isBrowser() ? window.bridge !== undefined ? "webGL" : "browser" : "node"); | ||
// if we have a authorization token on global context then add that to the headers, this is for the dashboard. | ||
if (typeof globalThis !== "undefined" && "TW_AUTH_TOKEN" in globalThis && typeof globalThis.TW_AUTH_TOKEN === "string") { | ||
xhr.setRequestHeader("authorization", `Bearer ${globalThis.TW_AUTH_TOKEN}`); | ||
} | ||
// CLI auth token | ||
if (typeof globalThis !== "undefined" && "TW_CLI_AUTH_TOKEN" in globalThis && typeof globalThis.TW_CLI_AUTH_TOKEN === "string") { | ||
xhr.setRequestHeader("authorization", `Bearer ${globalThis.TW_CLI_AUTH_TOKEN}`); | ||
xhr.setRequestHeader("x-authorize-wallet", `true`); | ||
} | ||
xhr.send(form); | ||
@@ -757,9 +864,3 @@ }); | ||
} | ||
if (this.secretKey && this.clientId) { | ||
throw new Error("Cannot use both secret key and client ID. Please use secretKey for server-side applications and clientId for client-side applications."); | ||
} | ||
const headers = {}; | ||
if (this.authToken) { | ||
headers["Authorization"] = `Bearer ${this.authToken}`; | ||
} | ||
if (this.secretKey) { | ||
@@ -770,3 +871,19 @@ headers["x-secret-key"] = this.secretKey; | ||
} | ||
const res = await fetch__default["default"](`${TW_UPLOAD_SERVER_URL}/ipfs/upload`, { | ||
// if we have a bundle id on global context then add that to the headers | ||
if (typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis) { | ||
headers["x-bundle-id"] = globalThis.APP_BUNDLE_ID; | ||
} | ||
// if we have a authorization token on global context then add that to the headers, this is for the dashboard. | ||
if (typeof globalThis !== "undefined" && "TW_AUTH_TOKEN" in globalThis && typeof globalThis.TW_AUTH_TOKEN === "string") { | ||
headers["authorization"] = `Bearer ${globalThis.TW_AUTH_TOKEN}`; | ||
} | ||
// CLI auth token | ||
if (typeof globalThis !== "undefined" && "TW_CLI_AUTH_TOKEN" in globalThis && typeof globalThis.TW_CLI_AUTH_TOKEN === "string") { | ||
headers["authorization"] = `Bearer ${globalThis.TW_CLI_AUTH_TOKEN}`; | ||
headers["x-authorize-wallet"] = "true"; | ||
} | ||
const res = await fetch(`${this.uploadServerUrl}/ipfs/upload`, { | ||
method: "POST", | ||
@@ -779,10 +896,12 @@ headers: { | ||
}); | ||
const body = await res.json(); | ||
if (!res.ok) { | ||
console.warn(body); | ||
throw new Error("Failed to upload files to IPFS"); | ||
if (res.status === 401) { | ||
throw new Error("Unauthorized - You don't have permission to use this service."); | ||
} | ||
throw new Error(`Failed to upload files to IPFS - ${res.status} - ${res.statusText} - ${await res.text()}`); | ||
} | ||
const body = await res.json(); | ||
const cid = body.IpfsHash; | ||
if (!cid) { | ||
throw new Error("Failed to upload files to IPFS"); | ||
throw new Error("Failed to upload files to IPFS - Bad CID"); | ||
} | ||
@@ -835,3 +954,4 @@ if (options?.uploadWithoutDirectory) { | ||
clientId: options?.clientId, | ||
secretKey: options?.secretKey | ||
secretKey: options?.secretKey, | ||
uploadServerUrl: options?.uploadServerUrl | ||
}); | ||
@@ -843,2 +963,3 @@ this.downloader = options?.downloader || new StorageDownloader({ | ||
this.gatewayUrls = prepareGatewayUrls(parseGatewayUrls(options?.gatewayUrls), options?.clientId, options?.secretKey); | ||
this.clientId = options?.clientId; | ||
} | ||
@@ -860,3 +981,3 @@ | ||
resolveScheme(url) { | ||
return replaceSchemeWithGatewayUrl(url, this.gatewayUrls); | ||
return replaceSchemeWithGatewayUrl(url, this.gatewayUrls, 0, this.clientId); | ||
} | ||
@@ -876,4 +997,4 @@ | ||
*/ | ||
async download(url) { | ||
return this.downloader.download(url, this.gatewayUrls); | ||
async download(url, options) { | ||
return this.downloader.download(url, this.gatewayUrls, options); | ||
} | ||
@@ -894,8 +1015,8 @@ | ||
*/ | ||
async downloadJSON(url) { | ||
const res = await this.download(url); | ||
async downloadJSON(url, options) { | ||
const res = await this.download(url, options); | ||
// If we get a JSON object, recursively replace any schemes with gatewayUrls | ||
const json = await res.json(); | ||
return replaceObjectSchemesWithGatewayUrls(json, this.gatewayUrls); | ||
return replaceObjectSchemesWithGatewayUrls(json, this.gatewayUrls, this.clientId); | ||
} | ||
@@ -999,3 +1120,3 @@ | ||
// Ex: used for Solana, where services don't resolve schemes for you, so URLs must be usable by default | ||
cleaned = replaceObjectSchemesWithGatewayUrls(cleaned, this.gatewayUrls); | ||
cleaned = replaceObjectSchemesWithGatewayUrls(cleaned, this.gatewayUrls, this.clientId); | ||
} | ||
@@ -1002,0 +1123,0 @@ return cleaned; |
import CIDTool from 'cid-tool'; | ||
import fetch, { Response } from 'cross-fetch'; | ||
import { sha256HexSync } from '@thirdweb-dev/crypto'; | ||
import FormData from 'form-data'; | ||
@@ -17,2 +17,3 @@ import { v4 } from 'uuid'; | ||
const TW_HOSTNAME_SUFFIX = ".ipfscdn.io"; | ||
const TW_STAGINGHOSTNAME_SUFFIX = ".thirdwebstorage-staging.com"; | ||
const TW_GATEWAY_URLS = [`https://{clientId}${TW_HOSTNAME_SUFFIX}/ipfs/{cid}/{path}`]; | ||
@@ -22,7 +23,13 @@ | ||
* @internal | ||
* @param url | ||
* @param url - the url to check | ||
* @returns | ||
*/ | ||
function isTwGatewayUrl(url) { | ||
return new URL(url).hostname.endsWith(TW_HOSTNAME_SUFFIX); | ||
const hostname = new URL(url).hostname; | ||
const isProd = hostname.endsWith(TW_HOSTNAME_SUFFIX); | ||
if (isProd) { | ||
return true; | ||
} | ||
// fall back to also handle staging urls | ||
return hostname.endsWith(TW_STAGINGHOSTNAME_SUFFIX); | ||
} | ||
@@ -64,3 +71,3 @@ const PUBLIC_GATEWAY_URLS = ["https://{cid}.ipfs.cf-ipfs.com/{path}", "https://{cid}.ipfs.dweb.link/{path}", "https://ipfs.io/ipfs/{cid}/{path}", "https://cloudflare-ipfs.com/ipfs/{cid}/{path}", "https://{cid}.ipfs.w3s.link/{path}", "https://w3s.link/ipfs/{cid}/{path}", "https://nftstorage.link/ipfs/{cid}/{path}", "https://gateway.pinata.cloud/ipfs/{cid}/{path}"]; | ||
*/ | ||
function getGatewayUrlForCid(gatewayUrl, cid) { | ||
function getGatewayUrlForCid(gatewayUrl, cid, clientId) { | ||
const parts = cid.split("/"); | ||
@@ -84,2 +91,9 @@ const hash = convertCidToV1(parts[0]); | ||
} | ||
// if the URL contains the {clientId} token, replace it with the client ID | ||
if (gatewayUrl.includes("{clientId}")) { | ||
if (!clientId) { | ||
throw new Error("Cannot use {clientId} in gateway URL without providing a client ID"); | ||
} | ||
url = url.replace("{clientId}", clientId); | ||
} | ||
return url; | ||
@@ -106,6 +120,3 @@ } | ||
} | ||
// this is on purpose because we're using the crypto module only in node | ||
// eslint-disable-next-line @typescript-eslint/no-var-requires | ||
const crypto = require("crypto"); | ||
const hashedSecretKey = crypto.createHash("sha256").update(secretKey).digest("hex"); | ||
const hashedSecretKey = sha256HexSync(secretKey); | ||
const derivedClientId = hashedSecretKey.slice(0, 32); | ||
@@ -249,2 +260,3 @@ return url.replace("{clientId}", derivedClientId); | ||
let index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; | ||
let clientId = arguments.length > 3 ? arguments[3] : undefined; | ||
const scheme = Object.keys(gatewayUrls).find(s => uri.startsWith(s)); | ||
@@ -259,3 +271,9 @@ const schemeGatewayUrls = scheme ? gatewayUrls[scheme] : []; | ||
const path = uri.replace(scheme, ""); | ||
return getGatewayUrlForCid(schemeGatewayUrls[index], path); | ||
try { | ||
const gatewayUrl = getGatewayUrlForCid(schemeGatewayUrls[index], path, clientId); | ||
return gatewayUrl; | ||
} catch (err) { | ||
console.warn(`The IPFS uri: ${path} is not valid.`); | ||
return undefined; | ||
} | ||
} | ||
@@ -291,5 +309,5 @@ | ||
*/ | ||
function replaceObjectSchemesWithGatewayUrls(data, gatewayUrls) { | ||
function replaceObjectSchemesWithGatewayUrls(data, gatewayUrls, clientId) { | ||
if (typeof data === "string") { | ||
return replaceSchemeWithGatewayUrl(data, gatewayUrls); | ||
return replaceSchemeWithGatewayUrl(data, gatewayUrls, 0, clientId); | ||
} | ||
@@ -304,7 +322,7 @@ if (typeof data === "object") { | ||
if (Array.isArray(data)) { | ||
return data.map(entry => replaceObjectSchemesWithGatewayUrls(entry, gatewayUrls)); | ||
return data.map(entry => replaceObjectSchemesWithGatewayUrls(entry, gatewayUrls, clientId)); | ||
} | ||
return Object.fromEntries(Object.entries(data).map(_ref2 => { | ||
let [key, value] = _ref2; | ||
return [key, replaceObjectSchemesWithGatewayUrls(value, gatewayUrls)]; | ||
return [key, replaceObjectSchemesWithGatewayUrls(value, gatewayUrls, clientId)]; | ||
})); | ||
@@ -366,2 +384,72 @@ } | ||
var pkg = { | ||
name: "@thirdweb-dev/storage", | ||
version: "2.0.5", | ||
main: "dist/thirdweb-dev-storage.cjs.js", | ||
module: "dist/thirdweb-dev-storage.esm.js", | ||
exports: { | ||
".": { | ||
module: "./dist/thirdweb-dev-storage.esm.js", | ||
"default": "./dist/thirdweb-dev-storage.cjs.js" | ||
}, | ||
"./package.json": "./package.json" | ||
}, | ||
repository: "https://github.com/thirdweb-dev/js/tree/main/packages/storage", | ||
author: "thirdweb eng <eng@thirdweb.com>", | ||
license: "Apache-2.0", | ||
sideEffects: false, | ||
scripts: { | ||
format: "prettier --write 'src/**/*'", | ||
lint: "eslint src/ && bunx publint --strict --level warning", | ||
fix: "eslint src/ --fix", | ||
"generate-docs": "api-extractor run --local && api-documenter markdown -i ./temp -o ./docs", | ||
clean: "rm -rf dist/", | ||
build: "tsc && preconstruct build", | ||
"test:all": "NODE_ENV=test SWC_NODE_PROJECT=./tsconfig.test.json mocha --timeout 30000 --parallel './test/**/*.test.ts'", | ||
test: "pnpm test:all", | ||
"test:single": "NODE_ENV=test SWC_NODE_PROJECT=./tsconfig.test.json mocha --timeout 30000", | ||
push: "yalc push", | ||
typedoc: "rimraf typedoc && node typedoc.js" | ||
}, | ||
files: [ | ||
"dist/" | ||
], | ||
preconstruct: { | ||
exports: true | ||
}, | ||
devDependencies: { | ||
"@babel/preset-env": "^7.22.9", | ||
"@babel/preset-typescript": "^7.22.5", | ||
"@microsoft/api-documenter": "^7.22.30", | ||
"@microsoft/api-extractor": "^7.36.3", | ||
"@microsoft/tsdoc": "^0.14.1", | ||
"@preconstruct/cli": "2.7.0", | ||
"@swc-node/register": "^1.6.6", | ||
"@thirdweb-dev/tsconfig": "workspace:*", | ||
"@types/chai": "^4.3.5", | ||
"@types/mocha": "^10.0.0", | ||
"@types/uuid": "^9.0.5", | ||
"@typescript-eslint/eslint-plugin": "^6.2.0", | ||
"@typescript-eslint/parser": "^6.2.0", | ||
chai: "^4.3.6", | ||
eslint: "^8.45.0", | ||
"eslint-config-thirdweb": "workspace:*", | ||
"eslint-plugin-tsdoc": "^0.2.16", | ||
esm: "^3.2.25", | ||
mocha: "^10.2.0", | ||
rimraf: "^3.0.2", | ||
typedoc: "^0.25.2", | ||
typescript: "^5.1.6" | ||
}, | ||
dependencies: { | ||
"cid-tool": "^3.0.0", | ||
"form-data": "^4.0.0", | ||
uuid: "^9.0.1", | ||
"@thirdweb-dev/crypto": "workspace:*" | ||
}, | ||
engines: { | ||
node: ">=18" | ||
} | ||
}; | ||
/** | ||
@@ -387,14 +475,13 @@ * Default downloader used - handles downloading from all schemes specified in the gateway URLs configuration. | ||
class StorageDownloader { | ||
DEFAULT_TIMEOUT_IN_SECONDS = 60; | ||
DEFAULT_MAX_RETRIES = 3; | ||
constructor(options) { | ||
this.secretKey = options.secretKey; | ||
this.clientId = options.clientId; | ||
this.authToken = null; | ||
const authTokenExists = typeof globalThis !== "undefined" && "AUTH_TOKEN" in globalThis; | ||
if (authTokenExists) { | ||
this.authToken = globalThis.AUTH_TOKEN; | ||
} | ||
this.defaultTimeout = options.timeoutInSeconds || this.DEFAULT_TIMEOUT_IN_SECONDS; | ||
} | ||
async download(uri, gatewayUrls) { | ||
let attempts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; | ||
if (attempts > 3) { | ||
async download(uri, gatewayUrls, options) { | ||
let attempts = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; | ||
const maxRetries = options?.maxRetries || this.DEFAULT_MAX_RETRIES; | ||
if (attempts > maxRetries) { | ||
console.error("[FAILED_TO_DOWNLOAD_ERROR] Failed to download from URI - too many attempts failed."); | ||
@@ -413,3 +500,3 @@ // return a 404 response to avoid retrying | ||
// Replace recognized scheme with the highest priority gateway URL that hasn't already been attempted | ||
let resolvedUri = replaceSchemeWithGatewayUrl(uri, gatewayUrls, attempts); | ||
let resolvedUri = replaceSchemeWithGatewayUrl(uri, gatewayUrls, attempts, this.clientId); | ||
// If every gateway URL we know about for the designated scheme has been tried (via recursion) and failed, throw an error | ||
@@ -429,9 +516,5 @@ if (!resolvedUri) { | ||
} | ||
let headers; | ||
let headers = {}; | ||
if (isTwGatewayUrl(resolvedUri)) { | ||
if (this.authToken) { | ||
headers = { | ||
Authorization: `Bearer ${this.authToken}` | ||
}; | ||
} | ||
const bundleId = typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis ? globalThis.APP_BUNDLE_ID : undefined; | ||
if (this.secretKey) { | ||
@@ -442,16 +525,32 @@ headers = { | ||
} else if (this.clientId) { | ||
if (typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis) { | ||
resolvedUri = resolvedUri + `?bundleId=${globalThis.APP_BUNDLE_ID}`; | ||
if (!resolvedUri.includes("bundleId")) { | ||
resolvedUri = resolvedUri + (bundleId ? `?bundleId=${bundleId}` : ""); | ||
} | ||
headers["x-client-Id"] = this.clientId; | ||
} | ||
// if we have a authorization token on global context then add that to the headers | ||
if (typeof globalThis !== "undefined" && "TW_AUTH_TOKEN" in globalThis && typeof globalThis.TW_AUTH_TOKEN === "string") { | ||
headers = { | ||
"x-client-Id": this.clientId | ||
...headers, | ||
authorization: `Bearer ${globalThis.TW_AUTH_TOKEN}` | ||
}; | ||
} | ||
if (typeof globalThis !== "undefined" && "TW_CLI_AUTH_TOKEN" in globalThis && typeof globalThis.TW_CLI_AUTH_TOKEN === "string") { | ||
headers = { | ||
...headers, | ||
authorization: `Bearer ${globalThis.TW_CLI_AUTH_TOKEN}` | ||
}; | ||
headers["x-authorize-wallet"] = "true"; | ||
} | ||
headers["x-sdk-version"] = pkg.version; | ||
headers["x-sdk-name"] = pkg.name; | ||
headers["x-sdk-platform"] = bundleId ? "react-native" : isBrowser() ? window.bridge !== undefined ? "webGL" : "browser" : "node"; | ||
} | ||
if (isTooManyRequests(resolvedUri)) { | ||
// skip the request if we're getting too many request error from the gateway | ||
return this.download(uri, gatewayUrls, attempts + 1); | ||
return this.download(uri, gatewayUrls, options, attempts + 1); | ||
} | ||
const controller = new AbortController(); | ||
const timeout = setTimeout(() => controller.abort(), 5000); | ||
const timeoutInSeconds = options?.timeoutInSeconds || this.defaultTimeout; | ||
const timeout = setTimeout(() => controller.abort(), timeoutInSeconds * 1000); | ||
const resOrErr = await fetch(resolvedUri, { | ||
@@ -467,3 +566,3 @@ headers, | ||
// early exit if we don't have a status code | ||
return this.download(uri, gatewayUrls, attempts + 1); | ||
throw new Error(`Request timed out after ${timeoutInSeconds} seconds. ${isTwGatewayUrl(resolvedUri) ? "You can update the timeoutInSeconds option to increase the timeout." : "You're using a public IPFS gateway, pass in a clientId or secretKey for a reliable IPFS gateway."}`); | ||
} | ||
@@ -479,3 +578,3 @@ | ||
// Since the current gateway failed, recursively try the next one we know about | ||
return this.download(uri, gatewayUrls, attempts + 1); | ||
return this.download(uri, gatewayUrls, options, attempts + 1); | ||
} | ||
@@ -498,6 +597,8 @@ if (resOrErr.status === 410) { | ||
// 5xx - Server Errors | ||
if (resOrErr.status !== 408 && resOrErr.status !== 429 && resOrErr.status < 500) ; | ||
if (resOrErr.status !== 408 && resOrErr.status !== 429 && resOrErr.status < 500) { | ||
return resOrErr; | ||
} | ||
// Since the current gateway failed, recursively try the next one we know about | ||
return this.download(uri, gatewayUrls, attempts + 1); | ||
return this.download(uri, gatewayUrls, options, attempts + 1); | ||
} | ||
@@ -559,9 +660,5 @@ } | ||
this.uploadWithGatewayUrl = options?.uploadWithGatewayUrl || false; | ||
this.uploadServerUrl = options?.uploadServerUrl || TW_UPLOAD_SERVER_URL; | ||
this.clientId = options?.clientId; | ||
this.secretKey = options?.secretKey; | ||
this.authToken = null; | ||
const authTokenExists = typeof globalThis !== "undefined" && "AUTH_TOKEN" in globalThis; | ||
if (authTokenExists) { | ||
this.authToken = globalThis.AUTH_TOKEN; | ||
} | ||
} | ||
@@ -709,3 +806,3 @@ async uploadBatch(data, options) { | ||
} else { | ||
return resolve(fileNames.map(name => `ipfs://${cid}/${name}`)); | ||
return resolve(fileNames.map(n => `ipfs://${cid}/${n}`)); | ||
} | ||
@@ -719,14 +816,7 @@ } | ||
if (xhr.readyState !== 0 && xhr.readyState !== 4 || xhr.status === 0) { | ||
return reject(new Error("This looks like a network error, the endpoint might be blocked by an internet provider or a firewall.")); | ||
return reject(new Error("Upload failed due to a network error.")); | ||
} | ||
return reject(new Error("Unknown upload error occured")); | ||
}); | ||
xhr.open("POST", `${TW_UPLOAD_SERVER_URL}/ipfs/upload`); | ||
if (this.secretKey && this.clientId) { | ||
throw new Error("Cannot use both secret key and client ID. Please use secretKey for server-side applications and clientId for client-side applications."); | ||
} | ||
console.log("AuthToken from globalThis", this.authToken); | ||
if (this.authToken) { | ||
xhr.setRequestHeader("Authorization", `Bearer ${this.authToken}`); | ||
} | ||
xhr.open("POST", `${this.uploadServerUrl}/ipfs/upload`); | ||
if (this.secretKey) { | ||
@@ -737,2 +827,20 @@ xhr.setRequestHeader("x-secret-key", this.secretKey); | ||
} | ||
const bundleId = typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis ? globalThis.APP_BUNDLE_ID : undefined; | ||
if (bundleId) { | ||
xhr.setRequestHeader("x-bundle-id", bundleId); | ||
} | ||
xhr.setRequestHeader("x-sdk-version", pkg.version); | ||
xhr.setRequestHeader("x-sdk-name", pkg.name); | ||
xhr.setRequestHeader("x-sdk-platform", bundleId ? "react-native" : isBrowser() ? window.bridge !== undefined ? "webGL" : "browser" : "node"); | ||
// if we have a authorization token on global context then add that to the headers, this is for the dashboard. | ||
if (typeof globalThis !== "undefined" && "TW_AUTH_TOKEN" in globalThis && typeof globalThis.TW_AUTH_TOKEN === "string") { | ||
xhr.setRequestHeader("authorization", `Bearer ${globalThis.TW_AUTH_TOKEN}`); | ||
} | ||
// CLI auth token | ||
if (typeof globalThis !== "undefined" && "TW_CLI_AUTH_TOKEN" in globalThis && typeof globalThis.TW_CLI_AUTH_TOKEN === "string") { | ||
xhr.setRequestHeader("authorization", `Bearer ${globalThis.TW_CLI_AUTH_TOKEN}`); | ||
xhr.setRequestHeader("x-authorize-wallet", `true`); | ||
} | ||
xhr.send(form); | ||
@@ -745,9 +853,3 @@ }); | ||
} | ||
if (this.secretKey && this.clientId) { | ||
throw new Error("Cannot use both secret key and client ID. Please use secretKey for server-side applications and clientId for client-side applications."); | ||
} | ||
const headers = {}; | ||
if (this.authToken) { | ||
headers["Authorization"] = `Bearer ${this.authToken}`; | ||
} | ||
if (this.secretKey) { | ||
@@ -758,3 +860,19 @@ headers["x-secret-key"] = this.secretKey; | ||
} | ||
const res = await fetch(`${TW_UPLOAD_SERVER_URL}/ipfs/upload`, { | ||
// if we have a bundle id on global context then add that to the headers | ||
if (typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis) { | ||
headers["x-bundle-id"] = globalThis.APP_BUNDLE_ID; | ||
} | ||
// if we have a authorization token on global context then add that to the headers, this is for the dashboard. | ||
if (typeof globalThis !== "undefined" && "TW_AUTH_TOKEN" in globalThis && typeof globalThis.TW_AUTH_TOKEN === "string") { | ||
headers["authorization"] = `Bearer ${globalThis.TW_AUTH_TOKEN}`; | ||
} | ||
// CLI auth token | ||
if (typeof globalThis !== "undefined" && "TW_CLI_AUTH_TOKEN" in globalThis && typeof globalThis.TW_CLI_AUTH_TOKEN === "string") { | ||
headers["authorization"] = `Bearer ${globalThis.TW_CLI_AUTH_TOKEN}`; | ||
headers["x-authorize-wallet"] = "true"; | ||
} | ||
const res = await fetch(`${this.uploadServerUrl}/ipfs/upload`, { | ||
method: "POST", | ||
@@ -767,10 +885,12 @@ headers: { | ||
}); | ||
const body = await res.json(); | ||
if (!res.ok) { | ||
console.warn(body); | ||
throw new Error("Failed to upload files to IPFS"); | ||
if (res.status === 401) { | ||
throw new Error("Unauthorized - You don't have permission to use this service."); | ||
} | ||
throw new Error(`Failed to upload files to IPFS - ${res.status} - ${res.statusText} - ${await res.text()}`); | ||
} | ||
const body = await res.json(); | ||
const cid = body.IpfsHash; | ||
if (!cid) { | ||
throw new Error("Failed to upload files to IPFS"); | ||
throw new Error("Failed to upload files to IPFS - Bad CID"); | ||
} | ||
@@ -823,3 +943,4 @@ if (options?.uploadWithoutDirectory) { | ||
clientId: options?.clientId, | ||
secretKey: options?.secretKey | ||
secretKey: options?.secretKey, | ||
uploadServerUrl: options?.uploadServerUrl | ||
}); | ||
@@ -831,2 +952,3 @@ this.downloader = options?.downloader || new StorageDownloader({ | ||
this.gatewayUrls = prepareGatewayUrls(parseGatewayUrls(options?.gatewayUrls), options?.clientId, options?.secretKey); | ||
this.clientId = options?.clientId; | ||
} | ||
@@ -848,3 +970,3 @@ | ||
resolveScheme(url) { | ||
return replaceSchemeWithGatewayUrl(url, this.gatewayUrls); | ||
return replaceSchemeWithGatewayUrl(url, this.gatewayUrls, 0, this.clientId); | ||
} | ||
@@ -864,4 +986,4 @@ | ||
*/ | ||
async download(url) { | ||
return this.downloader.download(url, this.gatewayUrls); | ||
async download(url, options) { | ||
return this.downloader.download(url, this.gatewayUrls, options); | ||
} | ||
@@ -882,8 +1004,8 @@ | ||
*/ | ||
async downloadJSON(url) { | ||
const res = await this.download(url); | ||
async downloadJSON(url, options) { | ||
const res = await this.download(url, options); | ||
// If we get a JSON object, recursively replace any schemes with gatewayUrls | ||
const json = await res.json(); | ||
return replaceObjectSchemesWithGatewayUrls(json, this.gatewayUrls); | ||
return replaceObjectSchemesWithGatewayUrls(json, this.gatewayUrls, this.clientId); | ||
} | ||
@@ -987,3 +1109,3 @@ | ||
// Ex: used for Solana, where services don't resolve schemes for you, so URLs must be usable by default | ||
cleaned = replaceObjectSchemesWithGatewayUrls(cleaned, this.gatewayUrls); | ||
cleaned = replaceObjectSchemesWithGatewayUrls(cleaned, this.gatewayUrls, this.clientId); | ||
} | ||
@@ -990,0 +1112,0 @@ return cleaned; |
{ | ||
"name": "@thirdweb-dev/storage", | ||
"version": "0.0.0-dev-c5ab0db-20230801204042", | ||
"version": "0.0.0-dev-c5e5452-20231204124728", | ||
"main": "dist/thirdweb-dev-storage.cjs.js", | ||
"module": "dist/thirdweb-dev-storage.esm.js", | ||
"browser": { | ||
"./dist/thirdweb-dev-storage.esm.js": "./dist/thirdweb-dev-storage.browser.esm.js" | ||
}, | ||
"exports": { | ||
".": { | ||
"module": { | ||
"browser": "./dist/thirdweb-dev-storage.browser.esm.js", | ||
"default": "./dist/thirdweb-dev-storage.esm.js" | ||
}, | ||
"module": "./dist/thirdweb-dev-storage.esm.js", | ||
"default": "./dist/thirdweb-dev-storage.cjs.js" | ||
@@ -27,7 +21,3 @@ }, | ||
"preconstruct": { | ||
"exports": { | ||
"envConditions": [ | ||
"browser" | ||
] | ||
} | ||
"exports": true | ||
}, | ||
@@ -45,3 +35,3 @@ "devDependencies": { | ||
"@types/mocha": "^10.0.0", | ||
"@types/uuid": "^9.0.2", | ||
"@types/uuid": "^9.0.5", | ||
"@typescript-eslint/eslint-plugin": "^6.2.0", | ||
@@ -52,4 +42,7 @@ "@typescript-eslint/parser": "^6.2.0", | ||
"eslint-config-thirdweb": "^0.1.6", | ||
"eslint-plugin-tsdoc": "^0.2.16", | ||
"esm": "^3.2.25", | ||
"mocha": "^10.2.0", | ||
"rimraf": "^3.0.2", | ||
"typedoc": "^0.25.2", | ||
"typescript": "^5.1.6" | ||
@@ -59,9 +52,12 @@ }, | ||
"cid-tool": "^3.0.0", | ||
"cross-fetch": "^3.1.8", | ||
"form-data": "^4.0.0", | ||
"uuid": "^9.0.0" | ||
"uuid": "^9.0.1", | ||
"@thirdweb-dev/crypto": "0.2.0" | ||
}, | ||
"engines": { | ||
"node": ">=18" | ||
}, | ||
"scripts": { | ||
"format": "prettier --write 'src/**/*'", | ||
"lint": "eslint src/", | ||
"lint": "eslint src/ && bunx publint --strict --level warning", | ||
"fix": "eslint src/ --fix", | ||
@@ -74,4 +70,5 @@ "generate-docs": "api-extractor run --local && api-documenter markdown -i ./temp -o ./docs", | ||
"test:single": "NODE_ENV=test SWC_NODE_PROJECT=./tsconfig.test.json mocha --timeout 30000", | ||
"push": "yalc push" | ||
"push": "yalc push", | ||
"typedoc": "rimraf typedoc && node typedoc.js" | ||
} | ||
} |
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
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
8
6
163942
22
43
3843
+ Added@thirdweb-dev/crypto@0.2.0
+ Added@noble/hashes@1.7.1(transitive)
+ Added@thirdweb-dev/crypto@0.2.0(transitive)
+ Addedjs-sha3@0.9.3(transitive)
- Removedcross-fetch@^3.1.8
- Removedcross-fetch@3.2.0(transitive)
- Removednode-fetch@2.7.0(transitive)
- Removedtr46@0.0.3(transitive)
- Removedwebidl-conversions@3.0.1(transitive)
- Removedwhatwg-url@5.0.0(transitive)
Updateduuid@^9.0.1