opennextjs / opennextjs-cloudflare

Open Next.js adapter for Cloudflare
https://opennext.js.org/cloudflare
MIT License
378 stars 8 forks source link

[unenv] fs.readFile is not implemented yet! (sendgrid) #79

Closed justindjeumenet closed 1 week ago

justindjeumenet commented 1 month ago

Hey guys!

I am sending an email with attachment from rsc api folder and I got this. Does that mean this is something not yet in Worker? Any help guys!! Thank you

{
  "truncated": false,
  "outcome": "ok",
  "scriptVersion": {
    "id": "4a60b4e9-00b0-4112-96f5-f6e169246941"
  },
  "scriptName": "cloudflare-invoices-center",
  "diagnosticsChannelEvents": [],
  "exceptions": [],
  "logs": [
    {
      "message": [
        "Error uploading file:",
        "Error: [unenv] fs.readFile is not implemented yet!"
      ],
      "level": "error",
      "timestamp": 1728451378141
    }
  ],
  "eventTimestamp": 1728451378141,
  "event": {
    "request": {
      "url": "https://xxxxxxxxxxxxxxxxx/api/upload-profile-picture",
      "method": "POST",
      "headers": {
        "accept": "*/*",
        "accept-encoding": "gzip, br",
        "accept-language": "en-US,en-CA;q=0.8,en;q=0.5,fr-CA;q=0.3",
        "cf-connecting-ip": "24.72.1.140",
        "cf-ipcountry": "CA",
        "cf-ray": "8cfbef97aedd111d",
        "cf-visitor": "{\"scheme\":\"https\"}",
        "connection": "Keep-Alive",
        "content-length": "321568",
        "content-type": "multipart/form-data; boundary=---------------------------65224256623712046681452898732",
        "cookie": "REDACTED",
        "dnt": "1",
        "sec-fetch-dest": "empty",
        "sec-fetch-mode": "cors",
        "sec-fetch-site": "same-origin",
        "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0",
        "x-forwarded-proto": "https",
        "x-real-ip": "24.72.1.140"
      },
    "response": {
      "status": 500
    }
  },
  "id": 20
}
justindjeumenet commented 1 month ago

Screenshot 2024-10-09 at 12 18 57 AM

shashankboosi commented 1 month ago

Hello,

Here is the compatibility matrix of node apis with wrangler and workerd https://workers-nodejs-compat-matrix.pages.dev/

If you enable nodejs_compat with wrangler version >3.78.10. Most of the fs library should be available for you. I hope this helps :)

shashankboosi commented 1 month ago

Found this link, where it explains why you are getting the error and a space where you can propose the APIs that you want in workerd.

vicb commented 1 month ago

Here is the compatibility matrix of node apis with wrangler and workerd https://workers-nodejs-compat-matrix.pages.dev/

One caveat here is that the fs API are mocked because you can not access to a disk in the worker runtime. There will probably be some work on this to support a virtual file system in the future.

But for now and coming back to the problem, the issue is that some package is trying to access the file system (fs).

Can you setup your code to attach a file from somewhere else than the file system? (i.e. inline, network, ...)

justindjeumenet commented 1 month ago

Hey @shashankboosi and @vicb !! I am using node v22.9.0, wrangler version 2.80.1.

Here is my wrangler.toml file:

#:schema node_modules/wrangler/config-schema.json
name = "xxxxxxxxxxxxxxxxx"
main = ".worker-next/index.mjs"

compatibility_date = "2024-09-25"
compatibility_flags = ["nodejs_compat"]

# Minification helps to keep the Worker bundle size down and improve start up time.
minify = true

# Use the new Workers + Assets to host the static frontend files
assets = { directory = ".worker-next/assets", binding = "ASSETS" }

[[kv_namespaces]]
binding = "NEXT_CACHE_WORKERS_KV"
id = "xxxxxxxxxxxxxxxxxxxxxxxxx"

[observability]
enabled = true

Here is the code causing the issues:

export async function POST(req: Request) {
  sendgrid.setApiKey(process.env.SENDGRID_API_KEY!);

  try{
    const formData = await req.formData();

    const username = formData.get('username') as string;
    const description = formData.get('description') as string;
    const date = formData.get('date') as string;
    const fileCount = +(formData.get('file-count') as string);

    const files: File[] = [];

    for (let i = 0; i < fileCount; i++)
      files.push(formData.get('file_' + i) as File);

    const attachments = [];
    const id = nanoid();
    let i = 1;

    for (const file of files) {
      const arrayBuffer = await file.arrayBuffer();
      const name = `${id}_${i}`;
      const fileType = file.name.split('.').at(-1);

      const command = new PutObjectCommand({
        Bucket: process.env.AWS_BUCKET_NAME,
        Body: arrayBuffer,
        Key: `customers-support-images/${name}.${fileType}`
      });

      await s3Client.send(command);

      attachments.push({
        filename: file.name,
        content: Buffer.from(arrayBuffer).toString('base64'),
      });

      i++;
    }

    const html = await render(SupportTicketEmail({ username, description, date }));

    const options: any = {
      from: 'xxxxxxxxxxxxxxxxxxxxxx',
      to: [xxxxxxxxxxxxxxxxx],
      subject: `New Support Ticket from ${username}`,
      html,
      attachments
    };

    await sendgrid.send(options);

    // Return success response
    return new Response(JSON.stringify({ success: true, message: 'Email sent successfully' }), {
      headers: { 'Content-Type': 'application/json' },
      status: 200
    });
  } catch (error: any) {
    console.error('Error sending email:', error.message);
    return new Response(JSON.stringify({ success: false, message: 'Failed to send email', error: error.message }), {
      headers: { 'Content-Type': 'application/json' },
      status: 500
    });
  }
}
justindjeumenet commented 1 month ago

This piece of code works fine and is so fast with my actual cloud provider but I want migrate to cloudflare worker and "hopefully" reduce my cloud costs.

vicb commented 1 month ago

The issue seems to be linked to sendgrid accessing the file system.

One thing I would suggest: create a striped down example reproducing the issue starting from a simple worker example npm create cloudflare@latest. Make this repro available in a github repo.

They you can start a discussion about the sendgrid package here.

You can also check with the sendgrip community why they have to use the file system and if there is a way around.

justindjeumenet commented 1 month ago

Hey @vicb! It is not just SendGrid, I have the same issue when I try to attach a file and send it to AWS S3. Same error as well when I try to get a file from AWS S3. I am wondering if you guys have an example with reading a file or involving the module fs. thanks

vicb commented 1 month ago

Ah, sorry for the misunderstanding, you can not use fs because the runtime does not have access to disk. You can download an S3 file to a Buffer or send a Buffer to S3 not you will not be access to save/load it to/from disk because there is no disk. Does that make sense?

justindjeumenet commented 1 month ago

@vicb Sorry I do not understand your explanation. The code I provided above is what recommended by AWS S3 and SendGrid. You are asking to do differently and do you have an example of code please? I am trying to follow the code example from AWS S3 and SendGrid. I will appreciate it so much if you can provide an example code please.

Thanks

vicb commented 1 month ago

I don't. Maybe try to check other SendGrid sample. You can also ask on the Cloudflare Discord. Hope this helps

vicb commented 1 week ago

Not related to OpenNext (but to node compatibility)