Skip to content
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

Angular hack with node: {crypto: true} doesn't work anymore in Angular 12 #4070

Closed
dmitry-salnikov opened this issue May 17, 2021 · 23 comments
Closed
Assignees
Labels
1.x 1.0 related issues

Comments

@dmitry-salnikov
Copy link

dmitry-salnikov commented May 17, 2021

Angular 12 has migrated to Webpack 5 and made a bunch of other changes to it's build system. Now the fix mentioned in readme - #2260 (comment) - has stopped to work: after modifying node_modules/@angular-devkit/build-angular/src/webpack/configs/browser.js by replacing node: false with node: {crypto: true, stream: true}, build fails with the next error:

[ng] An unhandled exception occurred: Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
[ng]  - configuration.node should be one of these:
[ng]    false | object { __dirname?, __filename?, global? }
[ng]    -> Include polyfills or mocks for various node stuff.
[ng]    Details:
[ng]     * configuration.node has an unknown property 'crypto'. These properties are valid:
[ng]       object { __dirname?, __filename?, global? }
[ng]       -> Options object for node compatibility features.
[ng]     * configuration.node has an unknown property 'stream'. These properties are valid:
[ng]       object { __dirname?, __filename?, global? }
[ng]       -> Options object for node compatibility features.
[ng]     * configuration.node has an unknown property 'os'. These properties are valid:
[ng]       object { __dirname?, __filename?, global? }
[ng]       -> Options object for node compatibility features.
[ng]     * configuration.node has an unknown property 'http'. These properties are valid:
[ng]       object { __dirname?, __filename?, global? }
[ng]       -> Options object for node compatibility features.
[ng]     * configuration.node has an unknown property 'https'. These properties are valid:
[ng]       object { __dirname?, __filename?, global? }
[ng]       -> Options object for node compatibility features.
[ng]     * configuration.node has an unknown property 'assert'. These properties are valid:
[ng]       object { __dirname?, __filename?, global? }
[ng]       -> Options object for node compatibility features.
[ng] See "/private/var/folders/hn/608xz_1x3rn_6q0xptx_lm100000gn/T/ng-RDkDB8/angular-errors.log" for further details.

Seems like some validation was added to browser config.

Could you please take a look for possible workarounds?

Environment

Angular 12.0.0
web3 1.3.6

@NicolasKritter
Copy link

NicolasKritter commented May 18, 2021

Hi, i spent 2 days on the same issue and found a solution.

in the same file: node_modules/@angular-devkit/build-angular/src/webpack/configs/browser.js you should add this property in the resolve part (line 51) such as:

fallback:{
    http: require.resolve("stream-http"),
    https: require.resolve("https-browserify"),
    crypto: require.resolve("crypto-browserify"),
    stream:require.resolve("stream-browserify"),
    os:require.resolve("os-browserify/browser"),
    assert:require.resolve("assert/"),
}

once you defined the resolve.fallback, you need to manually install theses packages (npm i stream-http...) (when you try co compile, angular will tell you which package to install.
I tried just setting the package to false like crypto:false in the fallback it it creates other errors

Secondly, I had other issue to fix such as global is not defined, Buffer is not defined,process is not defined) after the compilation when i go the the localhost:4200

solution:
in index.html

<script>
  var global = global || window; //open this if you take global error

  var process = process || {
    env: {
      DEBUG: undefined
    },
    version: ''
  };
</script>

in polyfills.ts:

just put
global.Buffer = global.Buffer || require('buffer').Buffer;

I also tried this "cleaner way"

import { Buffer } from 'buffer';
global.Buffer = Buffer;

It works now, I probably will find a better way to handle this. (probably we can disable some of the fallback by setting them to false).

This problem is caused by webpack 5 not including the node polyfills anymore.

this seems to be the reason:
https://github.com/webpack/changelog-v5/blob/master/README.md#automatic-nodejs-polyfills-removed

NOTE: you will need to edit the browsers.js after each npm i if it reinstall angular, i did this script to do it automagically:
and put in package.json:
"postinstall": "node patch.js && ngcc"

/* eslint-disable @typescript-eslint/no-var-requires */

const {
  readFileSync,
  writeFileSync,
  existsSync
} = require('fs');

const fix = `fallback:{
  http: require.resolve("stream-http"),
  https: require.resolve("https-browserify"),
  crypto: require.resolve("crypto-browserify"),
  stream:require.resolve("stream-browserify"),
  os:require.resolve("os-browserify/browser"),
  assert:require.resolve("assert/")
},`;


const patchTag = '//-patched';

const fileToPatch = 'node_modules/@angular-devkit/build-angular/src/webpack/configs/browser.js';
const codeToPatch = 'mainFields: [\'es2015\', \'browser\', \'module\', \'main\'],';
const patch =codeToPatch+ fix +patchTag;


function getAllIndexes(arr, val) {
  const indexes = [];
  let  i;
  for (i = 0; i < arr.length; i++)
  {if (arr[i].indexOf(val) !== -1)
  {indexes.push(i);}}
  return indexes;
}
function doPatch(fileName, sourceCode, patchCode, patchIdentifier) {
  if (!existsSync(fileName)) {
    console.log('file not found ' + fileName);
    return;
  }
  const contents = readFileSync(fileName).toString().split('\n');
  // Check if code has been patched already
  const hasBeenPatched = contents.find(line => line.indexOf(patchIdentifier) !== -1);

  if (!hasBeenPatched) {
    const lineNumbers = getAllIndexes(contents, sourceCode);
    if (lineNumbers.length < 1) {
      console.error('Could not find source code. Please check ' + fileName + ' and update the patch accordingly');
      return;
    }
    // replace the line
    lineNumbers.forEach((lineNumber) => {
      contents.splice(lineNumber, 1, patchCode);
    });
    const updatedContents = contents.join('\n');
    writeFileSync(fileName, updatedContents);

    console.log('Monkey patched');
  } else {
    console.log('already been patched');
  }
}
doPatch(fileToPatch, codeToPatch, patch, patchTag);

Cheers

@dmitry-salnikov
Copy link
Author

dmitry-salnikov commented May 18, 2021

@NicolasKritter Thanks for sharing your solution, I've fixed the process and Buffer errors the way you've described.

There is another solution to provide node polyfills. Instead of modifying node_modules/@angular-devkit/build-angular/src/webpack/configs/browser.js and adding postinstall script to provide resolve.fallback, you can specify paths property in tsconfig.json, e.g.:

tsconfig.json

{
  "compilerOptions": {
    "paths" : {
      "crypto": ["./node_modules/crypto-browserify"],
      "stream": ["./node_modules/stream-browserify"],
      "assert": ["./node_modules/assert"],
      "http": ["./node_modules/stream-http"],
      "https": ["./node_modules/https-browserify"],
      "os": ["./node_modules/os-browserify"],
    }
  }
}

Of course you still need to install mentioned packages, as you've mentioned above.

This makes the solution a bit cleaner. Hope it helps.

@NicolasKritter
Copy link

NicolasKritter commented May 18, 2021

Thanks @dmitry-salnikov This is a better solution as it does not require a dirty fix in the angular installation and is commited

I just had to change the pasth from ./node_modules/crypto-browserify to ../node_modules/crypto-browserify as I use "src" as baseUrl

@GregTheGreek
Copy link
Contributor

Sorry for the delay, we're looking into this, thanks for the report and debugging.

@pcaversaccio
Copy link

pcaversaccio commented May 28, 2021

Thanks, @NicolasKritter & @dmitry-salnikov your solutions worked like charm. I would like to add that one needs to first install the required dependencies using

npm install crypto-browserify stream-browserify assert stream-http https-browserify os-browserify

and thereafter using the solution by @dmitry-salnikov.

Now I'm facing the next issue. I'm currently building an Angular 12 based ETH faucet frontend for a private Ethereum blockchain. I can easily connect with e.g. MetaMask using web3.js (based on your great debugging!).

However, whenever I try to execute a contract call I get the following error:

core.js:6456 ERROR Error: Uncaught (in promise): TypeError: process.nextTick is not a function
TypeError: process.nextTick is not a function
    at util.js:706
    at ZoneDelegate.invoke (zone.js:372)
    at Object.onInvoke (core.js:28667)
    at ZoneDelegate.invoke (zone.js:371)
    at Zone.run (zone.js:134)
    at zone.js:1276
    at ZoneDelegate.invokeTask (zone.js:406)
    at Object.onInvokeTask (core.js:28654)
    at ZoneDelegate.invokeTask (zone.js:405)
    at Zone.runTask (zone.js:178)
    at resolvePromise (zone.js:1213)
    at zone.js:1283
    at ZoneDelegate.invokeTask (zone.js:406)
    at Object.onInvokeTask (core.js:28654)
    at ZoneDelegate.invokeTask (zone.js:405)
    at Zone.runTask (zone.js:178)
    at drainMicroTaskQueue (zone.js:582)

The function I'm executing is the following:

  async sendETH() {
    await this.onClickConnect();
    if ((typeof window.ethereum !== 'undefined') || (typeof window.web3 !== 'undefined')) {
      const provider = window['ethereum'] || window.web3.currentProvider;
      this.web3 = new Web3(provider);
    } else {
      this.web3 = new Web3(new Web3.providers.WebsocketProvider(infuraWebsocket));
    }
    const contract = new this.web3.eth.Contract(contractAbi, contractAddress);
    const tx = await contract.methods.sendFunds().send({ from: this.accounts[0] });
    return tx;
  }

There is no MetaMask popup with the transaction to be signed.

What is even more strange, when I call a simple ETH send transaction such as:

    const tx = await this.web3.eth.sendTransaction({
      from: this.accounts[0],
      to: '0x2f318C334780961FB129D2a6c30D0763d9a5C970',
      value: '0',
      gas: 21000,
      gasPrice: 20000000000,
    }, (result: any) => {
      console.log(result)
    });
    return tx;

the MetaMask pops up here and I can successfully sign the transaction but after that I see the same error about process.nextTick.

My index.html includes the script:

  <script>
    var global = global || window; //open this if you take global error
    var process = process || {
      env: {
        DEBUG: undefined
      },
      version: ''
    };
  </script>

and in my polyfills.ts I have the following code:

/***************************************************************************************************
 * Zone JS is required by default for Angular itself.
 */
import 'zone.js';  // Included with Angular CLI.


/***************************************************************************************************
 * APPLICATION IMPORTS
 */
 (window as any).global = window;
 import { Buffer } from 'buffer';
 global.Buffer = Buffer;

The problem message in the console disappears once I make the following change, but this does not help to get to the MetaMask pop-up:

  <script>
    var global = global || window; //open this if you take global error
    var process = process || {
      env: {
        DEBUG: undefined
      },
      version: '',
      nextTick: function() {
         return null;
   }
    };
  </script>

Does anyone know how to deal with this - I've spent 1 day already debugging without any result.

@pcaversaccio
Copy link

pcaversaccio commented May 29, 2021

Ok guys, after another couple of hours of debugging a finally reached the solution.

Solution:
Remove the script (see above from @NicolasKritter) from the index.html file and add the following lines to the polyfills.ts file:

 (window as any).global = window;
 import { Buffer } from 'buffer';
 global.Buffer = Buffer;
 global.process = {
    env: { DEBUG: undefined },
    version: '',
    nextTick: require('next-tick')
    } as any;

@NicolasKritter
Copy link

Yes it is better to move all the code to polyfills.ts thanks for this findings.

Having a quick fix in the index.html bring also some csp problems (need to add the hash of the script)

so this best solution is :

  • modifying the tsconfig with the dependencies script and install them manually
  • update the polyfills to fix the buffer and process problem

@GregTheGreek
Copy link
Contributor

We're rushing to get hardfork support out + this new webpack issue. Can I ask if someone can verify the output is similar to what was reported in #4090 ?

@pcaversaccio
Copy link

@GregTheGreek I didn't check every line but it definitely looks very similar to what I had in the terminal.

@dmitriyzhuk
Copy link

Hi guys, I guess this is still in the works

App •  ERROR  •  UI  in ./node_modules/eth-lib/lib/bytes.js

Module not found: Can't resolve imported dependency "crypto"
Did you forget to install it? You can run: npm install --save crypto

 App •  ERROR  •  UI  in ./node_modules/web3-eth-accounts/lib/index.js

Module not found: Can't resolve imported dependency "crypto"
Did you forget to install it? You can run: npm install --save crypto

 App •  ERROR  •  UI  in ./node_modules/web3-eth-accounts/node_modules/eth-lib/lib/bytes.js

Module not found: Can't resolve imported dependency "crypto"
Did you forget to install it? You can run: npm install --save crypto

 App •  ERROR  •  UI  in ./node_modules/web3-providers-http/lib/index.js

Module not found: Can't resolve imported dependency "http"
Did you forget to install it? You can run: npm install --save http

 App •  ERROR  •  UI  in ./node_modules/web3-providers-http/lib/index.js

Module not found: Can't resolve imported dependency "https"
Did you forget to install it? You can run: npm install --save https

 App •  ERROR  •  UI  in ./node_modules/xhr2-cookies/dist/xml-http-request.js

Module not found: Can't resolve imported dependency "http"
Did you forget to install it? You can run: npm install --save http

 App •  ERROR  •  UI  in ./node_modules/xhr2-cookies/dist/xml-http-request.js

Module not found: Can't resolve imported dependency "https"
Did you forget to install it? You can run: npm install --save https

my package.json

{
  "name": "foobar",
  "version": "0.0.1",
  "description": "",
  "private": true,
  "scripts": {
    "dev": "quasar dev",
    "build": "quasar build",
    "lint": "eslint --ext .js,.ts,.vue ./",
    "test": "echo \"No test specified\" && exit 0"
  },
  "dependencies": {
    "@quasar/extras": "^1.0.0",
    "assert": "^2.0.0",
    "axios": "^0.21.1",
    "core-js": "^3.6.5",
    "crypto": "^1.0.1",
    "crypto-browserify": "^3.12.0",
    "http": "0.0.1-security",
    "https": "^1.0.0",
    "https-browserify": "^1.0.0",
    "lodash-es": "^4.17.21",
    "moment": "^2.29.1",
    "moment-timezone": "^0.5.33",
    "os": "^0.1.1",
    "os-browserify": "^0.3.0",
    "pinia": "^2.0.0-alpha.19",
    "quasar": "^2.0.0-beta.1",
    "stream": "0.0.2",
    "stream-browserify": "^3.0.0",
    "stream-http": "^3.2.0",
    "vue-clamp": "^0.3.2",
    "vue-i18n": "^9.1.6",
    "web3": "^1.3.6"
  },
  "devDependencies": {
    "@babel/eslint-parser": "^7.13.14",
    "@quasar/app": "^3.0.0-beta.1",
    "@types/lodash-es": "^4.17.4",
    "@types/node": "^10.17.15",
    "@typescript-eslint/eslint-plugin": "^4.16.1",
    "@typescript-eslint/parser": "^4.16.1",
    "axios-mock-adapter": "^1.19.0",
    "eslint": "^7.14.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-vue": "^7.9.0"
  },
  "browserslist": [
    "last 10 Chrome versions",
    "last 10 Firefox versions",
    "last 4 Edge versions",
    "last 7 Safari versions",
    "last 8 Android versions",
    "last 8 ChromeAndroid versions",
    "last 8 FirefoxAndroid versions",
    "last 10 iOS versions",
    "last 5 Opera versions"
  ],
  "engines": {
    "node": ">= 12.22.1",
    "npm": ">= 6.13.4",
    "yarn": ">= 1.21.1"
  }
}

node version v14.16.1

@github-actions
Copy link

github-actions bot commented Aug 9, 2021

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions. If you believe this was a mistake, please comment.

@github-actions github-actions bot added the Stale Has not received enough activity label Aug 9, 2021
@pcaversaccio
Copy link

@GregTheGreek not sure this issue is resolved with the web3.js v1.5.x releases. Could you or someone else confirm?

@GregTheGreek
Copy link
Contributor

Cc @luu-alex

@luu-alex
Copy link
Contributor

luu-alex commented Aug 9, 2021

@pcaversaccio This issue is not resolved in 1.5.x yet.

@github-actions github-actions bot removed the Stale Has not received enough activity label Aug 10, 2021
@newcreativegamer
Copy link

Ok guys, after another couple of hours of debugging a finally reached the solution.

Solution:
Remove the script (see above from @NicolasKritter) from the index.html file and add the following lines to the polyfills.ts file:

 (window as any).global = window;
 import { Buffer } from 'buffer';
 global.Buffer = Buffer;
 global.process = {
    env: { DEBUG: undefined },
    version: '',
    nextTick: require('next-tick')
    } as any;

Absolute magic, thank you!

@philknows philknows added the 1.x 1.0 related issues label Sep 2, 2021
@mizzyexists
Copy link

Any progress on this? I see the above temp fix is working but just wanted to check in.

@luu-alex
Copy link
Contributor

Its been a while but we recently picked this issue back up, hopefully we find a more permanent solution for this soon

@theklayd
Copy link

this worked on me :

#1555 (comment)

@luu-alex
Copy link
Contributor

Thank you all, the web3js README now includes the new instructions to get Angular 12 working with web3js. I'll close this issue for now.

@mkamranaslam
Copy link

mkamranaslam commented Nov 3, 2021

@luu-alex
Hi! can someone help, please.
I have tried the paths and also the patch.js fix but both give error on build:

`An unhandled exception occurred: Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.

  • configuration.node should be one of these:
    false | object { __dirname?, __filename?, global? }
    -> Include polyfills or mocks for various node stuff.
    Details:
    • configuration.node has an unknown property 'crypto'. These properties are valid:
      object { __dirname?, __filename?, global? }
      -> Options object for node compatibility features.
    • configuration.node has an unknown property 'stream'. These properties are valid:
      object { __dirname?, __filename?, global? }
      -> Options object for node compatibility features.
    • configuration.node has an unknown property 'fs'. These properties are valid:
      object { __dirname?, __filename?, global? }
      -> Options object for node compatibility features.
    • configuration.node has an unknown property 'net'. These properties are valid:
      object { __dirname?, __filename?, global? }
      -> Options object for node compatibility features.`

@dmangesh1109
Copy link

Ok guys, after another couple of hours of debugging a finally reached the solution.
Solution:
Remove the script (see above from @NicolasKritter) from the index.html file and add the following lines to the polyfills.ts file:

 (window as any).global = window;
 import { Buffer } from 'buffer';
 global.Buffer = Buffer;
 global.process = {
    env: { DEBUG: undefined },
    version: '',
    nextTick: require('next-tick')
    } as any;

Absolute magic, thank you!

Thanks a lot. This is working fine.

@attilacsanyi
Copy link

Yepp since 2021 September 30 it is in the web3.js README as well 💚

@milesholt
Copy link

I get the error - file not found node_modules/@angular-devkit/build-angular/src/webpack/configs/browser.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
1.x 1.0 related issues
Projects
None yet
Development

No branches or pull requests