kozakdenys / qr-code-styling

Automaticly generate your styled QR code in your web app.
https://qr-code-styling.com
MIT License
1.57k stars 499 forks source link

self is not defined error with nextjs when used in backend #172

Closed sc0rp10n-py closed 1 month ago

sc0rp10n-py commented 1 year ago

this is my code

import cloudinary from "cloudinary";
import QRCodeStyling from "qr-code-styling";

cloudinary.v2.config({
    cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
    api_key: process.env.CLOUDINARY_API_KEY,
    api_secret: process.env.CLOUDINARY_API_SECRET,
});

const handler = async (req, res) => {
    if (req.method === "POST") {
        const { id } = req.body;
        try {
            const qrCode = new QRCodeStyling({
                width: 300,
                height: 300,
                type: "png",
                data: process.env.NEXT_PUBLIC_AUTH_URL+"view/" + id,
                image: "/logo.png",
                margin: 10,
                qrOptions: {
                    typeNumber: 0,
                    mode: "Byte",
                    errorCorrectionLevel: "Q",
                },
                imageOptions: {
                    hideBackgroundDots: true,
                    imageSize: 0.4,
                    margin: 0,
                    crossOrigin: "anonymous",
                },
                dotsOptions: {
                    color: "#1f2791",
                    type: "rounded",
                },
                backgroundOptions: {
                    color: "#ffffff",
                },
                cornersSquareOptions: {
                    color: "#f22d4e",
                    type: "extra-rounded",
                },
                cornersDotOptions: {
                    color: "#f22d4e",
                    type: "dot",
                },
            });
            const png = qrCode.png();
            const { secure_url } = await cloudinary.v2.uploader.upload(png, {
                folder: "qr-codes",
                public_id: id,
                overwrite: true,
                invalidate: true,
            });
            console.log(secure_url);
            res.status(200).json({ success: true, data: secure_url });
        } catch (error) {
            console.log(error);
            res.status(400).json({
                success: false,
                message: "Something went wrong!",
            });
        }
    } else {
        res.status(400).json({ success: false, message: "Invalid request" });
    }
};

export default handler;

i get this error

error - ReferenceError: self is not defined
    at Object.<anonymous> (/home/sc0rp10n/deeks/chain-trust/node_modules/qr-code-styling/lib/qr-code-styling.js:1:208)
    at Module._compile (node:internal/modules/cjs/loader:1099:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:999:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.qr-code-styling (/home/sc0rp10n/deeks/chain-trust/.next/server/pages/api/qr.js:32:18)
    at __webpack_require__ (/home/sc0rp10n/deeks/chain-trust/.next/server/webpack-api-runtime.js:33:42)
    at eval (webpack-internal:///(api)/./pages/api/qr.js:7:73)
    at Object.(api)/./pages/api/qr.js (/home/sc0rp10n/deeks/chain-trust/.next/server/pages/api/qr.js:42:1)
    at __webpack_require__ (/home/sc0rp10n/deeks/chain-trust/.next/server/webpack-api-runtime.js:33:42)
    at __webpack_exec__ (/home/sc0rp10n/deeks/chain-trust/.next/server/pages/api/qr.js:52:39)
    at /home/sc0rp10n/deeks/chain-trust/.next/server/pages/api/qr.js:53:28
    at Object.<anonymous> (/home/sc0rp10n/deeks/chain-trust/.next/server/pages/api/qr.js:56:3)
    at Module._compile (node:internal/modules/cjs/loader:1099:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:999:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at DevServer.runApi (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/next-server.js:507:34)
    at DevServer.handleApiRequest (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/next-server.js:878:21)
    at Object.fn (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/next-server.js:828:46)
    at async Router.execute (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/router.js:243:32)
    at async DevServer.runImpl (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/base-server.js:432:29)
    at async DevServer.run (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/dev/next-dev-server.js:831:20)
    at async DevServer.handleRequestImpl (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/base-server.js:375:20)
    at async /home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/base-server.js:157:99
FetchError: invalid json response body at http://localhost:3000/api/qr reason: Unexpected token < in JSON at position 0    at /home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/compiled/node-fetch/index.js:1:51220
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async handler (webpack-internal:///(api)/./pages/api/create-form.js:22:27)
    at async Object.apiResolver (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/api-utils/node.js:372:9)
    at async DevServer.runApi (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/next-server.js:514:9)
    at async Object.fn (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/next-server.js:828:35)
    at async Router.execute (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/router.js:243:32)
    at async DevServer.runImpl (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/base-server.js:432:29)
    at async DevServer.run (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/dev/next-dev-server.js:831:20)
    at async DevServer.handleRequestImpl (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/base-server.js:375:20)
    at async /home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/base-server.js:157:99 {
  type: 'invalid-json'
}
josipslavic commented 1 year ago

I haven't taken a look at the source code so this might be wrong, but this probably happens because qr-code-styling requires the Window object (aka, self) to be defined, which you don't have on the backend. So you can't really use this library in a NodeJS environment.

This is also why issues such as #89 happen on the frontend as well when using NextJS, because the Window object is not defined during SSR.

sc0rp10n-py commented 1 year ago

@josipslavic so is there any workaround for node js backend type environment?

ketz commented 1 year ago

also interested in this!

jzombie commented 1 year ago

Maybe JSDom could help you guys here: https://github.com/jsdom/jsdom

Disclaimer, I haven't tried it with this project but have used it with success before when needing to use other DOM-releated tools inside of Node.js.

You can probably use it to polyfill window, etc. as you need.


If this project requires a true DOM canvas, however, your only options may be to:

By far the easiest approach would be to run it on the client.

RomanSkrypnik commented 1 year ago

Getting the same error on Nuxt after upgrading it to version 3.4.2. Before updates it worked fine

josipslavic commented 1 year ago

I have done some playing around and came up with a band-aid solution for this problem (largely due to @jzombie and his jsdom proposition).

Here are a few things to note about this:

import cloudinary from 'cloudinary';
import streamifier from 'streamifier';
import QRCodeStyling from 'qr-code-styling';
import { JSDOM } from 'jsdom';
import nodeHtmlToImage from 'node-html-to-image';

cloudinary.v2.config({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
global.document = dom.window.document;
global.self = document.defaultView;
global.XMLSerializer = dom.window.XMLSerializer;

const handler = async (req, res) => {
  if (req.method === 'POST') {
    // Use dynamic imports so that qr-code-styling has access to our global polyfills
    import('qr-code-styling').then(async ({ default: QRCodeStyling }) => {
      const { id } = req.body;
      try {
        const qrCode = new QRCodeStyling({
          width: 300,
          height: 300,
          type: 'svg', // We have to create an svg that we could append to an HTML element
          data: process.env.NEXT_PUBLIC_AUTH_URL + 'view/' + id,
          // image: '/logo.png', Cannot set image due to Image being undefined (qr-code-styling uses new Image() under the hood)
          margin: 10,
          qrOptions: {
            typeNumber: 0,
            mode: 'Byte',
            errorCorrectionLevel: 'Q',
          },
          imageOptions: {
            hideBackgroundDots: true,
            imageSize: 0.4,
            margin: 0,
            crossOrigin: 'anonymous',
          },
          dotsOptions: {
            color: '#1f2791',
            type: 'rounded',
          },
          backgroundOptions: {
            color: '#ffffff',
          },
          cornersSquareOptions: {
            color: '#f22d4e',
            type: 'extra-rounded',
          },
          cornersDotOptions: {
            color: '#f22d4e',
            type: 'dot',
          },
        });

        // Append the QR code to some HTML element
        const htmlEl = dom.window.document.createElement('div');
        qrCode.append(htmlEl);

        // Convert that HTML to a Buffer
        const buffer = await nodeHtmlToImage({
          html: htmlEl.innerHTML,
        });

        // Upload buffer to cloudinary
        let cld_upload_stream = cloudinary.v2.uploader.upload_stream(
          { folder: 'some_folder' },
          function (error, result) {
            if (!error) {
              return res
                .status(200)
                .json({ success: true, url: result.secure_url });
            }
          }
        );
        streamifier.createReadStream(buffer).pipe(cld_upload_stream);
      } catch (error) {
        console.log(error);
        res.status(400).json({
          success: false,
          message: 'Something went wrong!',
        });
      }
    });
  } else {
    res.status(400).json({ success: false, message: 'Invalid request' });
  }
};

export default handler;
RomanSkrypnik commented 1 year ago

Getting the same error on Nuxt after upgrading it to version 3.4.2. Before updates it worked fine

Solved using dynamic import on client-side

onMounted(() => {
  import('qr-code-styling').then(({ default: QRCodeStyling }) => {
    qrCode = new QRCodeStyling(options);
    qrCode.append(qr.value);
  });
});
Zharkan commented 1 year ago

Well for Next.js at least, there's a way to bypass this using Dynamic imports :

const QrCode = dynamic(() => import('@/path/to/qrCode'), {
  ssr: false,
});
cresenciof commented 1 year ago

@josipslavic I used this fork https://github.com/Loskir/styled-qr-code and it works fine, this is my code, i am using NextJS 13.4.4

My repo: https://github.com/cresenciof/nextjs-qr-code-generator

// https://nextjs.org/docs/app/building-your-application/routing/router-handlers

import { NextResponse } from "next/server";

import { QRCodeCanvas, Options } from "@loskir/styled-qr-code-node";

const options: Partial<Options> = {
  width: 400,
  height: 400,
  data: "https://www.facebook.com/",
  image:
    "https://upload.wikimedia.org/wikipedia/commons/thumb/0/05/Facebook_Logo_%282019%29.png/1200px-Facebook_Logo_%282019%29.png",
  dotsOptions: {
    color: "#4267b2",
    type: "rounded",
  },
  backgroundOptions: {
    color: "#e9ebee",
  },
  imageOptions: {
    crossOrigin: "anonymous",
    margin: 20,
  },
};

export async function GET() {
  try {
    const qrCode = new QRCodeCanvas(options);

    const file = await qrCode.toDataUrl("png");

    return NextResponse.json({
      pnfFile: file,
    });
  } catch (error) {
    return NextResponse.json(
      { error: "Failed to generate QR code" },
      { status: 500 }
    );
  }
}

This is the component where I am rendering the API response:

"use client";
import { useState, useEffect } from "react";

function ImageViewer() {
  const [imageSrc, setImageSrc] = useState<string | null>(null);

  useEffect(() => {
    fetch("/api/generate")
      .then((response) => response.json())
      .then((data) => {
        if (data.pngFile) {
          // the pngFile contains the png image as a base64 string
          setImageSrc(data.pngFile);
        }
      })
      .catch((error) => {
        console.error("Error fetching image:", error);
      });
  }, []);

  if (!imageSrc) {
    return <div>Loading...</div>;
  }

  return <>{imageSrc && <img src={imageSrc} alt="SVG Image" />}</>;
}

export default ImageViewer;
image
natainditama commented 1 year ago

@josipslavic I used this fork https://github.com/Loskir/styled-qr-code and it works fine, this is my code, i am using NextJS 13.4.4

My repo: https://github.com/cresenciof/nextjs-qr-code-generator

// https://nextjs.org/docs/app/building-your-application/routing/router-handlers

import { NextResponse } from "next/server";

import { QRCodeCanvas, Options } from "@loskir/styled-qr-code-node";

const options: Partial<Options> = {
  width: 400,
  height: 400,
  data: "https://www.facebook.com/",
  image:
    "https://upload.wikimedia.org/wikipedia/commons/thumb/0/05/Facebook_Logo_%282019%29.png/1200px-Facebook_Logo_%282019%29.png",
  dotsOptions: {
    color: "#4267b2",
    type: "rounded",
  },
  backgroundOptions: {
    color: "#e9ebee",
  },
  imageOptions: {
    crossOrigin: "anonymous",
    margin: 20,
  },
};

export async function GET() {
  try {
    const qrCode = new QRCodeCanvas(options);

    const file = await qrCode.toDataUrl("png");

    return NextResponse.json({
      pnfFile: file,
    });
  } catch (error) {
    return NextResponse.json(
      { error: "Failed to generate QR code" },
      { status: 500 }
    );
  }
}

This is the component where I am rendering the API response:

"use client";
import { useState, useEffect } from "react";

function ImageViewer() {
  const [imageSrc, setImageSrc] = useState<string | null>(null);

  useEffect(() => {
    fetch("/api/generate")
      .then((response) => response.json())
      .then((data) => {
        if (data.pngFile) {
          // the pngFile contains the png image as a base64 string
          setImageSrc(data.pngFile);
        }
      })
      .catch((error) => {
        console.error("Error fetching image:", error);
      });
  }, []);

  if (!imageSrc) {
    return <div>Loading...</div>;
  }

  return <>{imageSrc && <img src={imageSrc} alt="SVG Image" />}</>;
}

export default ImageViewer;
image

Thanks

kozakdenys commented 1 month ago

Hi @dracula9906 @ketz @sc0rp10n-py @Zharkan @cresenciof @natainditama @josipslavic, sorry for missing for so long, and thank you for sharing the workarounds during this time.

node.js and next.js should work correctly in version v1.8.2 and above 🎉. You can find examples in this repo