Skip to content

qz-tray.js:768 Uncaught TypeError: Cannot read properties of undefined (reading '0') / ReactJS #1319

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Productivix opened this issue Feb 27, 2025 · 11 comments
Labels

Comments

@Productivix
Copy link

Productivix commented Feb 27, 2025

hi,
in ReactJS,
at line (95) : qz.security.setSignatureAlgorithm("SHA512"); // Since 2.1 -
I get the error on Chrome :

Image

qz-tray.js:768 Uncaught TypeError: Cannot read properties of undefined (reading '0')   

or on FireFox :

Uncaught TypeError: semver is undefined
    versionCompare qz-tray.js:768
    isVersion qz-tray.js:785
    algorithm qz-tray.js:967
    setSignatureAlgorithm qz-tray.js:2706
    App App.js:95"

I have the lib "qz-tray": "^2.2.4",
do you know where it comes from please ?
thanks

@tresf
Copy link
Contributor

tresf commented Feb 27, 2025

Please provide MINIMAL code to reproduce. Chances are you're calling setSignatureAlgorithm, setSignaturePromise or setCertificatePromise functions in the wrong place or not waiting for an asyc function to finish (improper use of promises/async/await). Ideally, the signing functions should be called once and then never called again.

Related: #538, #524

@Productivix Productivix changed the title qz-tray.js:768 Uncaught TypeError: Cannot read properties of undefined (reading '0') qz-tray.js:768 Uncaught TypeError: Cannot read properties of undefined (reading '0') / ReactJS Feb 28, 2025
@Productivix
Copy link
Author

the full code is may be complex because of Navigation, but for this part , I inspired from demo.qz.io
// see https://qz.io/api/qz.security
I am sure this is read 1 time according to my logs inserted ; the code is in App.js as in #1231 in your example https://playcode.io/2274692 but not in this order ;
exactly in App.js my code is :

 qz.security.setCertificatePromise((resolve) =>
    fetch("qzSign/digital-certificate.txt", {
      cache: "no-store",
      headers: { "Content-Type": "text/plain" },
    })
      .then((data) => data.text())
      .then((text) => resolve(text))
  );
  console.log(
    "setCertificatePromise passée, on a le certif repetable de qztray"
  );//this pass ok

  qz.security.setSignatureAlgorithm("SHA512"); // Since 2.1 - // causes the trouble "semver is undefined" (valeur à underfined sur FF), ou sur Chrome : Cannot read properties of undefined (reading '0')
  qz.security.setSignaturePromise(function (toSign) {
    return function (resolve, reject) {
      try {
        var wrapped = fetch("qzSign/private-key.pem", {
          cache: "no-store",
          headers: { "Content-Type": "text/plain" },
        });
        console.log("private key : ", wrapped.text());
        var pk = KEYUTIL.getKey(wrapped.text());
        var sig = new KJUR.crypto.Signature({ alg: "SHA512withRSA" }); // Use "SHA1withRSA" for QZ Tray 2.0 and older
        sig.init(pk);
        sig.updateString(toSign);
        var hex = sig.sign();
        console.log("DEBUG: \n\n" + stob64(hextorstr(hex)));
        resolve(stob64(hextorstr(hex)));
      } catch (err) {
        console.error(err);
        reject(err);
      }
    };
  }); 

@tresf
Copy link
Contributor

tresf commented Feb 28, 2025

qz.security.setCertificatePromise((resolve) =>
    fetch("qzSign/digital-certificate.txt", {
      cache: "no-store",
      headers: { "Content-Type": "text/plain" },
    })
      .then((data) => data.text())
      .then((text) => resolve(text))
  );
  console.log(
    "setCertificatePromise passée, on a le certif repetable de qztray"
  );//this pass ok

FYI, this console.log() is misplaced, it's outside of your promise chain.

        var wrapped = fetch("qzSign/private-key.pem", {
          cache: "no-store",
          headers: { "Content-Type": "text/plain" },
        });
        console.log("private key : ", wrapped.text());

Same here,the wrapped.text() is outside of your promise chain however this one is critical, you'll need to fix this.

return function (resolve, reject) {
    try {
[...]

Next, you're using try/catch, which is an anti-pattern when dealing with promises chains (they have natural support for this). If you prefer to use try/catch please change your code to use async/await.

Here's the code, rewritten with proper promise chain support.

qz.security.setCertificatePromise((resolve, reject) =>
    fetch('qzSign/digital-certificate.txt2', {
        cache: 'no-store',
        headers: { 'Content-Type': 'text/plain' },
    })
    .then(data => {
        if(data.ok) {
            return data.text();
        }
        throw new Error(data.status);
    })
    .then(text => {
        console.log("PASS: fetch('qzSign/digital-certificate.txt')");
        resolve(text);
    }).catch(err => {
        console.error("FAIL: fetch('qzSign/digital-certificate.txt')", err);
        reject(err);
    })
);

qz.security.setSignatureAlgorithm("SHA512");
qz.security.setSignaturePromise(function (toSign) {
    return function(resolve, reject) {
        fetch('qzSign/private-key.pem2', {
            cache: "no-store",
            headers: { "Content-Type": "text/plain" },
        })
        .then(data => {
            if(data.ok) {
                return data.text();
            }
            throw new Error(data.status + " qzSign/private-key.pem");
        })
        .then(text => {
            console.log("PASS: fetch('qzSign/private-key.pem')");
            var pk = KEYUTIL.getKey(text);
            var sig = new KJUR.crypto.Signature({ alg: "SHA512withRSA" });
            sig.init(pk);
            sig.updateString(toSign);
            var hex = sig.sign();
            console.log("DEBUG: \n\n" + stob64(hextorstr(hex)));
            resolve(stob64(hextorstr(hex)));
        })
        .catch(err => {
            console.error("A problem occured while signing", err);
            reject(err);
        })
    };
}); 
 // Since 2.1 - // causes the trouble "semver is undefined" (valeur à underfined sur FF), ou sur Chrome : Cannot read properties of undefined (reading '0')

I still cannot reproduce this error. Please provide a minimal reproducible code example.

@Productivix
Copy link
Author

Productivix commented Mar 3, 2025

hi,
thks I place all the code as you say there, I do have the same error as before.

If I remove the line that block :
qz.security.setSignatureAlgorithm("SHA512");

I have only in the console the error :

PASS: fetch('qzSign/private-key.pem') App.js:117
A problem occured while signing Error: PBKDF2 error: TypeError: b is undefined {"salt":"31dd87eb81ea29c1","iter":2048,"prf":"hmacWithSHA1","encalg":"aes256-CBC","enciv":"5eeaeb397b2281bbb3f828d24e869322","enc":"17cb482<snip>"} undefined
    getDKFromPBES2Param jsrsasign.js:240
    getPlainHexFromEncryptedPKCS8PEM jsrsasign.js:240
    getKeyFromEncryptedPKCS8PEM jsrsasign.js:240
    getKey jsrsasign.js:240
    App App.js:119
    promise callback*./src/App.js/App/</< App.js:115
    promise qz-tray.js:637
    callSign qz-tray.js:601
    sendData qz-tray.js:256
    promise callback*./node_modules/qz-tray/qz-tray.js/openConnection/_qz.websocket.connection.sendData qz-tray.js:255
    qz qz-tray.js:413
    promise qz-tray.js:637
    dataPromise qz-tray.js:404
    find qz-tray.js:1368
    loadPrinters QzPrinterSelector.js:14
    React 11
    js index.js:8
    factory react refresh:6
    Webpack 3
App.js:128
    App App.js:128
    (Asynchrone : promise callback)
    App App.js:126
    promise qz-tray.js:637
    callSign qz-tray.js:601
    sendData qz-tray.js:256
    (Asynchrone : promise callback)
    sendData qz-tray.js:255
    qz qz-tray.js:413
    promise qz-tray.js:637
    dataPromise qz-tray.js:404
    find qz-tray.js:1368
    loadPrinters QzPrinterSelector.js:14
    React 11
    js index.js:8
    factory react refresh:6
    Webpack 3

my code is so (small correction in reject, .pem instead of .pem2 :

  qz.security.setCertificatePromise((resolve) =>
    fetch("qzSign/digital-certificate.txt", {
      cache: "no-store",
      headers: { "Content-Type": "text/plain" },
    })
      .then((data) => {
        if (data.ok) {
          return data.text();
        }
        throw new Error(data.status);
      })
      .then((text) => {
        console.log("PASS: fetch('qzSign/digital-certificate.txt')");
        resolve(text);
      })
      .catch((err) => {
        console.error("FAIL: fetch('qzSign/digital-certificate.txt')", err);
        throw err;
      })
  );

  //qz.security.setSignatureAlgorithm("SHA512"); // Since 2.1
  qz.security.setSignaturePromise(function (toSign) {
    return function (resolve, reject) {
      fetch("qzSign/private-key.pem", {
        cache: "no-store",
        headers: { "Content-Type": "text/plain" },
      })
        .then((data) => {
          if (data.ok) {
            return data.text();
          }
          throw new Error(data.status + " qzSign/private-key.pem");
        })
        .then((text) => {
          console.log("PASS: fetch('qzSign/private-key.pem')");

          var pk = KEYUTIL.getKey(text);
          var sig = new KJUR.crypto.Signature({ alg: "SHA512withRSA" }); // Use "SHA1withRSA" for QZ Tray 2.0 and older
          sig.init(pk);
          sig.updateString(toSign);
          var hex = sig.sign();
          console.log("DEBUG: \n\n" + stob64(hextorstr(hex)));
          resolve(stob64(hextorstr(hex)));
        })
        .catch((err) => {
          console.error("A problem occured while signing", err);
          reject(err);
        });
    };
  });

I do understand the use of the line removed but do not understand why it is sending an error alone. Do we have both the same lib version of QZ ?

@tresf
Copy link
Contributor

tresf commented Mar 3, 2025

I do have the same error as before.

I do not believe this statement to be true. The error that you had before was:

Uncaught TypeError: semver is undefined

Now the error that you have is:

A problem occured while signing Error: PBKDF2 error: TypeError: b is undefined

...

I do understand the use of the line removed but do not understand why it is sending an error alone. Do we have both the same lib version of QZ ?

This error is not generated by QZ, it's generated by the crypto library (jsrsasign). Is there a chance you have a password protected private key? In our experience, that's not supported with jsrsasign.

@Productivix
Copy link
Author

ah yes it is ! I will remove that, and tell you , thanks !

@tresf
Copy link
Contributor

tresf commented Mar 3, 2025

@Productivix thanks for sharing your environment with me. I've filed a dedicated bug report for the issue you've discovered.

A workaround was to ensure setSignatureAlgorithm is only called once. You said on the call you would provide the ReactJS solution you develop here.

@Productivix
Copy link
Author

It works if the 2 conditions are respected :

  • security calls (3) are called only one time
  • qz.websocket.connect() being called 1 time but after security calls only

So the ReactJs structure of App.js is :

import React, { useState, useEffect } from "react";
import { KJUR, KEYUTIL, stob64, hextorstr } from "jsrsasign";
import * as qz from "qz-tray";

function App() {
const [securityDone, setsecurityDone] = useState(false);//to tell that security calls are finished

useEffect(() => {
      const security = () => {//3 security calls 
      qz.security.setCertificatePromise((resolve) =>
        fetch("qzSign/digital-certificate.txt", {
          cache: "no-store",
          headers: { "Content-Type": "text/plain" },
        })
          .then((data) => {
            if (data.ok) {
              return data.text();
            }
            throw new Error(data.status);
          })
          .then((text) => {
            console.log("PASS: fetch('qzSign/digital-certificate.txt')");
            resolve(text);
          })
          .catch((err) => {
            console.error("FAIL: fetch('qzSign/digital-certificate.txt')", err);
            throw err;
          })
      );
      qz.security.setSignatureAlgorithm("SHA512"); // Since 2.1 -but no password in key

      qz.security.setSignaturePromise(function (toSign) {
        return function (resolve, reject) {
          fetch("qzSign/private-key.pem", {
            cache: "no-store",
            headers: { "Content-Type": "text/plain" },
          })
            .then((data) => {
              if (data.ok) {
                return data.text();
              }
              throw new Error(data.status + " qzSign/private-key.pem");
            })
            .then((text) => {
              console.log("PASS: fetch('qzSign/private-key.pem')");

              var pk = KEYUTIL.getKey(text);
              var sig = new KJUR.crypto.Signature({ alg: "SHA512withRSA" }); // Use "SHA1withRSA" for QZ Tray 2.0 and older
              sig.init(pk);
              sig.updateString(toSign);
              var hex = sig.sign();
              console.log("DEBUG: \n\n" + stob64(hextorstr(hex)));
              resolve(stob64(hextorstr(hex)));
            })
            .catch((err) => {
              console.error("A problem occured while signing", err);
              reject(err);
            });
        };
      });
      console.log("end of security");
      setsecurityDone(true);
    };

    if (!securityDone) security();
  }, []); //done only at start of app.js

 useEffect(() => {
    const majFctBrowser = async () => {
     
        try {
          console.log("we connect to Qz");
          await qz.websocket.connect()
          if (qz.websocket.isActive()) {
            console.log(
              "state of Qz connection:",
              qz.websocket.isActive(),
              qz.websocket.getConnectionInfo()
            );
            //alert("Qz-tray Connected!")
          }
        } catch (error) {
          console.log("error from TC de qzTray:", error.message);
        }
      };

    if (securityDone) majFctBrowser();
  }, [securityDone]);//done only when security is done
}

hope this reseach will help you all .

@tresf
Copy link
Contributor

tresf commented Mar 3, 2025

@Productivix thanks, I'm sure this will help others. In the meantime, I'll try to harden the setSignatureAlgorithm to be able to handle the race condition that you've exposed.

@tresf tresf added the support label Mar 3, 2025
@tresf
Copy link
Contributor

tresf commented Mar 3, 2025

@Productivix please forgive my bad memory, this issue is already resolved on master. #1302.

@tresf tresf closed this as completed Mar 3, 2025
@Productivix
Copy link
Author

Rem : of course the 2 files qzSign/digital-certificate.txt & qzSign/private-key.pem are stored under public/qzSign in React Project (it is not the best practice for secrets, I agree)

@tresf tresf marked this as a duplicate of #1323 Mar 18, 2025
@tresf tresf marked this as not a duplicate of #1323 Apr 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants