davidyack / Xrm.Tools.CRMWebAPI

This is an API helper for working with the Common Data Service (CDS) Web API
MIT License
143 stars 73 forks source link

urllib not set and exception thrown when using nodejs version but bundled into web app using webpack #43

Open aappddeevv opened 7 years ago

aappddeevv commented 7 years ago

urllib is not set when using new CRMWebAPI({APIUrl: '...'}} and causes an exception to be thrown in

CRMWebAPI.prototype._GetHttpRequestHTTPS = function (config, method, url, payload, callback) {
        var parsed_url = this.urllib.parse(url);

If webpack is bundling, it appears that even if its running in the browser, crmwebapi thinks its running undre node.

aappddeevv commented 7 years ago

I've not found a workaround after trying some webpack trickery as the init function directly references modules in the init part of the script to determine if its running under node. If it only mentioned module and exports in the module init portion we would be ok I think. It appears to be an unnecessary dependency on module/exports variables as you could just check to see if the two parameters are not-nil.

davidyack commented 7 years ago

@aappddeevv sorry I didn't see this one come in previously, do you have a small project that demonstrates the problem that you could share?

aappddeevv commented 7 years ago

I don't. I only have a huge project really. I tried using a webpack hack that works with other libraries that have the same problem:

 {
                test: /CRMWebAPI/,
                use: "imports-loader?define=>false,exports=>false,this=>window"
            }

But the issues was the unnecessary test against exports/module inside the init function. I think you can just test for the two parameters.

aappddeevv commented 6 years ago

I think script-loader may work for webpack and js. It evaluates an imported module as a string using eval() which should remove any vestiges of modules/exports from the UMD module evaluation inside the eval....however this does not seem to translate into typescript well e.g. the consumer is a typescript module (.ts).

I have to require("CRMWebAPI") then "import CRMWebAPI = require("CRMWebAPI") to get webpack's script loader to activate and the 2nd to pull in the .d.ts. However, under the webpack bundle CRMWebAPI comes up null for some reason inside the consuming ts file.

aappddeevv commented 6 years ago

I created a pure typescript webpack based build for CRMWebAPI that may be able to work. If you use compilation flags you can kick out UMD js and es6 modules. Also, there were some missing declares in the typescript .d.ts file. I think the issue is that the bundling options for a single isomorphic .js file probably won't work. Also, you can kick out a min version.

paulbreuler commented 6 years ago

I just ran into this issue using while setting up a new react project, though I'm not as proficient as aappddeevv at resolving the issue on my own.

Fails for the following snippet.

    let apiconfig = {
      APIUrl: "https://contoso.crm.dynamics.com/api/data/v9.0/",
      AccessToken: authContext.getCachedToken(authContext.config.clientId),
    };
    let crmAPI = new CRMWebAPI(apiconfig);

    var params = {
      Country: "US",
      Amount: 500
    };

    crmAPI.ExecuteAction("xt_CalculateDiscount", params).then(
      function(r) {
        console.log(r);
      },
      function(e) {
        console.log(e);
      }
    );
TypeError: Cannot read property 'parse' of undefined
    at CRMWebAPI._GetHttpRequestHTTPS (CRMWebAPI.js:460)
    at CRMWebAPI.js:342
    at new Promise (<anonymous>)
    at CRMWebAPI.ExecuteAction (CRMWebAPI.js:336)
    at App.render (App.js:22)
    at finishClassComponent (react-dom.development.js:13085)
    at updateClassComponent (react-dom.development.js:13047)
    at beginWork (react-dom.development.js:13715)
    at performUnitOfWork (react-dom.development.js:15741)
    at workLoop (react-dom.development.js:15780)
    at renderRoot (react-dom.development.js:15820)
    at performWorkOnRoot (react-dom.development.js:16437)
    at performWork (react-dom.development.js:16358)
    at performSyncWork (react-dom.development.js:16330)
    at requestWork (react-dom.development.js:16230)
    at scheduleWork$1 (react-dom.development.js:16096)
    at scheduleRootUpdate (react-dom.development.js:16663)
    at updateContainerAtExpirationTime (react-dom.development.js:16690)
    at updateContainer (react-dom.development.js:16717)
    at ReactRoot../node_modules/react-dom/cjs/react-dom.development.js.ReactRoot.render (react-dom.development.js:17000)
    at react-dom.development.js:17140
    at unbatchedUpdates (react-dom.development.js:16557)
    at legacyRenderSubtreeIntoContainer (react-dom.development.js:17136)
    at Object.render (react-dom.development.js:17195)
    at index.js:9
    at runWithAdal (react-adal.js:57)
    at Object../src/index.js (index.js:8)
    at __webpack_require__ (bootstrap 3180c7fd1cfef6d3ab5a:678)
    at fn (bootstrap 3180c7fd1cfef6d3ab5a:88)
    at Object.0 (logo.svg:1)
    at __webpack_require__ (bootstrap 3180c7fd1cfef6d3ab5a:678)
    at bootstrap 3180c7fd1cfef6d3ab5a:724
    at bootstrap 3180c7fd1cfef6d3ab5a:724

using create-react-app with the following package.json

{
  "name": "playground",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "CRMWebAPI": "^1.3.6",
    "adal-node": "^0.1.28",
    "react": "^16.4.0",
    "react-adal": "^0.4.18",
    "react-dom": "^16.4.0",
    "react-scripts": "1.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject",
    "precommit": "pretty-quick --staged"
  },
  "devDependencies": {
    "eslint-config-prettier": "^2.9.0",
    "eslint-plugin-prettier": "^2.6.0",
    "husky": "^0.14.3",
    "lint-staged": "^7.1.3",
    "prettier": "^1.13.4"
  }
}
paulbreuler commented 6 years ago

For now, I just hard-coded the required libraries since I'm just using this for testing at the moment.

let myUrl = require('url');
let myHttps = require('https');
...
if (typeof module !== 'undefined' && module.exports) {
                this.node = true;
                this.https = myHttps;
                this.urllib =  myUrl;
jnaquin commented 5 years ago

For anyone who encounters this issue, below is the webpack configuration I use to work around the issue with the npm package not working in the browser.

var StringReplacePlugin = require("string-replace-webpack-plugin");

module.exports = {
    module: {
        rules: [
            {
                test: /CRMWebAPI.js/,
                loader: StringReplacePlugin.replace({
                    replacements: [
                        {
                            pattern: /typeof module !== 'undefined' && module.exports/,
                            replacement: function(match, offset, string) {
                                return "false";
                            }
                        }
                    ]
                })
            }
        ]
    },
}