ipfs-storage
Version:
IPFS storage API providers.
159 lines (145 loc) • 5.32 kB
JavaScript
import { create } from "@web3-storage/w3up-client";
import { StoreConf } from "@web3-storage/w3up-client/stores/conf";
import { Blob } from "buffer";
import { CID } from "multiformats";
// --- Storacha Provider ---
// IMPORTANT:
// This provider works in two modes:
// 1. Ambient State (Default): Assumes the execution environment has a w3up agent
// that has ALREADY been authorized via email (`w3 login <email>`) and has
// a default space selected (`w3 space use <did>`). The library CANNOT handle
// the initial interactive email login required by Storacha.
// 2. Pre-configured Client: Accepts an optional authorized `client` instance and
// an optional `spaceDid` via the `connect` object. This allows applications
// (like Strapi plugins) to manage the Storacha client lifecycle externally.
const ERR_MSG_LOGIN =
"Storacha agent not logged in or initialized. Run 'w3 login <email>' externally first, or pass a pre-configured client.";
const ERR_MSG_SPACE =
"No Storacha space DID provided in 'connect.spaceDid' and no default space set for the agent. Run 'w3 space use <did>' externally or provide 'spaceDid'.";
const uploadFile = async function (
connect = { client: undefined, spaceDid: undefined },
file = { hash: "", ext: "", stream: "", buffer: "" }
) {
let client = connect.client;
const managedClient = !client;
try {
// --- Client Initialization and Auth Check ---
if (!client) {
const store = new StoreConf({ profile: "w3cli" });
client = await create({ store });
}
if (managedClient) {
const accounts = await client.accounts();
if (!accounts || Object.keys(accounts).length === 0) {
throw new Error(ERR_MSG_LOGIN);
}
}
// --- Space Selection ---
if (connect.spaceDid) {
await client.setCurrentSpace(connect.spaceDid);
} else {
const currentSpace = await client.currentSpace();
if (!currentSpace) {
throw new Error(ERR_MSG_SPACE);
}
}
// --- Prepare File Blob ---
let fileContent = file.buffer;
if (!fileContent) {
if (file.stream) {
const chunks = [];
for await (const chunk of file.stream) {
chunks.push(chunk);
}
fileContent = Buffer.concat(chunks);
} else {
throw new Error("Storacha upload requires file buffer or stream.");
}
}
const blob = new Blob([fileContent]);
// --- Upload ---
const rootCid = await client.uploadFile(blob);
if (!rootCid) {
throw new Error("Storacha upload failed: No CID returned.");
}
return Promise.resolve(`https://${rootCid.toString()}.ipfs.w3s.link`);
} catch (err) {
// --- Error Handling ---
const baseMessage = `Storacha upload failed: ${err.message}`;
// Add specific guidance for common setup issues
if (
err.message?.includes("missing capability") ||
err.message?.includes("space not found") ||
err.message?.includes("space mismatch")
) {
console.error(`${baseMessage}. ${ERR_MSG_SPACE}`);
throw new Error(`${baseMessage}. ${ERR_MSG_SPACE}`);
} else if (
err.message?.includes("Agent not authorized") ||
err.message?.includes("not logged in")
) {
console.error(`${baseMessage}. ${ERR_MSG_LOGIN}`);
throw new Error(`${baseMessage}. ${ERR_MSG_LOGIN}`);
} else {
console.error(baseMessage, err); // Log full error for other cases
throw new Error(baseMessage);
}
}
};
const deleteFile = async function (
connect = { client: undefined, spaceDid: undefined },
file = { cid: "" }
) {
let client = connect.client;
const managedClient = !client;
if (!file.cid) {
throw new Error("Storacha delete requires the file CID.");
}
try {
// --- Client Initialization and Auth Check ---
if (!client) {
const store = new StoreConf({ profile: "w3cli" });
client = await create({ store });
}
if (managedClient) {
const accounts = await client.accounts();
if (!accounts || Object.keys(accounts).length === 0) {
throw new Error(ERR_MSG_LOGIN);
}
}
// --- Space Selection ---
if (connect.spaceDid) {
await client.setCurrentSpace(connect.spaceDid);
} else {
const currentSpace = await client.currentSpace();
if (!currentSpace) {
throw new Error(ERR_MSG_SPACE);
}
}
// --- Delete ---
const cidToDelete = CID.parse(file.cid);
await client.remove(cidToDelete, { shards: true });
return true;
} catch (err) {
// --- Error Handling ---
const baseMessage = `Storacha delete failed for CID ${file.cid}: ${err.message}`;
if (
err.message?.includes("missing capability") ||
err.message?.includes("space not found") ||
err.message?.includes("space mismatch")
) {
console.error(`${baseMessage}. ${ERR_MSG_SPACE}`);
throw new Error(`${baseMessage}. ${ERR_MSG_SPACE}`);
} else if (
err.message?.includes("Agent not authorized") ||
err.message?.includes("not logged in")
) {
console.error(`${baseMessage}. ${ERR_MSG_LOGIN}`);
throw new Error(`${baseMessage}. ${ERR_MSG_LOGIN}`);
} else {
console.error(baseMessage, err);
throw new Error(baseMessage);
}
}
};
export { deleteFile, uploadFile };