Cannot get the correct asyncLocalStorage for a function

172 Views Asked by At

I have a web application with reactjs and node. Visitors can choose their language for the interface of the frontend. We use i18next to manage that in the frontend.

To sign up on the web application, a visitor needs to enter their email, we will send them a verification email with a link like https://localhost:3000/1/auth/verification/1ab3d0f189191?language=zh. He then finds out the email and clicks on the link to validate his email. In the end, we send him a congratulation email for signing up.

The emails need to be written in the language set in the verification link (which is made based on the language sent from the frontend of a visitor). I would like to use AsyncLocalStorage to store the language which can be accessible from different functions in the backend.

Here is Context.js:

const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
module.exports = asyncLocalStorage;

Here is the middleware to save the language

router.use((req, res, next) => {
    asyncLocalStorage.run(new Map(), () => {
        console.log("asyncLocalStorage, previous language:", asyncLocalStorage.getStore().get('language'));
        const languageFromBody = req.body.language; // for requests such as register
        const languageFromQuery = req.query.language; // for links such as /auth/verification/
        const language = languageFromBody || languageFromQuery;
        asyncLocalStorage.getStore().set('language', language);
        console.log("asyncLocalStorage, current language:", language);
        next();
    });
});

The endpoint of the verification link like https://localhost:3000/1/auth/verification/1ab3d0f189191?language=zh sent to visitors:

router.get('/' + process.env.versionNumber + '/auth/verification/:code', function (req, res) { //HANDLE VERIFICATION
    const language = asyncLocalStorage.getStore()?.get('language') ?? 'en';
    const i18n = language === "zh" ? i18next_zh : i18next_en;
    console.log("language in router:", language);

    userModel.verificationAccount(req.params.code)
        .then(data => {
            res.send(i18n.t('Verified successfully! You can now sign in with your credentials for our tools'))
        })
        .catch(err => {
            res.send(err)
        })
});

verificationAccount in userModel:

    verificationAccount (code) {
        const language = asyncLocalStorage.getStore()?.get('language') ?? 'en';
        console.log("language in verificationAccount:", language);

        return new Promise((resolve, reject) => {
            this.user.find({ "local.verificationCodes": { $in: [code] } }, { }, (err, users) => {
                if (err) {
                    console.log("Error in verificationAccount:", err);
                    reject("Database error");
                    return
                }
                if (users.length > 0) {
                    if (users[0].local.verified === 'no') {
                        this.user.findByIdAndUpdate(users[0]._id, { $set: { "local.verified": 'yes' } }, { new: true }, (err, userAfterChanged) => {
                            if (err) {
                                console.log("Error in verificationAccount update:", err);
                                reject("Update error");
                                return
                            }
                            verfModule.sendNewUserSignUpSuccess(users[0].local.id, users[0].currentName, users[0].currentProvider); 
                            resolve(userAfterChanged)
                        })
                    }
                } else {
                    reject('not found the code of verification')
                }
            })
        })
    }

sendNewUserSignUpSuccess in verfModule:

  sendNewUserSignUpSuccess (email, currentName, currentProvider) {
    const language = asyncLocalStorage.getStore()?.get('language') ?? 'en';
    const i18n = language === "zh" ? i18next_zh : i18next_en;
    console.log("language in sendNewUserSignUpSuccess:", language);

    return emailService.sendMail({
      to: email,
      subject: i18n.t('Congratulations! You have successfully signed up!'),
      html: "<div>body</div>",
      text: "text",
      category: 'sign_up_success',
      unique_args: {
        user_email: email
      },
    });
  }

Test logs in the console:

enter image description here

My tests show that, because of language=zh in the verification link, after the visitor's click on the link, language in asyncLocalStorage is set as zh. language in router: zh and language in verificationAccount: zh are correct. By contrast, language in sendNewUserSignUpSuccess: en is not correct. As a result, the title of the congratulation email sent is still in English.

Does anyone know why we cannot correctly get the language of asyncLocalStorage in sendNewUserSignUpSuccess?

0

There are 0 best solutions below