@thirdweb-dev/storage
Advanced tools
Comparing version 0.0.0-dev-ae0f6f2-20230822133010 to 0.0.0-dev-ae85a7ad0f937a26e740cf9f9970835c68ac3841-20240402161005
import { GatewayUrls } from "../types"; | ||
/** | ||
* @internal | ||
* @param url | ||
* @param url - the url to check | ||
* @returns | ||
@@ -6,0 +6,0 @@ */ |
@@ -1,2 +0,2 @@ | ||
import { GatewayUrls, IStorageDownloader, IpfsDownloaderOptions } from "../../types"; | ||
import { GatewayUrls, IStorageDownloader, IpfsDownloaderOptions, SingleDownloadOptions } from "../../types"; | ||
/** | ||
@@ -23,8 +23,9 @@ * Default downloader used - handles downloading from all schemes specified in the gateway URLs configuration. | ||
DEFAULT_TIMEOUT_IN_SECONDS: number; | ||
DEFAULT_MAX_RETRIES: number; | ||
private secretKey?; | ||
private clientId?; | ||
private timeoutInSeconds; | ||
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"; | ||
/** | ||
@@ -68,3 +68,3 @@ * Upload and download files from decentralized storage systems. | ||
*/ | ||
download(url: string): Promise<Response>; | ||
download(url: string, options?: SingleDownloadOptions): Promise<Response>; | ||
/** | ||
@@ -83,3 +83,3 @@ * Downloads JSON data from any URL scheme. | ||
*/ | ||
downloadJSON<TJSON = any>(url: string): Promise<TJSON>; | ||
downloadJSON<TJSON = any>(url: string, options?: SingleDownloadOptions): Promise<TJSON>; | ||
/** | ||
@@ -91,3 +91,3 @@ * Upload arbitrary file or JSON data using the configured decentralized storage system. | ||
* @param options - Options to pass through to the storage uploader class | ||
* @returns - The URI of the uploaded data | ||
* @returns The URI of the uploaded data | ||
* | ||
@@ -112,3 +112,3 @@ * @example | ||
* @param options - Options to pass through to the storage uploader class | ||
* @returns - The URIs of the uploaded data | ||
* @returns The URIs of the uploaded data | ||
* | ||
@@ -115,0 +115,0 @@ * @example |
@@ -12,3 +12,3 @@ /** | ||
*/ | ||
download(url: string, gatewayUrls?: GatewayUrls): Promise<Response>; | ||
download(url: string, gatewayUrls?: GatewayUrls, options?: SingleDownloadOptions): Promise<Response>; | ||
} | ||
@@ -30,6 +30,16 @@ /** | ||
/** | ||
* Optional timeout in seconds for the download request | ||
* 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; | ||
}; | ||
/** | ||
@@ -36,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,3 +7,2 @@ export type ThirdwebStorageOptions<T extends UploadOptions> = { | ||
gatewayUrls?: GatewayUrls | string[]; | ||
downloadTimeoutInSeconds?: number; | ||
uploadServerUrl?: string; | ||
@@ -15,3 +14,3 @@ clientId?: string; | ||
resolveScheme(url: string): string; | ||
download(url: string): Promise<Response>; | ||
download(url: string, options?: SingleDownloadOptions): Promise<Response>; | ||
downloadJSON<TJSON = any>(url: string): Promise<TJSON>; | ||
@@ -18,0 +17,0 @@ upload(data: any, options?: { |
@@ -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); | ||
@@ -34,3 +33,3 @@ | ||
* @internal | ||
* @param url | ||
* @param url - the url to check | ||
* @returns | ||
@@ -129,6 +128,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); | ||
@@ -152,3 +148,3 @@ return url.replace("{clientId}", derivedClientId); | ||
function convertCidToV1(cid) { | ||
let normalized; | ||
let normalized = ''; | ||
try { | ||
@@ -283,3 +279,9 @@ const hash = cid.split("/")[0]; | ||
const path = uri.replace(scheme, ""); | ||
return getGatewayUrlForCid(schemeGatewayUrls[index], path, clientId); | ||
try { | ||
const gatewayUrl = getGatewayUrlForCid(schemeGatewayUrls[index], path, clientId); | ||
return gatewayUrl; | ||
} catch (err) { | ||
console.warn(`The IPFS uri: ${path} is not valid.`); | ||
return undefined; | ||
} | ||
} | ||
@@ -390,14 +392,8 @@ | ||
name: "@thirdweb-dev/storage", | ||
version: "1.2.6", | ||
version: "2.0.12", | ||
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" | ||
@@ -407,3 +403,3 @@ }, | ||
}, | ||
repository: "https://github.com/thirdweb-dev/js/tree/main/packages/storage", | ||
repository: "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/storage", | ||
author: "thirdweb eng <eng@thirdweb.com>", | ||
@@ -414,11 +410,9 @@ license: "Apache-2.0", | ||
format: "prettier --write 'src/**/*'", | ||
lint: "eslint 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" | ||
test: "vitest", | ||
push: "yalc push", | ||
typedoc: "node scripts/typedoc.mjs" | ||
}, | ||
@@ -429,34 +423,28 @@ files: [ | ||
preconstruct: { | ||
exports: { | ||
envConditions: [ | ||
"browser" | ||
] | ||
} | ||
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", | ||
"@microsoft/api-documenter": "^7.24.1", | ||
"@microsoft/api-extractor": "^7.43.0", | ||
"@microsoft/tsdoc": "^0.14.2", | ||
"@preconstruct/cli": "2.7.0", | ||
"@swc-node/register": "^1.6.6", | ||
"@swc-node/register": "^1.9.0", | ||
"@thirdweb-dev/tsconfig": "workspace:*", | ||
"@types/chai": "^4.3.5", | ||
"@types/mocha": "^10.0.0", | ||
"@types/uuid": "^9.0.2", | ||
"@typescript-eslint/eslint-plugin": "^6.2.0", | ||
"@typescript-eslint/parser": "^6.2.0", | ||
chai: "^4.3.6", | ||
eslint: "^8.45.0", | ||
"@types/uuid": "^9.0.8", | ||
eslint: "^8.57.0", | ||
"eslint-config-thirdweb": "workspace:*", | ||
"eslint-plugin-tsdoc": "^0.2.17", | ||
esm: "^3.2.25", | ||
mocha: "^10.2.0", | ||
typescript: "^5.1.6" | ||
rimraf: "5.0.5", | ||
"typedoc-gen": "workspace:*", | ||
typescript: "5.4.3" | ||
}, | ||
dependencies: { | ||
"@thirdweb-dev/crypto": "workspace:*", | ||
"cid-tool": "^3.0.0", | ||
"cross-fetch": "^3.1.8", | ||
"form-data": "^4.0.0", | ||
uuid: "^9.0.0" | ||
uuid: "^9.0.1" | ||
}, | ||
engines: { | ||
node: ">=18" | ||
} | ||
@@ -466,2 +454,83 @@ }; | ||
/** | ||
* @internal | ||
* | ||
* The code below comes from the package https://github.com/DamonOehlman/detect-browser | ||
*/ | ||
const operatingSystemRules = [["iOS", /iP(hone|od|ad)/], ["Android OS", /Android/], ["BlackBerry OS", /BlackBerry|BB10/], ["Windows Mobile", /IEMobile/], ["Amazon OS", /Kindle/], ["Windows 3.11", /Win16/], ["Windows 95", /(Windows 95)|(Win95)|(Windows_95)/], ["Windows 98", /(Windows 98)|(Win98)/], ["Windows 2000", /(Windows NT 5.0)|(Windows 2000)/], ["Windows XP", /(Windows NT 5.1)|(Windows XP)/], ["Windows Server 2003", /(Windows NT 5.2)/], ["Windows Vista", /(Windows NT 6.0)/], ["Windows 7", /(Windows NT 6.1)/], ["Windows 8", /(Windows NT 6.2)/], ["Windows 8.1", /(Windows NT 6.3)/], ["Windows 10", /(Windows NT 10.0)/], ["Windows ME", /Windows ME/], ["Windows CE", /Windows CE|WinCE|Microsoft Pocket Internet Explorer/], ["Open BSD", /OpenBSD/], ["Sun OS", /SunOS/], ["Chrome OS", /CrOS/], ["Linux", /(Linux)|(X11)/], ["Mac OS", /(Mac_PowerPC)|(Macintosh)/], ["QNX", /QNX/], ["BeOS", /BeOS/], ["OS/2", /OS\/2/]]; | ||
function detectOS(ua) { | ||
for (let ii = 0, count = operatingSystemRules.length; ii < count; ii++) { | ||
const result = operatingSystemRules[ii]; | ||
if (!result) { | ||
continue; | ||
} | ||
const [os, regex] = result; | ||
const match = regex.exec(ua); | ||
if (match) { | ||
return os; | ||
} | ||
} | ||
return null; | ||
} | ||
function getOperatingSystem() { | ||
if (typeof navigator !== "undefined" && navigator.product === "ReactNative") { | ||
return ""; | ||
} else if (typeof window !== "undefined") { | ||
const userAgent = navigator.userAgent; | ||
return detectOS(userAgent) || ""; | ||
} else { | ||
return process.platform; | ||
} | ||
} | ||
function setAnalyticsHeaders(headers) { | ||
const globals = getAnalyticsGlobals(); | ||
headers["x-sdk-version"] = globals.x_sdk_version; | ||
headers["x-sdk-name"] = globals.x_sdk_name; | ||
headers["x-sdk-platform"] = globals.x_sdk_platform; | ||
headers["x-sdk-os"] = globals.x_sdk_os; | ||
if (globals.app_bundle_id) { | ||
headers["x-bundle-id"] = globals.app_bundle_id; | ||
} | ||
} | ||
function setAnalyticsHeadersForXhr(xhr) { | ||
const globals = getAnalyticsGlobals(); | ||
xhr.setRequestHeader("x-sdk-version", globals.x_sdk_version); | ||
xhr.setRequestHeader("x-sdk-os", globals.x_sdk_os); | ||
xhr.setRequestHeader("x-sdk-name", globals.x_sdk_name); | ||
xhr.setRequestHeader("x-sdk-platform", globals.x_sdk_platform); | ||
if (globals.app_bundle_id) { | ||
xhr.setRequestHeader("x-bundle-id", globals.app_bundle_id); | ||
} | ||
} | ||
function getAnalyticsGlobals() { | ||
if (typeof globalThis === "undefined") { | ||
return { | ||
x_sdk_name: pkg.name, | ||
x_sdk_platform: getPlatform(), | ||
x_sdk_version: pkg.version, | ||
x_sdk_os: getOperatingSystem(), | ||
app_bundle_id: undefined | ||
}; | ||
} | ||
if (globalThis.X_SDK_NAME === undefined) { | ||
globalThis.X_SDK_NAME = pkg.name; | ||
globalThis.X_SDK_PLATFORM = getPlatform(); | ||
globalThis.X_SDK_VERSION = pkg.version; | ||
globalThis.X_SDK_OS = getOperatingSystem(); | ||
globalThis.APP_BUNDLE_ID = undefined; | ||
} | ||
return { | ||
x_sdk_name: globalThis.X_SDK_NAME, | ||
x_sdk_platform: globalThis.X_SDK_PLATFORM, | ||
x_sdk_version: globalThis.X_SDK_VERSION, | ||
x_sdk_os: globalThis.X_SDK_OS, | ||
app_bundle_id: globalThis.APP_BUNDLE_ID || "" // if react, this will be empty | ||
}; | ||
} | ||
function getPlatform() { | ||
return typeof navigator !== "undefined" && navigator.product === "ReactNative" ? "mobile" : typeof window !== "undefined" ? "browser" : "node"; | ||
} | ||
/** | ||
* Default downloader used - handles downloading from all schemes specified in the gateway URLs configuration. | ||
@@ -486,14 +555,16 @@ * | ||
class StorageDownloader { | ||
DEFAULT_TIMEOUT_IN_SECONDS = 10; | ||
DEFAULT_TIMEOUT_IN_SECONDS = 60; | ||
DEFAULT_MAX_RETRIES = 3; | ||
constructor(options) { | ||
this.secretKey = options.secretKey; | ||
this.clientId = options.clientId; | ||
this.timeoutInSeconds = options.timeoutInSeconds || this.DEFAULT_TIMEOUT_IN_SECONDS; | ||
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" | ||
@@ -513,3 +584,3 @@ }), { | ||
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" | ||
@@ -527,3 +598,3 @@ }), { | ||
if (isTwGatewayUrl(resolvedUri)) { | ||
const bundleId = typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis ? globalThis.APP_BUNDLE_ID : undefined; | ||
const bundleId = getAnalyticsGlobals().app_bundle_id; | ||
if (this.secretKey) { | ||
@@ -534,3 +605,3 @@ headers = { | ||
} else if (this.clientId) { | ||
if (!resolvedUri.includes("bundleId")) { | ||
if (!resolvedUri.includes("bundleId") && bundleId) { | ||
resolvedUri = resolvedUri + (bundleId ? `?bundleId=${bundleId}` : ""); | ||
@@ -547,13 +618,19 @@ } | ||
} | ||
headers["x-sdk-version"] = pkg.version; | ||
headers["x-sdk-name"] = pkg.name; | ||
headers["x-sdk-platform"] = bundleId ? "react-native" : isBrowser() ? "browser" : "node"; | ||
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"; | ||
} | ||
setAnalyticsHeaders(headers); | ||
} | ||
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(), this.timeoutInSeconds * 1000); | ||
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, | ||
@@ -568,3 +645,3 @@ signal: controller.signal | ||
// early exit if we don't have a status code | ||
throw new Error(`Request timed out after ${this.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."}`); | ||
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."}`); | ||
} | ||
@@ -580,3 +657,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); | ||
} | ||
@@ -604,3 +681,3 @@ if (resOrErr.status === 410) { | ||
// 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); | ||
} | ||
@@ -826,14 +903,14 @@ } | ||
} | ||
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() ? "browser" : "node"); | ||
setAnalyticsHeadersForXhr(xhr); | ||
// if we have a authorization token on global context then add that to the headers | ||
// 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); | ||
@@ -853,12 +930,14 @@ }); | ||
// 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 | ||
// 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}`; | ||
} | ||
const res = await fetch__default["default"](`${this.uploadServerUrl}/ipfs/upload`, { | ||
// 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"; | ||
} | ||
setAnalyticsHeaders(headers); | ||
const res = await fetch(`${this.uploadServerUrl}/ipfs/upload`, { | ||
method: "POST", | ||
@@ -872,4 +951,6 @@ headers: { | ||
if (!res.ok) { | ||
console.warn(await res.text()); | ||
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()}`); | ||
} | ||
@@ -879,3 +960,3 @@ const body = await res.json(); | ||
if (!cid) { | ||
throw new Error("Failed to upload files to IPFS"); | ||
throw new Error("Failed to upload files to IPFS - Bad CID"); | ||
} | ||
@@ -933,4 +1014,3 @@ if (options?.uploadWithoutDirectory) { | ||
secretKey: options?.secretKey, | ||
clientId: options?.clientId, | ||
timeoutInSeconds: options?.downloadTimeoutInSeconds | ||
clientId: options?.clientId | ||
}); | ||
@@ -970,4 +1050,4 @@ this.gatewayUrls = prepareGatewayUrls(parseGatewayUrls(options?.gatewayUrls), options?.clientId, options?.secretKey); | ||
*/ | ||
async download(url) { | ||
return this.downloader.download(url, this.gatewayUrls); | ||
async download(url, options) { | ||
return this.downloader.download(url, this.gatewayUrls, options); | ||
} | ||
@@ -988,4 +1068,4 @@ | ||
*/ | ||
async downloadJSON(url) { | ||
const res = await this.download(url); | ||
async downloadJSON(url, options) { | ||
const res = await this.download(url, options); | ||
@@ -1003,3 +1083,3 @@ // If we get a JSON object, recursively replace any schemes with gatewayUrls | ||
* @param options - Options to pass through to the storage uploader class | ||
* @returns - The URI of the uploaded data | ||
* @returns The URI of the uploaded data | ||
* | ||
@@ -1028,3 +1108,3 @@ * @example | ||
* @param options - Options to pass through to the storage uploader class | ||
* @returns - The URIs of the uploaded data | ||
* @returns The URIs of the uploaded data | ||
* | ||
@@ -1031,0 +1111,0 @@ * @example |
@@ -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); | ||
@@ -34,3 +33,3 @@ | ||
* @internal | ||
* @param url | ||
* @param url - the url to check | ||
* @returns | ||
@@ -129,6 +128,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); | ||
@@ -152,3 +148,3 @@ return url.replace("{clientId}", derivedClientId); | ||
function convertCidToV1(cid) { | ||
let normalized; | ||
let normalized = ''; | ||
try { | ||
@@ -283,3 +279,9 @@ const hash = cid.split("/")[0]; | ||
const path = uri.replace(scheme, ""); | ||
return getGatewayUrlForCid(schemeGatewayUrls[index], path, clientId); | ||
try { | ||
const gatewayUrl = getGatewayUrlForCid(schemeGatewayUrls[index], path, clientId); | ||
return gatewayUrl; | ||
} catch (err) { | ||
console.warn(`The IPFS uri: ${path} is not valid.`); | ||
return undefined; | ||
} | ||
} | ||
@@ -390,14 +392,8 @@ | ||
name: "@thirdweb-dev/storage", | ||
version: "1.2.6", | ||
version: "2.0.12", | ||
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" | ||
@@ -407,3 +403,3 @@ }, | ||
}, | ||
repository: "https://github.com/thirdweb-dev/js/tree/main/packages/storage", | ||
repository: "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/storage", | ||
author: "thirdweb eng <eng@thirdweb.com>", | ||
@@ -414,11 +410,9 @@ license: "Apache-2.0", | ||
format: "prettier --write 'src/**/*'", | ||
lint: "eslint 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" | ||
test: "vitest", | ||
push: "yalc push", | ||
typedoc: "node scripts/typedoc.mjs" | ||
}, | ||
@@ -429,34 +423,28 @@ files: [ | ||
preconstruct: { | ||
exports: { | ||
envConditions: [ | ||
"browser" | ||
] | ||
} | ||
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", | ||
"@microsoft/api-documenter": "^7.24.1", | ||
"@microsoft/api-extractor": "^7.43.0", | ||
"@microsoft/tsdoc": "^0.14.2", | ||
"@preconstruct/cli": "2.7.0", | ||
"@swc-node/register": "^1.6.6", | ||
"@swc-node/register": "^1.9.0", | ||
"@thirdweb-dev/tsconfig": "workspace:*", | ||
"@types/chai": "^4.3.5", | ||
"@types/mocha": "^10.0.0", | ||
"@types/uuid": "^9.0.2", | ||
"@typescript-eslint/eslint-plugin": "^6.2.0", | ||
"@typescript-eslint/parser": "^6.2.0", | ||
chai: "^4.3.6", | ||
eslint: "^8.45.0", | ||
"@types/uuid": "^9.0.8", | ||
eslint: "^8.57.0", | ||
"eslint-config-thirdweb": "workspace:*", | ||
"eslint-plugin-tsdoc": "^0.2.17", | ||
esm: "^3.2.25", | ||
mocha: "^10.2.0", | ||
typescript: "^5.1.6" | ||
rimraf: "5.0.5", | ||
"typedoc-gen": "workspace:*", | ||
typescript: "5.4.3" | ||
}, | ||
dependencies: { | ||
"@thirdweb-dev/crypto": "workspace:*", | ||
"cid-tool": "^3.0.0", | ||
"cross-fetch": "^3.1.8", | ||
"form-data": "^4.0.0", | ||
uuid: "^9.0.0" | ||
uuid: "^9.0.1" | ||
}, | ||
engines: { | ||
node: ">=18" | ||
} | ||
@@ -466,2 +454,83 @@ }; | ||
/** | ||
* @internal | ||
* | ||
* The code below comes from the package https://github.com/DamonOehlman/detect-browser | ||
*/ | ||
const operatingSystemRules = [["iOS", /iP(hone|od|ad)/], ["Android OS", /Android/], ["BlackBerry OS", /BlackBerry|BB10/], ["Windows Mobile", /IEMobile/], ["Amazon OS", /Kindle/], ["Windows 3.11", /Win16/], ["Windows 95", /(Windows 95)|(Win95)|(Windows_95)/], ["Windows 98", /(Windows 98)|(Win98)/], ["Windows 2000", /(Windows NT 5.0)|(Windows 2000)/], ["Windows XP", /(Windows NT 5.1)|(Windows XP)/], ["Windows Server 2003", /(Windows NT 5.2)/], ["Windows Vista", /(Windows NT 6.0)/], ["Windows 7", /(Windows NT 6.1)/], ["Windows 8", /(Windows NT 6.2)/], ["Windows 8.1", /(Windows NT 6.3)/], ["Windows 10", /(Windows NT 10.0)/], ["Windows ME", /Windows ME/], ["Windows CE", /Windows CE|WinCE|Microsoft Pocket Internet Explorer/], ["Open BSD", /OpenBSD/], ["Sun OS", /SunOS/], ["Chrome OS", /CrOS/], ["Linux", /(Linux)|(X11)/], ["Mac OS", /(Mac_PowerPC)|(Macintosh)/], ["QNX", /QNX/], ["BeOS", /BeOS/], ["OS/2", /OS\/2/]]; | ||
function detectOS(ua) { | ||
for (let ii = 0, count = operatingSystemRules.length; ii < count; ii++) { | ||
const result = operatingSystemRules[ii]; | ||
if (!result) { | ||
continue; | ||
} | ||
const [os, regex] = result; | ||
const match = regex.exec(ua); | ||
if (match) { | ||
return os; | ||
} | ||
} | ||
return null; | ||
} | ||
function getOperatingSystem() { | ||
if (typeof navigator !== "undefined" && navigator.product === "ReactNative") { | ||
return ""; | ||
} else if (typeof window !== "undefined") { | ||
const userAgent = navigator.userAgent; | ||
return detectOS(userAgent) || ""; | ||
} else { | ||
return process.platform; | ||
} | ||
} | ||
function setAnalyticsHeaders(headers) { | ||
const globals = getAnalyticsGlobals(); | ||
headers["x-sdk-version"] = globals.x_sdk_version; | ||
headers["x-sdk-name"] = globals.x_sdk_name; | ||
headers["x-sdk-platform"] = globals.x_sdk_platform; | ||
headers["x-sdk-os"] = globals.x_sdk_os; | ||
if (globals.app_bundle_id) { | ||
headers["x-bundle-id"] = globals.app_bundle_id; | ||
} | ||
} | ||
function setAnalyticsHeadersForXhr(xhr) { | ||
const globals = getAnalyticsGlobals(); | ||
xhr.setRequestHeader("x-sdk-version", globals.x_sdk_version); | ||
xhr.setRequestHeader("x-sdk-os", globals.x_sdk_os); | ||
xhr.setRequestHeader("x-sdk-name", globals.x_sdk_name); | ||
xhr.setRequestHeader("x-sdk-platform", globals.x_sdk_platform); | ||
if (globals.app_bundle_id) { | ||
xhr.setRequestHeader("x-bundle-id", globals.app_bundle_id); | ||
} | ||
} | ||
function getAnalyticsGlobals() { | ||
if (typeof globalThis === "undefined") { | ||
return { | ||
x_sdk_name: pkg.name, | ||
x_sdk_platform: getPlatform(), | ||
x_sdk_version: pkg.version, | ||
x_sdk_os: getOperatingSystem(), | ||
app_bundle_id: undefined | ||
}; | ||
} | ||
if (globalThis.X_SDK_NAME === undefined) { | ||
globalThis.X_SDK_NAME = pkg.name; | ||
globalThis.X_SDK_PLATFORM = getPlatform(); | ||
globalThis.X_SDK_VERSION = pkg.version; | ||
globalThis.X_SDK_OS = getOperatingSystem(); | ||
globalThis.APP_BUNDLE_ID = undefined; | ||
} | ||
return { | ||
x_sdk_name: globalThis.X_SDK_NAME, | ||
x_sdk_platform: globalThis.X_SDK_PLATFORM, | ||
x_sdk_version: globalThis.X_SDK_VERSION, | ||
x_sdk_os: globalThis.X_SDK_OS, | ||
app_bundle_id: globalThis.APP_BUNDLE_ID || "" // if react, this will be empty | ||
}; | ||
} | ||
function getPlatform() { | ||
return typeof navigator !== "undefined" && navigator.product === "ReactNative" ? "mobile" : typeof window !== "undefined" ? "browser" : "node"; | ||
} | ||
/** | ||
* Default downloader used - handles downloading from all schemes specified in the gateway URLs configuration. | ||
@@ -486,14 +555,16 @@ * | ||
class StorageDownloader { | ||
DEFAULT_TIMEOUT_IN_SECONDS = 10; | ||
DEFAULT_TIMEOUT_IN_SECONDS = 60; | ||
DEFAULT_MAX_RETRIES = 3; | ||
constructor(options) { | ||
this.secretKey = options.secretKey; | ||
this.clientId = options.clientId; | ||
this.timeoutInSeconds = options.timeoutInSeconds || this.DEFAULT_TIMEOUT_IN_SECONDS; | ||
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" | ||
@@ -513,3 +584,3 @@ }), { | ||
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" | ||
@@ -527,3 +598,3 @@ }), { | ||
if (isTwGatewayUrl(resolvedUri)) { | ||
const bundleId = typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis ? globalThis.APP_BUNDLE_ID : undefined; | ||
const bundleId = getAnalyticsGlobals().app_bundle_id; | ||
if (this.secretKey) { | ||
@@ -534,3 +605,3 @@ headers = { | ||
} else if (this.clientId) { | ||
if (!resolvedUri.includes("bundleId")) { | ||
if (!resolvedUri.includes("bundleId") && bundleId) { | ||
resolvedUri = resolvedUri + (bundleId ? `?bundleId=${bundleId}` : ""); | ||
@@ -547,13 +618,19 @@ } | ||
} | ||
headers["x-sdk-version"] = pkg.version; | ||
headers["x-sdk-name"] = pkg.name; | ||
headers["x-sdk-platform"] = bundleId ? "react-native" : isBrowser() ? "browser" : "node"; | ||
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"; | ||
} | ||
setAnalyticsHeaders(headers); | ||
} | ||
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(), this.timeoutInSeconds * 1000); | ||
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, | ||
@@ -568,3 +645,3 @@ signal: controller.signal | ||
// early exit if we don't have a status code | ||
throw new Error(`Request timed out after ${this.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."}`); | ||
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."}`); | ||
} | ||
@@ -580,3 +657,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); | ||
} | ||
@@ -604,3 +681,3 @@ if (resOrErr.status === 410) { | ||
// 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); | ||
} | ||
@@ -826,14 +903,14 @@ } | ||
} | ||
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() ? "browser" : "node"); | ||
setAnalyticsHeadersForXhr(xhr); | ||
// if we have a authorization token on global context then add that to the headers | ||
// 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); | ||
@@ -853,12 +930,14 @@ }); | ||
// 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 | ||
// 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}`; | ||
} | ||
const res = await fetch__default["default"](`${this.uploadServerUrl}/ipfs/upload`, { | ||
// 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"; | ||
} | ||
setAnalyticsHeaders(headers); | ||
const res = await fetch(`${this.uploadServerUrl}/ipfs/upload`, { | ||
method: "POST", | ||
@@ -872,4 +951,6 @@ headers: { | ||
if (!res.ok) { | ||
console.warn(await res.text()); | ||
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()}`); | ||
} | ||
@@ -879,3 +960,3 @@ const body = await res.json(); | ||
if (!cid) { | ||
throw new Error("Failed to upload files to IPFS"); | ||
throw new Error("Failed to upload files to IPFS - Bad CID"); | ||
} | ||
@@ -933,4 +1014,3 @@ if (options?.uploadWithoutDirectory) { | ||
secretKey: options?.secretKey, | ||
clientId: options?.clientId, | ||
timeoutInSeconds: options?.downloadTimeoutInSeconds | ||
clientId: options?.clientId | ||
}); | ||
@@ -970,4 +1050,4 @@ this.gatewayUrls = prepareGatewayUrls(parseGatewayUrls(options?.gatewayUrls), options?.clientId, options?.secretKey); | ||
*/ | ||
async download(url) { | ||
return this.downloader.download(url, this.gatewayUrls); | ||
async download(url, options) { | ||
return this.downloader.download(url, this.gatewayUrls, options); | ||
} | ||
@@ -988,4 +1068,4 @@ | ||
*/ | ||
async downloadJSON(url) { | ||
const res = await this.download(url); | ||
async downloadJSON(url, options) { | ||
const res = await this.download(url, options); | ||
@@ -1003,3 +1083,3 @@ // If we get a JSON object, recursively replace any schemes with gatewayUrls | ||
* @param options - Options to pass through to the storage uploader class | ||
* @returns - The URI of the uploaded data | ||
* @returns The URI of the uploaded data | ||
* | ||
@@ -1028,3 +1108,3 @@ * @example | ||
* @param options - Options to pass through to the storage uploader class | ||
* @returns - The URIs of the uploaded data | ||
* @returns The URIs of the uploaded data | ||
* | ||
@@ -1031,0 +1111,0 @@ * @example |
import CIDTool from 'cid-tool'; | ||
import fetch, { Response } from 'cross-fetch'; | ||
import { sha256HexSync } from '@thirdweb-dev/crypto'; | ||
import FormData from 'form-data'; | ||
@@ -22,3 +22,3 @@ import { v4 } from 'uuid'; | ||
* @internal | ||
* @param url | ||
* @param url - the url to check | ||
* @returns | ||
@@ -117,6 +117,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); | ||
@@ -140,3 +137,3 @@ return url.replace("{clientId}", derivedClientId); | ||
function convertCidToV1(cid) { | ||
let normalized; | ||
let normalized = ''; | ||
try { | ||
@@ -271,3 +268,9 @@ const hash = cid.split("/")[0]; | ||
const path = uri.replace(scheme, ""); | ||
return getGatewayUrlForCid(schemeGatewayUrls[index], path, clientId); | ||
try { | ||
const gatewayUrl = getGatewayUrlForCid(schemeGatewayUrls[index], path, clientId); | ||
return gatewayUrl; | ||
} catch (err) { | ||
console.warn(`The IPFS uri: ${path} is not valid.`); | ||
return undefined; | ||
} | ||
} | ||
@@ -378,14 +381,8 @@ | ||
name: "@thirdweb-dev/storage", | ||
version: "1.2.6", | ||
version: "2.0.12", | ||
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" | ||
@@ -395,3 +392,3 @@ }, | ||
}, | ||
repository: "https://github.com/thirdweb-dev/js/tree/main/packages/storage", | ||
repository: "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/storage", | ||
author: "thirdweb eng <eng@thirdweb.com>", | ||
@@ -402,11 +399,9 @@ license: "Apache-2.0", | ||
format: "prettier --write 'src/**/*'", | ||
lint: "eslint 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" | ||
test: "vitest", | ||
push: "yalc push", | ||
typedoc: "node scripts/typedoc.mjs" | ||
}, | ||
@@ -417,34 +412,28 @@ files: [ | ||
preconstruct: { | ||
exports: { | ||
envConditions: [ | ||
"browser" | ||
] | ||
} | ||
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", | ||
"@microsoft/api-documenter": "^7.24.1", | ||
"@microsoft/api-extractor": "^7.43.0", | ||
"@microsoft/tsdoc": "^0.14.2", | ||
"@preconstruct/cli": "2.7.0", | ||
"@swc-node/register": "^1.6.6", | ||
"@swc-node/register": "^1.9.0", | ||
"@thirdweb-dev/tsconfig": "workspace:*", | ||
"@types/chai": "^4.3.5", | ||
"@types/mocha": "^10.0.0", | ||
"@types/uuid": "^9.0.2", | ||
"@typescript-eslint/eslint-plugin": "^6.2.0", | ||
"@typescript-eslint/parser": "^6.2.0", | ||
chai: "^4.3.6", | ||
eslint: "^8.45.0", | ||
"@types/uuid": "^9.0.8", | ||
eslint: "^8.57.0", | ||
"eslint-config-thirdweb": "workspace:*", | ||
"eslint-plugin-tsdoc": "^0.2.17", | ||
esm: "^3.2.25", | ||
mocha: "^10.2.0", | ||
typescript: "^5.1.6" | ||
rimraf: "5.0.5", | ||
"typedoc-gen": "workspace:*", | ||
typescript: "5.4.3" | ||
}, | ||
dependencies: { | ||
"@thirdweb-dev/crypto": "workspace:*", | ||
"cid-tool": "^3.0.0", | ||
"cross-fetch": "^3.1.8", | ||
"form-data": "^4.0.0", | ||
uuid: "^9.0.0" | ||
uuid: "^9.0.1" | ||
}, | ||
engines: { | ||
node: ">=18" | ||
} | ||
@@ -454,2 +443,83 @@ }; | ||
/** | ||
* @internal | ||
* | ||
* The code below comes from the package https://github.com/DamonOehlman/detect-browser | ||
*/ | ||
const operatingSystemRules = [["iOS", /iP(hone|od|ad)/], ["Android OS", /Android/], ["BlackBerry OS", /BlackBerry|BB10/], ["Windows Mobile", /IEMobile/], ["Amazon OS", /Kindle/], ["Windows 3.11", /Win16/], ["Windows 95", /(Windows 95)|(Win95)|(Windows_95)/], ["Windows 98", /(Windows 98)|(Win98)/], ["Windows 2000", /(Windows NT 5.0)|(Windows 2000)/], ["Windows XP", /(Windows NT 5.1)|(Windows XP)/], ["Windows Server 2003", /(Windows NT 5.2)/], ["Windows Vista", /(Windows NT 6.0)/], ["Windows 7", /(Windows NT 6.1)/], ["Windows 8", /(Windows NT 6.2)/], ["Windows 8.1", /(Windows NT 6.3)/], ["Windows 10", /(Windows NT 10.0)/], ["Windows ME", /Windows ME/], ["Windows CE", /Windows CE|WinCE|Microsoft Pocket Internet Explorer/], ["Open BSD", /OpenBSD/], ["Sun OS", /SunOS/], ["Chrome OS", /CrOS/], ["Linux", /(Linux)|(X11)/], ["Mac OS", /(Mac_PowerPC)|(Macintosh)/], ["QNX", /QNX/], ["BeOS", /BeOS/], ["OS/2", /OS\/2/]]; | ||
function detectOS(ua) { | ||
for (let ii = 0, count = operatingSystemRules.length; ii < count; ii++) { | ||
const result = operatingSystemRules[ii]; | ||
if (!result) { | ||
continue; | ||
} | ||
const [os, regex] = result; | ||
const match = regex.exec(ua); | ||
if (match) { | ||
return os; | ||
} | ||
} | ||
return null; | ||
} | ||
function getOperatingSystem() { | ||
if (typeof navigator !== "undefined" && navigator.product === "ReactNative") { | ||
return ""; | ||
} else if (typeof window !== "undefined") { | ||
const userAgent = navigator.userAgent; | ||
return detectOS(userAgent) || ""; | ||
} else { | ||
return process.platform; | ||
} | ||
} | ||
function setAnalyticsHeaders(headers) { | ||
const globals = getAnalyticsGlobals(); | ||
headers["x-sdk-version"] = globals.x_sdk_version; | ||
headers["x-sdk-name"] = globals.x_sdk_name; | ||
headers["x-sdk-platform"] = globals.x_sdk_platform; | ||
headers["x-sdk-os"] = globals.x_sdk_os; | ||
if (globals.app_bundle_id) { | ||
headers["x-bundle-id"] = globals.app_bundle_id; | ||
} | ||
} | ||
function setAnalyticsHeadersForXhr(xhr) { | ||
const globals = getAnalyticsGlobals(); | ||
xhr.setRequestHeader("x-sdk-version", globals.x_sdk_version); | ||
xhr.setRequestHeader("x-sdk-os", globals.x_sdk_os); | ||
xhr.setRequestHeader("x-sdk-name", globals.x_sdk_name); | ||
xhr.setRequestHeader("x-sdk-platform", globals.x_sdk_platform); | ||
if (globals.app_bundle_id) { | ||
xhr.setRequestHeader("x-bundle-id", globals.app_bundle_id); | ||
} | ||
} | ||
function getAnalyticsGlobals() { | ||
if (typeof globalThis === "undefined") { | ||
return { | ||
x_sdk_name: pkg.name, | ||
x_sdk_platform: getPlatform(), | ||
x_sdk_version: pkg.version, | ||
x_sdk_os: getOperatingSystem(), | ||
app_bundle_id: undefined | ||
}; | ||
} | ||
if (globalThis.X_SDK_NAME === undefined) { | ||
globalThis.X_SDK_NAME = pkg.name; | ||
globalThis.X_SDK_PLATFORM = getPlatform(); | ||
globalThis.X_SDK_VERSION = pkg.version; | ||
globalThis.X_SDK_OS = getOperatingSystem(); | ||
globalThis.APP_BUNDLE_ID = undefined; | ||
} | ||
return { | ||
x_sdk_name: globalThis.X_SDK_NAME, | ||
x_sdk_platform: globalThis.X_SDK_PLATFORM, | ||
x_sdk_version: globalThis.X_SDK_VERSION, | ||
x_sdk_os: globalThis.X_SDK_OS, | ||
app_bundle_id: globalThis.APP_BUNDLE_ID || "" // if react, this will be empty | ||
}; | ||
} | ||
function getPlatform() { | ||
return typeof navigator !== "undefined" && navigator.product === "ReactNative" ? "mobile" : typeof window !== "undefined" ? "browser" : "node"; | ||
} | ||
/** | ||
* Default downloader used - handles downloading from all schemes specified in the gateway URLs configuration. | ||
@@ -474,11 +544,13 @@ * | ||
class StorageDownloader { | ||
DEFAULT_TIMEOUT_IN_SECONDS = 10; | ||
DEFAULT_TIMEOUT_IN_SECONDS = 60; | ||
DEFAULT_MAX_RETRIES = 3; | ||
constructor(options) { | ||
this.secretKey = options.secretKey; | ||
this.clientId = options.clientId; | ||
this.timeoutInSeconds = options.timeoutInSeconds || this.DEFAULT_TIMEOUT_IN_SECONDS; | ||
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."); | ||
@@ -514,3 +586,3 @@ // return a 404 response to avoid retrying | ||
if (isTwGatewayUrl(resolvedUri)) { | ||
const bundleId = typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis ? globalThis.APP_BUNDLE_ID : undefined; | ||
const bundleId = getAnalyticsGlobals().app_bundle_id; | ||
if (this.secretKey) { | ||
@@ -521,3 +593,3 @@ headers = { | ||
} else if (this.clientId) { | ||
if (!resolvedUri.includes("bundleId")) { | ||
if (!resolvedUri.includes("bundleId") && bundleId) { | ||
resolvedUri = resolvedUri + (bundleId ? `?bundleId=${bundleId}` : ""); | ||
@@ -534,12 +606,18 @@ } | ||
} | ||
headers["x-sdk-version"] = pkg.version; | ||
headers["x-sdk-name"] = pkg.name; | ||
headers["x-sdk-platform"] = bundleId ? "react-native" : isBrowser() ? "browser" : "node"; | ||
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"; | ||
} | ||
setAnalyticsHeaders(headers); | ||
} | ||
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(), this.timeoutInSeconds * 1000); | ||
const timeoutInSeconds = options?.timeoutInSeconds || this.defaultTimeout; | ||
const timeout = setTimeout(() => controller.abort(), timeoutInSeconds * 1000); | ||
const resOrErr = await fetch(resolvedUri, { | ||
@@ -555,3 +633,3 @@ headers, | ||
// early exit if we don't have a status code | ||
throw new Error(`Request timed out after ${this.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."}`); | ||
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."}`); | ||
} | ||
@@ -567,3 +645,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); | ||
} | ||
@@ -591,3 +669,3 @@ if (resOrErr.status === 410) { | ||
// 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); | ||
} | ||
@@ -813,14 +891,14 @@ } | ||
} | ||
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() ? "browser" : "node"); | ||
setAnalyticsHeadersForXhr(xhr); | ||
// if we have a authorization token on global context then add that to the headers | ||
// 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); | ||
@@ -840,11 +918,13 @@ }); | ||
// 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 | ||
// 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"; | ||
} | ||
setAnalyticsHeaders(headers); | ||
const res = await fetch(`${this.uploadServerUrl}/ipfs/upload`, { | ||
@@ -859,4 +939,6 @@ method: "POST", | ||
if (!res.ok) { | ||
console.warn(await res.text()); | ||
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()}`); | ||
} | ||
@@ -866,3 +948,3 @@ const body = await res.json(); | ||
if (!cid) { | ||
throw new Error("Failed to upload files to IPFS"); | ||
throw new Error("Failed to upload files to IPFS - Bad CID"); | ||
} | ||
@@ -920,4 +1002,3 @@ if (options?.uploadWithoutDirectory) { | ||
secretKey: options?.secretKey, | ||
clientId: options?.clientId, | ||
timeoutInSeconds: options?.downloadTimeoutInSeconds | ||
clientId: options?.clientId | ||
}); | ||
@@ -957,4 +1038,4 @@ this.gatewayUrls = prepareGatewayUrls(parseGatewayUrls(options?.gatewayUrls), options?.clientId, options?.secretKey); | ||
*/ | ||
async download(url) { | ||
return this.downloader.download(url, this.gatewayUrls); | ||
async download(url, options) { | ||
return this.downloader.download(url, this.gatewayUrls, options); | ||
} | ||
@@ -975,4 +1056,4 @@ | ||
*/ | ||
async downloadJSON(url) { | ||
const res = await this.download(url); | ||
async downloadJSON(url, options) { | ||
const res = await this.download(url, options); | ||
@@ -990,3 +1071,3 @@ // If we get a JSON object, recursively replace any schemes with gatewayUrls | ||
* @param options - Options to pass through to the storage uploader class | ||
* @returns - The URI of the uploaded data | ||
* @returns The URI of the uploaded data | ||
* | ||
@@ -1015,3 +1096,3 @@ * @example | ||
* @param options - Options to pass through to the storage uploader class | ||
* @returns - The URIs of the uploaded data | ||
* @returns The URIs of the uploaded data | ||
* | ||
@@ -1018,0 +1099,0 @@ * @example |
{ | ||
"name": "@thirdweb-dev/storage", | ||
"version": "0.0.0-dev-ae0f6f2-20230822133010", | ||
"version": "0.0.0-dev-ae85a7ad0f937a26e740cf9f9970835c68ac3841-20240402161005", | ||
"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" | ||
@@ -19,3 +13,3 @@ }, | ||
}, | ||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/storage", | ||
"repository": "https://github.com/thirdweb-dev/js/tree/main/legacy_packages/storage", | ||
"author": "thirdweb eng <eng@thirdweb.com>", | ||
@@ -28,47 +22,39 @@ "license": "Apache-2.0", | ||
"preconstruct": { | ||
"exports": { | ||
"envConditions": [ | ||
"browser" | ||
] | ||
} | ||
"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", | ||
"@microsoft/api-documenter": "^7.24.1", | ||
"@microsoft/api-extractor": "^7.43.0", | ||
"@microsoft/tsdoc": "^0.14.2", | ||
"@preconstruct/cli": "2.7.0", | ||
"@swc-node/register": "^1.6.6", | ||
"@swc-node/register": "^1.9.0", | ||
"@thirdweb-dev/tsconfig": "^0.1.7", | ||
"@types/chai": "^4.3.5", | ||
"@types/mocha": "^10.0.0", | ||
"@types/uuid": "^9.0.2", | ||
"@typescript-eslint/eslint-plugin": "^6.2.0", | ||
"@typescript-eslint/parser": "^6.2.0", | ||
"chai": "^4.3.6", | ||
"eslint": "^8.45.0", | ||
"eslint-config-thirdweb": "^0.1.6", | ||
"@types/uuid": "^9.0.8", | ||
"eslint": "^8.57.0", | ||
"eslint-config-thirdweb": "^0.0.0-dev-ae85a7ad0f937a26e740cf9f9970835c68ac3841-20240402161005", | ||
"eslint-plugin-tsdoc": "^0.2.17", | ||
"esm": "^3.2.25", | ||
"mocha": "^10.2.0", | ||
"typescript": "^5.1.6" | ||
"rimraf": "5.0.5", | ||
"typedoc-gen": "^1.0.2", | ||
"typescript": "5.4.3" | ||
}, | ||
"dependencies": { | ||
"@thirdweb-dev/crypto": "0.0.0-dev-ae85a7ad0f937a26e740cf9f9970835c68ac3841-20240402161005", | ||
"cid-tool": "^3.0.0", | ||
"cross-fetch": "^3.1.8", | ||
"form-data": "^4.0.0", | ||
"uuid": "^9.0.0" | ||
"uuid": "^9.0.1" | ||
}, | ||
"engines": { | ||
"node": ">=18" | ||
}, | ||
"scripts": { | ||
"format": "prettier --write 'src/**/*'", | ||
"lint": "eslint 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" | ||
"test": "vitest", | ||
"push": "yalc push", | ||
"typedoc": "node scripts/typedoc.mjs" | ||
} | ||
} |
<p align="center"> | ||
<br /> | ||
<a href="https://thirdweb.com"><img src="https://github.com/thirdweb-dev/js/blob/main/packages/sdk/logo.svg?raw=true" width="200" alt=""/></a> | ||
<a href="https://thirdweb.com"><img src="https://github.com/thirdweb-dev/js/blob/main/legacy_packages/sdk/logo.svg?raw=true" width="200" alt=""/></a> | ||
<br /> | ||
@@ -30,3 +30,3 @@ </p> | ||
Once you have the Thirdweb Storage SDK installed, you can use it to easily upload and download files and other data using decentralized storage systems. | ||
Once you have the thirdweb Storage SDK installed, you can use it to easily upload and download files and other data using decentralized storage systems. | ||
@@ -33,0 +33,0 @@ Here's a simple example using the SDK to upload and download a file from IPFS: |
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
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
14
49
8
6
176370
4097
+ Added@thirdweb-dev/crypto@0.0.0-dev-ae85a7ad0f937a26e740cf9f9970835c68ac3841-20240402161005
+ Added@noble/hashes@1.7.1(transitive)
+ Added@thirdweb-dev/crypto@0.0.0-dev-ae85a7ad0f937a26e740cf9f9970835c68ac3841-20240402161005(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