ipfs / helia

An implementation of IPFS in JavaScript
https://helia.io
Other
811 stars 81 forks source link

Attempted import error: 'RTCSessionDescription' is not exported #553

Open thachp opened 4 weeks ago

thachp commented 4 weeks ago

I have no luck writing a ReactJS Hook for Helia. I keep getting the following errors... Any thoughts?

Repro(s)

  1. Start a new NextJS project
  2. Add Helia
  3. copy & paste hooks below.
  4. Use it in any react component

// version "helia": "4.2.3", "next": "14.2.3",

Error

 ⨯ ../../node_modules/.pnpm/@libp2p+webrtc@4.0.33_react-native@0.74.1/node_modules/@libp2p/webrtc/dist/src/private-to-private/initiate-connection.js
Attempted import error: 'RTCSessionDescription' is not exported from '../webrtc/index.js' (imported as 'RTCSessionDescription').

Sample Codes


import { json } from "@helia/json";
import { Helia, createHelia } from "helia";
import { useCallback, useEffect, useState } from "react";

export type HeliaJsonResult = {
  helia: Helia | null;
  nodeId: string | null;
  nodeIsOnline: boolean;
  addJson: (jsonData: any) => Promise<string>;
  getJson: (cid: string) => Promise<any>;
  error: Error | null;
  loading: boolean;
};

export const useHeliaJson = (): HeliaJsonResult => {
  const [helia, setHelia] = useState<Helia | null>(null);
  const [nodeId, setNodeId] = useState<string | null>(null);
  const [nodeIsOnline, setNodeIsOnline] = useState<boolean>(false);
  const [error, setError] = useState<Error | null>(null);
  const [loading, setLoading] = useState<boolean>(true);

  useEffect(() => {
    const init = async () => {
      try {
        const heliaInstance = await createHelia();
        setHelia(heliaInstance);

        console.log("Helia instance: ", heliaInstance);

        const nodeId = heliaInstance.libp2p.peerId.toString();
        const nodeIsOnline = heliaInstance.libp2p.status === "started";
        setNodeId(nodeId);
        setNodeIsOnline(nodeIsOnline);

        setLoading(false);
      } catch (err) {
        setError(err as Error);
        setLoading(false);
      }
    };

    init();
  }, []);

  const addJson = useCallback(
    async (jsonData: any): Promise<string> => {
      if (!helia) {
        throw new Error("Helia instance is not initialized");
      }
      try {
        const cid = await json(helia).add(jsonData);
        return cid.toString();
      } catch (err) {
        setError(err as Error);
        throw err;
      }
    },
    [helia]
  );

  const getJson = useCallback(
    async (cid: string): Promise<any> => {
      if (!helia) {
        throw new Error("Helia instance is not initialized");
      }
      try {
        const data = await json(helia).get<any>(cid as any);
        return data;
      } catch (err) {
        setError(err as Error);
        throw err;
      }
    },
    [helia]
  );

  return {
    helia,
    nodeId,
    nodeIsOnline,
    addJson,
    getJson,
    error,
    loading,
  };
};

export default useHeliaJson;

``
adapole commented 3 weeks ago

I get the same error when using app-router, while I'm using 'use client'; and a ClientContext. But the error doesn't occur for page-router.

lixb-v commented 3 weeks ago

I had the same error

adapole commented 2 weeks ago

I checked what the cause was, and found the following.

Tldr; it's due to @libp2p/webrtc having ./dist/src/webrtc/index.js optimised to ./dist/src/webrtc/index.browser.js for browser environments. And on app-router it tries to use index.js on the browser environment instead of index.browser.js.

@libp2p "browser" field in a package.json file

Difference between index.js and index.browser.js.

index.js

export { RTCSessionDescription, RTCIceCandidate, RTCPeerConnection } from 'node-datachannel/polyfill';
//# sourceMappingURL=index.js.map

The index.browser.js file is a smaller version of index.js that only includes the functionality needed for the browser (by using Native WebRTC API of the browser from the globalThis object), reducing the overall bundle size.

index.browser.js

export const RTCPeerConnection = globalThis.RTCPeerConnection;
export const RTCSessionDescription = globalThis.RTCSessionDescription;
export const RTCIceCandidate = globalThis.RTCIceCandidate;
//# sourceMappingURL=index.browser.js.map

Lastly, Nextjs 14.2 polyfills states that "to reduce bundle size, Next.js will only load these polyfills for browsers that require them", so if index.js is used the node-datachannel/polyfill will not be imported, which is the cause of the bug here.

Possible fixes

  1. When using app-router, Force Nextjs to load node-datachannel/polyfill in next.config.mjs
    
    /** @type {import('next').NextConfig} */
    const nextConfig = {
    reactStrictMode: true,
    webpack: (config) => {
        config.externals.push('node-datachannel/polyfill');
        return config;
    },
    };

export default nextConfig;



2. Create a way to enforce the use of `index.browser.js` on `@libp2p/webrtc`
thachp commented 1 week ago

@adapole thx for looking into this. you are a life saver!