diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 2ceb9b1..e177bcd 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -11,7 +11,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
- node-version: '20.x'
+ node-version: '22.x'
registry-url: 'https://registry.npmjs.org'
- run: yarn install --frozen-lockfile
- run: yarn run build
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f977e9c..df4e06f 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -11,7 +11,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v3
with:
- node-version: '20.x'
+ node-version: '22.x'
- run: yarn install --frozen-lockfile
- run: yarn run build
- run: node --test --test-concurrency 1
diff --git a/package.json b/package.json
index 4183897..fb45ce8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@filebase/sdk",
- "version": "1.0.6",
+ "version": "2.0.0",
"description": "SDK for Interacting with Filebase Services [S3(Buckets, Objects), IPFS(Gateways, Pins) IPNS(Names)]",
"repository": {
"type": "git",
@@ -11,22 +11,19 @@
"module": "./src/index.js",
"types": "./dist/index.d.ts",
"exports": {
- ".": {
- "require": "./dist/index.js",
- "import": "./src/index.js",
- "types": "./dist/index.d.ts"
- }
+ "node": {
+ "types": "./dist/node/index.d.ts",
+ "require": "./dist/node/index.js",
+ "import": "./dist/node/index.mjs"
+ },
+ "default": "./dist/browser/index.js"
},
"files": [
"dist",
"src"
],
- "engines": {
- "node": ">=16.0.0",
- "npm": ">=8.0.0"
- },
"scripts": {
- "build": "tsup src/index.js --format cjs --dts --clean",
+ "build": "tsup src/index.js",
"test": "node --test",
"doc": "jsdoc -c jsdoc.json"
},
@@ -38,28 +35,23 @@
"ipns",
"sdk",
"web3",
- "distributed"
+ "distributed",
+ "decentralized"
],
"devDependencies": {
- "clean-jsdoc-theme": "4.2.17",
- "jsdoc": "4.0.2",
- "prettier": "3.1.0",
- "tsup": "8.0.1",
- "typescript": "5.3.3"
+ "clean-jsdoc-theme": "4.3.0",
+ "esbuild": "^0.25.8",
+ "esbuild-plugins-node-modules-polyfill": "1.7.1",
+ "jsdoc": "4.0.4",
+ "prettier": "3.6.2",
+ "tsup": "8.5.0",
+ "typescript": "5.8.3",
+ "uuid": "11.1.0"
},
"dependencies": {
- "@aws-sdk/client-s3": "3.478.0",
- "@aws-sdk/lib-storage": "3.478.0",
- "@helia/car": "1.0.4",
- "@helia/mfs": "3.0.1",
- "@helia/unixfs": "1.4.3",
- "@ipld/car": "5.2.4",
- "axios": "1.6.2",
- "blockstore-fs": "1.1.10",
- "blockstore-core": "4.4.1",
- "datastore-core": "9.2.9",
- "p-queue": "8.0.1",
- "uuid": "9.0.1",
- "winston": "3.12.0"
+ "@aws-sdk/client-s3": "3.842.0",
+ "@aws-sdk/s3-request-presigner": "3.844.0",
+ "axios": "1.10.0",
+ "ipns": "10.1.2"
}
}
diff --git a/readme.md b/readme.md
index 766424e..d4137cf 100644
--- a/readme.md
+++ b/readme.md
@@ -1,99 +1,77 @@
-
• Filebase SDK •
-Developer Friendly [ IPFS | IPNS | S3 ]
+# 🗂️ Filebase SDK
-## About
-
-The Filebase SDK provides a hybrid data management solution, blending S3-compatible cloud storage with IPFS
-(InterPlanetary File System) pinning services. It features robust S3 bucket management, object handling for uploads and
-downloads, and seamless integration with IPFS and IPNS (InterPlanetary Naming System) for decentralized storage
-operations. The SDK supports advanced data tasks like compiling files into CAR (Content Addressable aRchive) formats and
-ensures secure transactions through strong authentication. Designed for varied applications, the Filebase SDK is ideal
-for scenarios demanding the dependability of cloud storage combined with the advantages of decentralized, peer-to-peer
-storage, catering to diverse needs such as content distribution, data backup, and archival. Developing InterPlanetary
-Applications has never been easier.
+[](https://badge.fury.io/js/@filebase%2Fsdk)
+[](https://opensource.org/licenses/MIT)
-### JS Client
+---
+## About
-Install the package using npm
+The Filebase SDK for JavaScript offers a straightforward way to add decentralized storage to your applications. It lets your team easily work with IPFS and IPNS, handling the tricky parts for you.
-```shell
-npm install @filebase/sdk
-```
+Here's what you can do:
-or yarn:
+* Manage Storage: Easily create and control your storage spaces and gateways.
+* Handle IPFS Files: Upload, download, and "pin" files to the IPFS network with ease.
+* Control IPNS Names: Set up and find IPNS names to keep your content links consistent.
+* Move Data: Quickly copy files between different storage spots.
-```shell
-yarn add @filebase/sdk
-```
+Just install it with `npm install @filebase/sdk`. This SDK helps your team quickly build solutions using the power of decentralized data. Check out the full guide for all the details!
### Getting started
-The snippet below shows how to create a new bucket with `BucketManager`, upload a new object to IPFS
-with `ObjectManager`, publish the object to IPNS with `NameManager`, delete the object with `ObjectManager` and finally
-delete the bucket with `BucketManager`.
+The snippet below shows how to create a new bucket, create a new gateway, upload a file to IPFS, publish the
+file to IPNS.
-To use the library in your project, use npm or yarn to install the [`@filebase/sdk`](https://www.npmjs.com/package/@filebase/sdk) module. Requires node.js 16+.
+To use the library in your project, use npm to install the [
+`@filebase/sdk`](https://www.npmjs.com/package/@filebase/sdk) module.
**node.js**
+
````js
-// Import Classes
-import {
- BucketManager,
- ObjectManager,
- NameManager,
- GatewayManager,
- PinManager
-} from '@filebase/sdk'
-
-// Initialize BucketManager
-const bucketManager = new BucketManager(S3_KEY, S3_SECRET);
+// Import example
+import {FilebaseClient} from '@filebase/sdk'
+
// Create bucket
+const client = new FilebaseClient(clientKey, clientSecret);
const bucketName = `create-bucket-[random string]`;
-await bucketManager.create(bucketName);
+await client.createBucket(bucketName);
+
+// Create New Gateway
+const gatewayName = "myRandomGatewayName";
+const myGateway = await client.createGateway(gatewayName);
+
+// Upload File
+const client = new FilebaseClient(clientKey, clientSecret, {
+ bucket: bucketName,
+ gateway: "https://myRandomGatewayName.myfilebase.com"
+});
+const fileName = `new-object`;
+const uploadedFile = await client.uploadFile(fileName, new Blob(["Hello Filebase!"]));
-// Initialize ObjectManager
-const objectManager = new ObjectManager(S3_KEY, S3_SECRET, {
- bucket: bucketName
+// Pin File
+const myNewPin = await client.pinFile("my-pin", "QmTJkc7crTuPG7xRmCQSz1yioBpCW3juFBtJPXhQfdCqGF");
+
+// Download File
+await client.downloadFile("/organized/my-object");
+await client.fetchContentByCid(uploadedFile.cid, {
+ endpoint: "my-custom-gateway.myfilebase.com",
});
-// Upload Object
-const objectName = `new-object`;
-const uploadedObject = await objectManager.upload(objectName, body);
-// Download Object
-await uploadedObject.download();
-// Copy Object to a New Bucket
+
+// Copy File to a New Bucket
const bucketCopyDestinationName = `copy-dest-bucket`
-await bucketManager.create(bucketCopyDestinationName);
-await objectManager.copy(`new-object`, bucketCopyDestinationName);
+await client.createBucket(bucketCopyDestinationName);
+await client.copyFile(`my-original-file`, 'my-copied-file', {
+ destinationBucket: bucketCopyDestinationName
+});
-// Initialize NameManager
-const nameManager = new NameManager(S3_KEY, S3_SECRET);
// Create New IPNS Name with Broadcast Disabled
const ipnsLabel = `myFirstIpnsKey`;
-const ipnsName = await nameManager.create(ipnsLabel, uploadedObject.cid, {
+const ipnsName = await client.createIpnsName(ipnsLabel, uploadedObject.cid, {
enabled: true
});
-
-// Initialize GatewayManager
-const gatewayManager = new GatewayManager(S3_KEY, S3_SECRET);
-// Create New Gateway
-const gatewayName = "myRandomGatewayName";
-const myGateway = await gatewayManager.create(gatewayName);
-
-// Initialize PinManager
-const pinManager = new PinManager(S3_KEY, S3_SECRET, {
- bucket: bucketName,
- gateway: {
- endpoint: "https://myRandomGatewayName.myfilebase.com"
- }
-});
-// Create New Pin with Metadata
-const myNewPin = await pinManager.create("my-pin", "QmTJkc7crTuPG7xRmCQSz1yioBpCW3juFBtJPXhQfdCqGF", {
- "application": "my-custom-app-on-filebase"
-});
+const downloadedFile = await client.fetchContentByIpnsName(ipnsLabel, {
+ endpoint: "my-gw.myfilebase.com"
+})
````
-Full API reference doc for the JS client are available at https://filebase.github.io/filebase-sdk
-
-### Testing
-
-Test are found in the `test` directory and are built to be run with the Node.js v20+ test runner.
\ No newline at end of file
+Full API reference doc for the JS client are available at https://filebase.github.io/filebase-sdk
\ No newline at end of file
diff --git a/src/bucketManager.js b/src/bucketManager.js
deleted file mode 100644
index f09c1ac..0000000
--- a/src/bucketManager.js
+++ /dev/null
@@ -1,168 +0,0 @@
-import {
- CreateBucketCommand,
- DeleteBucketCommand,
- GetBucketAclCommand,
- ListBucketsCommand,
- PutBucketAclCommand, PutBucketTaggingCommand,
- S3Client,
-} from "@aws-sdk/client-s3";
-
-/** Provides methods for managing buckets in an S3 endpoint. */
-class BucketManager {
- #DEFAULT_ENDPOINT = "https://s3.filebase.com";
- #DEFAULT_REGION = "us-east-1";
-
- #client;
-
- /**
- * @summary Creates a new instance of the constructor.
- * @param {string} clientKey - The access key ID for authentication.
- * @param {string} clientSecret - The secret access key for authentication.
- * @tutorial quickstart-bucket
- * @example
- * import { BucketManager } from "@filebase/sdk";
- * const bucketManager = new BucketManager("KEY_FROM_DASHBOARD", "SECRET_FROM_DASHBOARD");
- */
- constructor(clientKey, clientSecret) {
- const clientEndpoint =
- process.env.NODE_ENV === "test"
- ? process.env.TEST_S3_ENDPOINT || this.#DEFAULT_ENDPOINT
- : this.#DEFAULT_ENDPOINT,
- clientConfiguration = {
- credentials: {
- accessKeyId: clientKey,
- secretAccessKey: clientSecret,
- },
- endpoint: clientEndpoint,
- region: this.#DEFAULT_REGION,
- forcePathStyle: true,
- };
- this.#client = new S3Client(clientConfiguration);
- }
-
- /**
- * @typedef {Object} bucket
- * @property {string} Name The name of the bucket
- * @property {date} Date the bucket was created
- */
-
- /**
- * @summary Creates a new bucket with the specified name.
- * @param {string} name - The name of the bucket to create.
- * @returns {Promise} - A promise that resolves when the bucket is created.
- * @example
- * // Create bucket with name of `create-bucket-example`
- * await bucketManager.create(`create-bucket-example`);
- */
- async create(name) {
- const command = new CreateBucketCommand({
- Bucket: name,
- });
-
- return await this.#client.send(command);
- }
-
- /**
- * @summary Lists the buckets in the client.
- * @returns {Promise>} - A promise that resolves with an array of objects representing the buckets in the client.
- * @example
- * // List all buckets
- * await bucketManager.list();
- */
- async list() {
- const command = new ListBucketsCommand({}),
- { Buckets } = await this.#client.send(command);
-
- return Buckets;
- }
-
- /**
- * @summary Deletes the specified bucket.
- * @param {string} name - The name of the bucket to delete.
- * @returns {Promise} - A promise that resolves when the bucket is deleted.
- * @example
- * // Delete bucket with name of `bucket-name-to-delete`
- * await bucketManager.delete(`bucket-name-to-delete`);
- */
- async delete(name) {
- const command = new DeleteBucketCommand({
- Bucket: name,
- });
-
- await this.#client.send(command);
- return true;
- }
-
- /**
- * @summary Sets the privacy of a given bucket.
- * @param {string} name - The name of the bucket to toggle.
- * @param {boolean} targetState - The new target state. [true=private,false=public]
- * @returns {Promise} A promise that resolves to true if the bucket was successfully toggled.
- * @example
- * // Toggle bucket with label of `toggle-bucket-example`
- * await bucketManager.setPrivacy(`toggle-bucket-example`, true); // Enabled
- * await bucketManager.setPrivacy(`toggle-bucket-example`, false); // Disabled
- */
-
- async setPrivacy(name, targetState) {
- const command = new PutBucketAclCommand({
- Bucket: name,
- ACL: targetState ? "private" : "public-read",
- });
-
- await this.#client.send(command);
- return true;
- }
-
- /**
- * @summary Gets the privacy of a given bucket
- * @param {string} name - The name of the bucket to query.
- * @returns {Promise} A promise that resolves to true if the bucket is private.
- */
- async getPrivacy(name) {
- const command = new GetBucketAclCommand({
- Bucket: name,
- });
-
- const response = await this.#client.send(command),
- readPermission = response.Grants.find((grant) => {
- return grant.Grantee.Type === "Group" && grant.Permission === "READ";
- });
- return !(typeof readPermission !== "undefined");
- }
-
- /**
- * @summary Generates the IPFS Directory/Folder CID for a given bucket
- * @param {string} name - The name of the bucket to use.
- * @returns {Promise} A promise that resolves with the CID of the new directory/folder
- */
- async generateCid(name) {
- const command = new PutBucketTaggingCommand({
- Bucket: name,
- Tagging: {
- TagSet: [
- {
- Key: "generateBucketCid",
- Value: "true"
- }
- ]
- }
- });
-
- let cid = false;
- command.middlewareStack.add(
- (next) => async (args) => {
- const response = await next(args);
-
- // Get cid from headers
- cid = response.response.headers["x-amz-meta-cid"];
- return response;
- }
- );
-
- await this.#client.send(command);
- return cid;
- }
-}
-
-export default BucketManager;
diff --git a/src/gatewayManager.js b/src/gatewayManager.js
deleted file mode 100644
index b87dd29..0000000
--- a/src/gatewayManager.js
+++ /dev/null
@@ -1,221 +0,0 @@
-import axios from "axios";
-import { apiErrorHandler } from "./helpers.js";
-
-class GatewayManager {
- #DEFAULT_ENDPOINT = "https://api.filebase.io";
- #DEFAULT_TIMEOUT = 60000;
-
- #client;
-
- /**
- * @summary Creates a new instance of the constructor.
- * @param {string} clientKey - The access key ID for authentication.
- * @param {string} clientSecret - The secret access key for authentication.
- * @tutorial quickstart-gateway
- * @example
- * import { GatewayManager } from "@filebase/sdk";
- * const gatewayManager = new GatewayManager("KEY_FROM_DASHBOARD", "SECRET_FROM_DASHBOARD");
- */
- constructor(clientKey, clientSecret) {
- const clientEndpoint =
- process.env.NODE_ENV === "test"
- ? process.env.TEST_GW_ENDPOINT || this.#DEFAULT_ENDPOINT
- : this.#DEFAULT_ENDPOINT,
- encodedToken = Buffer.from(`${clientKey}:${clientSecret}`).toString(
- "base64",
- ),
- baseURL = `${clientEndpoint}/v1/gateways`;
- this.#client = axios.create({
- baseURL: baseURL,
- timeout: this.#DEFAULT_TIMEOUT,
- headers: { Authorization: `Bearer ${encodedToken}` },
- });
- }
-
- /**
- * @typedef {Object} gateway
- * @property {string} name Name for the gateway
- * @property {string} domain Custom Domain for the gateway
- * @property {boolean} enabled Whether the gateway is enabled or not
- * @property {string} private Whether the gateway is scoped to users content
- * @property {date} created_at Date the gateway was created
- * @property {date} updated_at Date the gateway was last updated
- */
-
- /**
- * @typedef {Object} gatewayOptions
- * @property {boolean} [domain] Optional Domain to allow for using a Custom Domain
- * @property {string} [enabled] Optional Toggle to use for enabling the gateway
- * @property {boolean} [private] Optional Boolean determining if gateway is Public or Private
- */
-
- /**
- * @summary Creates a gateway with the given name and options
- * @param {string} name Unique name across entire platform for the gateway. Must be a valid subdomain name.
- * @param {gatewayOptions} [options]
- * @returns {Promise} - A promise that resolves to the value of a gateway.
- * @example
- * // Create gateway with name of `create-gateway-example` and a custom domain of `cname.mycustomdomain.com`.
- * // The custom domain must already exist and have a CNAME record pointed at `create-gateway-example.myfilebase.com`.
- * await gatewayManager.create(`create-gateway-example`, {
- * domain: `cname.mycustomdomain.com`
- * });
- */
- async create(name, options = {}) {
- try {
- let createOptions = {
- name,
- };
- if (typeof options.domain === "string") {
- createOptions.domain = options.domain;
- }
- if (typeof options.enabled === "boolean") {
- createOptions.enabled = options.enabled;
- }
- if (typeof options.private === "boolean") {
- createOptions.private = options.private;
- }
- const createResponse = await this.#client.request({
- method: "POST",
- data: createOptions,
- });
- return createResponse.data;
- } catch (err) {
- apiErrorHandler(err);
- }
- }
-
- /**
- * @summary Deletes a gateway with the given name.
- * @param {string} name - The name of the gateway to delete.
- * @returns {Promise} - A promise that resolves to true if the gateway was successfully deleted.
- * @example
- * // Delete gateway with name of `delete-gateway-example`
- * await gatewayManager.delete(`delete-name-example`);
- */
- async delete(name) {
- try {
- await this.#client.request({
- method: "DELETE",
- url: `/${name}`,
- validateStatus: (status) => {
- return status === 204;
- },
- });
- return true;
- } catch (err) {
- apiErrorHandler(err);
- }
- }
-
- /**
- * @summary Returns the value of a gateway
- * @param {string} name - Parameter representing the name to get.
- * @returns {Promise} - A promise that resolves to the value of a gateway.
- * @example
- * // Get gateway with name of `gateway-get-example`
- * await gatewayManager.get(`gateway-get-example`);
- */
- async get(name) {
- try {
- const getResponse = await this.#client.request({
- method: "GET",
- url: `/${name}`,
- validateStatus: (status) => {
- return status === 200 || status === 404;
- },
- });
- return getResponse.status === 200 ? getResponse.data : false;
- } catch (err) {
- apiErrorHandler(err);
- }
- }
-
- /**
- * @summary Returns a list of gateways
- * @returns {Promise>} - A promise that resolves to an array of gateways.
- * @example
- * // List all gateways
- * await gatewayManager.list();
- */
- async list() {
- try {
- const getResponse = await this.#client.request({
- method: "GET",
- });
- return getResponse.data;
- } catch (err) {
- apiErrorHandler(err);
- }
- }
-
- /**
- * @summary Updates the specified gateway.
- * @param {string} name - The name of the gateway to update.
- * @param {gatewayOptions} options - The options for the update operation.
- *
- * @returns {Promise} - A Promise that resolves to true if the gateway was updated.
- * @example
- * // Update gateway with name of `update-gateway-example` and set the gateway to only serve CIDs pinned by user.
- * await gatewayManager.update(`update-gateway-example`, {
- * private: true
- * });
- */
- async update(name, options) {
- try {
- const updateOptions = {
- name,
- };
- if (options?.domain) {
- updateOptions.domain = String(options.private);
- }
- if (options?.enabled) {
- updateOptions.enabled = Boolean(options.enabled);
- }
- if (options?.private) {
- updateOptions.private = Boolean(options.private);
- }
- await this.#client.request({
- method: "PUT",
- url: `/${name}`,
- data: updateOptions,
- validateStatus: (status) => {
- return status === 200;
- },
- });
- return true;
- } catch (err) {
- apiErrorHandler(err);
- }
- }
-
- /**
- * @summary Toggles the enabled state of a given gateway.
- * @param {string} name - The name of the gateway to toggle.
- * @param {boolean} targetState - The new target state.
- * @returns {Promise} A promise that resolves to true if the gateway was successfully toggled.
- * @example
- * // Toggle gateway with label of `toggle-gateway-example`
- * await gatewayManager.toggle(`toggle-gateway-example`, true); // Enabled
- * await gatewayManager.toggle(`toggle-gateway-example`, false); // Disabled
- */
- async toggle(name, targetState) {
- try {
- await this.#client.request({
- method: "PUT",
- url: `/${name}`,
- data: {
- enabled: Boolean(targetState),
- },
- validateStatus: (status) => {
- return status === 200;
- },
- });
- return true;
- } catch (err) {
- apiErrorHandler(err);
- }
- }
-}
-
-export default GatewayManager;
diff --git a/src/helpers.js b/src/helpers.js
deleted file mode 100644
index 9f604b2..0000000
--- a/src/helpers.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import axios from "axios";
-
-const GATEWAY_DEFAULT_TIMEOUT = 60000;
-
-async function downloadFromGateway(cid, options) {
- if (typeof options.endpoint !== "string") {
- throw new Error(`Default Gateway must be set`);
- }
-
- const downloadHeaders = {};
- if (options.token) {
- downloadHeaders["x-filebase-gateway-token"] = options.token;
- }
-
- const downloadResponse = await axios.request({
- method: "GET",
- baseURL: options.endpoint,
- url: `/ipfs/${cid}`,
- headers: downloadHeaders,
- type: "stream",
- timeout: options?.timeout || GATEWAY_DEFAULT_TIMEOUT,
- });
- return downloadResponse.data;
-}
-
-function apiErrorHandler(err) {
- if (
- err?.response &&
- err?.response?.status &&
- (err.response.status.toString()[0] === "4" ||
- err.response.status.toString()[0] === "5")
- ) {
- throw new Error(
- err.response.data.error?.details ||
- err.response.data.error?.reason ||
- err,
- );
- }
- throw err;
-}
-
-export { downloadFromGateway, apiErrorHandler };
diff --git a/src/index.js b/src/index.js
index 8687af6..c704c0b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,13 +1,1048 @@
-import BucketManager from "./bucketManager.js";
-import GatewayManager from "./gatewayManager.js";
-import NameManager from "./nameManager.js";
-import ObjectManager from "./objectManager.js";
-import PinManager from "./pinManager.js";
-
-export {
- BucketManager,
- GatewayManager,
- NameManager,
- ObjectManager,
- PinManager,
-};
+import axios from "axios";
+import {
+ CopyObjectCommand,
+ CreateBucketCommand,
+ DeleteBucketCommand,
+ DeleteObjectCommand,
+ GetBucketTaggingCommand,
+ GetObjectCommand,
+ HeadObjectCommand,
+ ListBucketsCommand,
+ ListObjectsV2Command,
+ PutBucketTaggingCommand,
+ PutObjectCommand,
+ S3Client,
+} from "@aws-sdk/client-s3";
+import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
+import { unmarshalIPNSRecord } from "ipns";
+
+/**
+ */
+
+class FilebaseClient {
+ #DEFAULT_RPC_TIMEOUT = 60000;
+ #DEFAULT_RPC_ENDPOINT = "https://rpc.filebase.io";
+ #DEFAULT_S3_ENDPOINT = "https://s3.filebase.com";
+ #DEFAULT_REGION = "us-east-1";
+
+ #DEFAULT_ENDPOINT = "https://api.filebase.io";
+ #DEFAULT_TIMEOUT = 60000;
+
+ #GATEWAY_DEFAULT_TIMEOUT = 60000;
+ #PUBLIC_IPFS_GATEWAY = "https://ipfs.filebase.io";
+ #VALID_FORMATS = ["ipns-record", "raw", "car", "tar"];
+
+ #default_bucket;
+ #default_gateway;
+
+ #ipfs_credentials;
+ #ipfs_client;
+ #gateways_client;
+ #names_client;
+ #s3_client;
+
+ /**
+ * @summary Creates a new instance of the constructor.
+ * @param {string} clientKey - The access key ID for authentication.
+ * @param {string} clientSecret - The secret access key for authentication.
+ * @param {Object} [options] - Options for the client (optional)
+ * @property {string} options.bucket The bucket to use for file operations (optional)
+ * @property {string} options.gateway The gateway to use for file retrievals (optional)
+ * @property {string} options.timeout The amount of time to wait for responses (optional)
+ * @tutorial quickstart-bucket
+ * @example
+ * import FilebaseClient from "@filebase/sdk";
+ * const client = new FilebaseClient("KEY_FROM_DASHBOARD", "SECRET_FROM_DASHBOARD", {
+ * bucket: "my-main-bucket"
+ * });
+ */
+ constructor(clientKey, clientSecret, options) {
+ //region S3 Client
+ const clientEndpoint = options?.endpoints?.s3 || this.#DEFAULT_S3_ENDPOINT;
+ this.#s3_client = new S3Client({
+ credentials: {
+ accessKeyId: clientKey,
+ secretAccessKey: clientSecret,
+ },
+ endpoint: clientEndpoint,
+ region: this.#DEFAULT_REGION,
+ forcePathStyle: true,
+ });
+ //endregion
+
+ //region IPFS Client
+ const ipfsEndpoint = options?.endpoints?.rpc || this.#DEFAULT_RPC_ENDPOINT;
+ this.#ipfs_credentials = `${clientKey}:${clientSecret}`;
+ let ipfsCredentials = this.#ipfs_credentials;
+ if (options?.bucket) {
+ ipfsCredentials = `${ipfsCredentials}:${options.bucket}`;
+ this.#default_bucket = options.bucket;
+ }
+ this.#ipfs_client = axios.create({
+ baseURL: ipfsEndpoint,
+ timeout: options?.timeout || this.#DEFAULT_RPC_TIMEOUT,
+ headers: {
+ common: {
+ Authorization: `Bearer ${Buffer.from(ipfsCredentials).toString("base64")}`,
+ },
+ },
+ responseType: "text",
+ });
+ //endregion
+
+ //region Gateways Client
+ const gatewayClientEndpoint =
+ options?.endpoints?.platform || this.#DEFAULT_ENDPOINT;
+ this.#gateways_client = axios.create({
+ baseURL: `${gatewayClientEndpoint}/v1/gateways`,
+ timeout: options?.timeout || this.#GATEWAY_DEFAULT_TIMEOUT,
+ headers: {
+ common: {
+ Authorization: `Bearer ${Buffer.from(this.#ipfs_credentials).toString("base64")}`,
+ },
+ },
+ });
+ //endregion
+
+ //region Names Client
+ const namesClientEndpoint =
+ options?.endpoints?.platform || this.#DEFAULT_ENDPOINT;
+ this.#names_client = axios.create({
+ baseURL: `${namesClientEndpoint}/v1/names`,
+ timeout: this.#DEFAULT_TIMEOUT,
+ headers: {
+ common: {
+ Authorization: `Bearer ${Buffer.from(this.#ipfs_credentials).toString("base64")}`,
+ },
+ },
+ });
+ //endregion
+
+ //region IPFS Gateway Client
+ this.#default_gateway = options?.gateway || this.#PUBLIC_IPFS_GATEWAY;
+ //endregion
+ }
+
+ //region Utility Methods
+ #apiErrorHandler(err) {
+ if (
+ err?.response &&
+ err?.response?.status &&
+ (err.response.status.toString()[0] === "4" ||
+ err.response.status.toString()[0] === "5")
+ ) {
+ throw new Error(
+ err.response.data.error?.details ||
+ err.response.data.error?.reason ||
+ err,
+ );
+ }
+ throw err;
+ }
+
+ #getIpfsCredentials(bucket) {
+ let encodedCredentials = this.#ipfs_credentials;
+ if (bucket) {
+ encodedCredentials = `${encodedCredentials}:${bucket}`;
+ } else if (this.#default_bucket) {
+ encodedCredentials = `${encodedCredentials}:${this.#default_bucket}`;
+ } else {
+ throw new Error("Bucket must be set");
+ }
+ return Buffer.from(encodedCredentials).toString("base64");
+ }
+ //endregion
+
+ //region Bucket Methods
+ /**
+ * @typedef {Object} bucket
+ * @property {string} Name The name of the bucket
+ * @property {date} Date the bucket was created
+ */
+
+ /**
+ * @summary Creates a new bucket with the specified name.
+ * @param {string} name - The name of the bucket to create.
+ * @returns {Promise} - A promise that resolves when the bucket is created.
+ * @example
+ * // Create bucket with name of `create-bucket-example`
+ * const createdBucket = await client.createBucket(`create-bucket-example`);
+ */
+ async createBucket(name) {
+ const command = new CreateBucketCommand({
+ Bucket: name,
+ });
+
+ return await this.#s3_client.send(command);
+ }
+
+ /**
+ * @summary Deletes the specified bucket.
+ * @param {string} name - The name of the bucket to delete.
+ * @returns {Promise} - A promise that resolves when the bucket is deleted.
+ * @example
+ * // Delete bucket with name of `bucket-name-to-delete`
+ * await client.deleteBucket(`bucket-name-to-delete`);
+ */
+ async deleteBucket(name) {
+ const command = new DeleteBucketCommand({
+ Bucket: name,
+ });
+
+ await this.#s3_client.send(command);
+ return true;
+ }
+
+ /**
+ * @summary Generates the IPFS Directory/Folder CID for a given bucket
+ * @param {string} name - The name of the bucket to use.
+ * @returns {Promise} A promise that resolves with the CID of the new directory/folder
+ * @example
+ * // Generate CID for bucket with name of `bucket-name-to-mfs`
+ * const generatedCid = await client.generateBucketCid(`bucket-name-to-mfs`);
+ */
+ async generateBucketCid(name) {
+ const command = new PutBucketTaggingCommand({
+ Bucket: name,
+ Tagging: {
+ TagSet: [
+ {
+ Key: "generateBucketCid",
+ Value: "true",
+ },
+ ],
+ },
+ });
+
+ let cid = false;
+ command.middlewareStack.add((next) => async (args) => {
+ const response = await next(args);
+
+ // Get cid from headers
+ cid = response.response.headers["x-amz-meta-cid"];
+ return response;
+ });
+
+ await this.#s3_client.send(command);
+ return cid;
+ }
+
+ /**
+ * @summary Gets the IPFS Directory/Folder CID for a given bucket
+ * @param {string} name - The name of the bucket to use.
+ * @returns {Promise} A promise that resolves with the CID of the directory
+ * @example
+ * // Get CID for bucket with name of `bucket-name-with-mfs`
+ * const bucketCid = await client.generateBucketCid(`bucket-name-with-mfs`);
+ */
+ async getBucketCid(name) {
+ const getCidCommand = new GetBucketTaggingCommand({
+ Bucket: name,
+ });
+ const getCidResponse = await this.#s3_client.send(getCidCommand);
+ if (
+ typeof getCidResponse !== "undefined" &&
+ getCidResponse.TagSet !== "undefined"
+ ) {
+ const resolvedTag = getCidResponse.TagSet.find((element) => {
+ return element.Key === "CID";
+ });
+ if (typeof resolvedTag !== "undefined" && resolvedTag.Value !== "") {
+ return resolvedTag.Value;
+ }
+ }
+ throw new Error(`Failed to Fetch CID for Bucket`);
+ }
+
+ /**
+ * @summary Lists the buckets in the client.
+ * @returns {Promise>} - A promise that resolves with an array of objects representing the buckets in the client.
+ * @example
+ * // List all buckets
+ * const bucketList = await client.listBuckets();
+ */
+ async listBuckets() {
+ const command = new ListBucketsCommand({}),
+ { Buckets } = await this.#s3_client.send(command);
+
+ return Buckets;
+ }
+ //endregion
+
+ //region File Methods
+ async #uploadFiles(formData, options) {
+ const uploadHeaders = options.headers || {};
+ uploadHeaders["Authorization"] =
+ `Bearer ${this.#getIpfsCredentials(options?.bucket)}`;
+ const uploadParams = options.params || {};
+ uploadParams["preserve-filenames"] = "true";
+ uploadParams["cid-version"] = uploadParams.cidVersion
+ ? Number(uploadParams.cidVersion)
+ : 0;
+
+ const downloadResponse = await this.#ipfs_client.request({
+ method: "POST",
+ url: "api/v0/add",
+ headers: uploadHeaders,
+ params: uploadParams,
+ data: formData,
+ validateStatus: function (status) {
+ return status === 200;
+ },
+ });
+
+ const pins = [];
+ for (const entry of downloadResponse.data.split("\n")) {
+ if (entry === "") {
+ continue;
+ }
+ const parsedEntry = JSON.parse(entry);
+ pins.push({
+ name: parsedEntry["Name"],
+ cid: parsedEntry["Hash"],
+ size: parsedEntry["Size"],
+ });
+ }
+ return pins;
+ }
+
+ /**
+ * @summary Copies a file by name. Can also copy files to another bucket.
+ * @param {string} from - The name of the file to use as the source.
+ * @param {string} to - The name to use for the destination file
+ * @param {Object} [options] Options for copying file
+ * @property {string} options.sourceBucket The bucket to copy the file from.
+ * @property {string} entries.destinationBucket The bucket to copy the file into.
+ * @returns {Promise} - A promise that resolves when the file has been copied.
+ * @example
+ * // Copy file with name of `copy-file-example`
+ * const copiedFile = await client.copyFile(`copy-file-example`, `copy-file-example-copy1`);
+ */
+ async copyFile(from, to, options) {
+ const copySource = `${
+ options?.sourceBucket || this.#default_bucket
+ }/${from}`,
+ command = new CopyObjectCommand({
+ CopySource: copySource,
+ Bucket: options?.destinationBucket || this.#default_bucket,
+ Key: to,
+ });
+
+ await this.#s3_client.send(command);
+ return true;
+ }
+
+ /**
+ * @summary Deletes a file by name.
+ * @param {string} name - The name of the file to delete.
+ * @param {Object} [options] Options for deleting file
+ * @property {string} options.bucket The bucket to delete the file from.
+ * @returns {Promise} - A promise that resolves when the file has been deleted.
+ * @example
+ * // Delete file with name of `delete-file-example`
+ * const deletedFile = await client.deleteFile(`delete-file-example`);
+ */
+ async deleteFile(name, options) {
+ const command = new DeleteObjectCommand({
+ Bucket: options?.bucket || this.#default_bucket,
+ Key: name,
+ });
+
+ await this.#s3_client.send(command);
+ return true;
+ }
+
+ /**
+ * @summary Downloads a file by name.
+ * @param {string} name - The name of the file to download.
+ * @param {Object} [options] Options for downloading file
+ * @property {string} options.bucket The bucket to download the file from.
+ * @returns {Promise} - A promise that resolves with the contents of the file.
+ * @example
+ * // Download file with name of `download-file-example`
+ * const downloadedFile = await client.downloadFile(`download-file-example`);
+ */
+ async downloadFile(name, options) {
+ const command = new GetObjectCommand({
+ Bucket: options?.bucket || this.#default_bucket,
+ Key: name,
+ }),
+ response = await this.#s3_client.send(command);
+
+ return response.Body;
+ }
+
+ /**
+ * @summary Generate presigned URL for uploading a file.
+ * @param {string} name - The name of the file to upload.
+ * @param {Object} [options] Options for downloading file
+ * @property {string} options.bucket The bucket to upload the file into.
+ * @property {string} options.expectedContentType The content type that the uploaded file should be.
+ * @property {integer} options.expectedFileSize The number of bytes the file should be on upload.
+ * @property {integer} options.expirationInSeconds The number of seconds for the URL to be valid.
+ * @returns {Promise} - A promise that resolves with the presigned URL to use for the upload.
+ * @example
+ * // Generate a presigned URL to upload a file with name of `presigned-upload-file-example`
+ * const presignedUrl = await client.generatePresignedUrl(`presigned-upload-file-example`, {
+ * expirationInSeconds: 600,
+ * });
+ */
+ async generatePresignedUrl(name, options) {
+ const putObjectOptions = {
+ Bucket: options?.bucket || this.#default_bucket,
+ Key: name,
+ };
+ if (options?.expectedContentType) {
+ putObjectOptions["ContentType"] = options?.expectedContentType;
+ }
+ if (options?.expectedFileSize) {
+ putObjectOptions["ContentLength"] = options?.expectedFileSize;
+ }
+
+ try {
+ const command = new PutObjectCommand(putObjectOptions);
+ return await getSignedUrl(this.#s3_client, command, {
+ expiresIn: options?.expirationInSeconds || 3600, // URL valid for 1 hour by default
+ });
+ } catch (error) {
+ console.error(`Error generating presigned upload URL:`, error);
+ throw error;
+ }
+ }
+
+ /**
+ * @summary Gets an objects info and metadata using the S3 API.
+ * @param {string} name - The key of the object to be inspected.
+ * @param {Object} [options] - The options for inspecting the object.
+ * @property {string} options.bucket - The bucket to pin the IPFS CID into.
+ * @returns {Promise