Adding IPFS to my JavaScript OS
Tags: web3, webdev, javascript, cloud
Published: 2022-09-26 20:35:54

I've been wanting to dip my toes into the Web 3.0 verse for a while now, specifically with an integration into my side project, a [Desktop environment in the browser](https://github.com/DustinBrett/daedalOS) ([daedalOS](https://dustinbrett.com/)).

I've decided as a first step I would add [ipfs](https://ipfs.io/) [native url](https://docs.ipfs.tech/how-to/address-ipfs-on-web/#native-urls) support. I've added this support to the [Browser](https://github.com/DustinBrett/daedalOS/blob/main/components/apps/Browser/index.tsx) ([Demo](https://dustinbrett.com/?app=Browser&url=ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/wiki/Vincent_van_Gogh.html)), [Run Dialog](https://github.com/DustinBrett/daedalOS/blob/main/components/system/Dialogs/Run/index.tsx), [Shortcuts](https://github.com/DustinBrett/daedalOS/blob/main/components/system/Files/FileEntry/useFile.ts) & [Terminal](https://github.com/DustinBrett/daedalOS/blob/main/components/apps/Terminal/useCommandInterpreter.ts). Most of the main ipfs functions I've migrated to [utils/ipfs.ts](https://github.com/DustinBrett/daedalOS/blob/main/utils/ipfs.ts).

![ipfs://bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/29xquiyxitakaholtrdj.png)

The basic concept for how this works is by taking the ipfs native url and converting it to one that uses an [ipfs gateway](https://docs.ipfs.tech/concepts/ipfs-gateway/), in my case I've added the [official ipfs gateways](https://ipfs.github.io/public-gateway-checker/) as the fallback and the [Cloudflare gateways](https://developers.cloudflare.com/web3/ipfs-gateway/) as the primary.

```ts
const IPFS_GATEWAY_URLS = [
  "https://<CID>.ipfs.cf-ipfs.com/",
  "https://<CID>.ipfs.dweb.link/",
  "https://cloudflare-ipfs.com/ipfs/<CID>/",
  "https://gateway.ipfs.io/ipfs/<CID>/",
];
```

When I make the URL I attempt to use the newer [CID v1 format](https://docs.ipfs.tech/concepts/content-addressing/), not to be confused with CID v0 which is less CORS friendly. To convert into this format which uses a case-insensitive base32 format, I used the library [multiformats](https://www.npmjs.com/package/multiformats).

```ts
let IPFS_GATEWAY_URL = "";

const getIpfsGatewayUrl = async (
  ipfsUrl: string,
  notCurrent?: boolean
): Promise<string> => {
  if (!IPFS_GATEWAY_URL || notCurrent) {
    const urlList = notCurrent
      ? IPFS_GATEWAY_URLS.filter((url) => url !== IPFS_GATEWAY_URL)
      : IPFS_GATEWAY_URLS;

    for (const url of urlList) {
      if (await isIpfsGatewayAvailable(url)) {
        IPFS_GATEWAY_URL = url;
        break;
      }
    }

    if (!IPFS_GATEWAY_URL) return "";
  }

  const { pathname, protocol, search } = new URL(ipfsUrl);

  if (protocol !== "ipfs:") return "";

  const [cid = "", ...path] = pathname.split("/").filter(Boolean);
  const { CID } = await import("multiformats/cid");

  return `${IPFS_GATEWAY_URL.replace(
    "<CID>",
    CID.parse(cid).toV1().toString()
  )}${path.join("/")}${search}`;
};
```

![ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/wiki/Vincent_van_Gogh.html](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nibmb4qpp7xi326vmxns.png)

When doing the initial check to find the gateway to use, I've based the availability checker on code taken from [public-gateway-checker](https://github.com/ipfs/public-gateway-checker).

```ts
const isIpfsGatewayAvailable = (gatewayUrl: string): Promise<boolean> =>
  new Promise((resolve) => {
    const timeoutId = window.setTimeout(
      () => resolve(false),
      1000
    );
    const img = new Image();

    img.addEventListener("load", () => {
      window.clearTimeout(timeoutId);
      resolve(true);
    });
    img.addEventListener("error", () => {
      window.clearTimeout(timeoutId);
      resolve(false);
    });

    img.src = `${gatewayUrl.replace(
      "<CID>",
      // https://github.com/ipfs/public-gateway-checker/blob/master/src/constants.ts
      "bafybeibwzifw52ttrkqlikfzext5akxu7lz4xiwjgwzmqcpdzmp3n5vnbe"
    )}?now=${Date.now()}&filename=1x1.png#x-ipfs-companion-no-redirect`;
  });
```

When it comes to actually doing the request, a normal `fetch()` command is all that is needed with the new ipfs gateway url.

```ts
const getIpfsResource = async (ipfsUrl: string): Promise<Buffer> => {
  let response: Response | null = null;
  const requestOptions = {
    cache: "no-cache",
    credentials: "omit",
    keepalive: false,
    mode: "cors",
    priority: "high",
    referrerPolicy: "no-referrer",
    window: null,
  } as RequestInit;

  try {
    response = await fetch(await getIpfsGatewayUrl(ipfsUrl), requestOptions);
  } catch (error) {
    if ((error as Error).message === "Failed to fetch") {
      response = await fetch(
        await getIpfsGatewayUrl(ipfsUrl, true),
        requestOptions
      );
    }
  }

  return response instanceof Response
    ? Buffer.from(await response.arrayBuffer())
    : Buffer.from("");
};
```

![ipfs://QmcniBv7UQ4gGPQQW2BwbD4ZZHzN3o3tPuNLZCbBchd1zh](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zerk435338ytjelmts13.png)

I've also added file type detection so it can try to open the url in the correct app. This is done using the library [file-type](https://www.npmjs.com/package/file-type).

```ts
const getIpfsFileName = async (
  ipfsUrl: string,
  ipfsData: Buffer
): Promise<string> => {
  const { pathname, searchParams } = new URL(ipfsUrl);
  const fileName = searchParams.get("filename");

  if (fileName) return fileName;

  const { fileTypeFromBuffer } = await import("file-type");
  const { ext = "" } = (await fileTypeFromBuffer(ipfsData)) || {};

  return `${pathname.split("/").filter(Boolean).join("_")}${
    ext ? `.${ext}` : ""
  }`;
};
```

I've found the reliability of the gateway's to be pretty good, but sometimes there was a large amount of lag before a request would begin.

Thanks for reading my article and feel free to try out some ipfs urls on daedalOS which is also my personal website, [dustinbrett.com](https://dustinbrett.com/).

If you want to know more about daedalOS which is the client of all this ipfs magic, feel free to check out my [YouTube](https://www.youtube.com/c/DustinBrett) video where I go over the various features, including ipfs. Thanks!

{% youtube CkvKPspIPLs %}