Search code examples
javascriptnode.js.netvb.netclearscript

'x is not defined' error in Microsoft ClearScript


I would like to create a basic .NET wrapper for the xkpasswd JavaScript library (see generate.js source-code here, and the live demo here). To accomplish this, I am using Microsoft ClearScript.

I have two problems. The first of them I think it is solved or partially solved...

The first problem I had is when trying to execute the generate.js file, I get an error message: "require is not defined". I tried to solve the error by setting the document category to ModuleCategory.CommonJS when executing the script document (engine.ExecuteDocument(".\generate.js", ModuleCategory.CommonJS)) which I figured it out after I seen a similar solution in this answer; and also doing changes to get rid of 'fs' and 'path' modules from node.js, so this is the code that I'm executing now:

//var fs = require('fs');
//var path = require('path');
var defaultWordList = require('./xkpasswd-words.json');

// define helpers
var h = {
  random: function random(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  },

  getRandomWord: function getRandomWord(wordList) {
    return wordList[h.random(0, wordList.length - 1)];
  },

  resolveComplexity: function resolveComplexity(complexity) {
    // Patterns can consist of any combination of the following: (w)ords, (d)igits, (s)eparators
    var compl = complexity || 2;
    var rtn = {};
    rtn.separators = '#.-=+_';
    if (compl < 1) compl = 1;
    if (compl > 6) compl = 6;

    if (compl === 1) rtn.pattern = 'wsw';
    if (compl === 2) rtn.pattern = 'wswsw';
    if (compl === 3) rtn.pattern = 'wswswsdd';
    if (compl === 4) rtn.pattern = 'wswswswsdd';

    if (compl === 5) {
      rtn.pattern = 'wswswswswsdd';
      rtn.separators = '#.-=+_!$*:~?';
    }

    if (compl === 6) {
      rtn.pattern = 'ddswswswswswsdd';
      rtn.transform = 'alternate';
      rtn.separators = '#.-=+_!$*:~?%^&;';
    }

    return rtn;
  },

  processOpts: function processOpts(opts) {
    var rtn = {};

    var complexity = parseInt(opts.complexity, 10);
    complexity = typeof complexity === 'number' ? complexity : 3;

    var predefined = h.resolveComplexity(complexity);
    var separators = typeof opts.separators === 'string' ? opts.separators : predefined.separators;

    rtn.pattern = opts.pattern || predefined.pattern;
    rtn.separator = separators.split('')[h.random(0, separators.length - 1)];
    rtn.transform = opts.transform || predefined.transform || 'lowercase';
    rtn.wordList = opts.wordList;

    return rtn;
  },

  // this needs to support the following options:
  // 1) ""words.json""
  // 2) ""words.txt""
  // 3) ""orange,banana, fizz, buzz"" (string of comma-separated words)
  readCustomWordList: function readCustomWordList(input) {
    var data;
    var rtn = [];

    if (Array.isArray(input)) {
      data = input;
    }

    // parse string input
    if (typeof input === 'string') {
      var tmpWordList = input.split(',');

      if (tmpWordList.length === 1) {
        //var targetFile = path.resolve(tmpWordList[0]);
        var fileName = tmpWordList[0];
        var shell = new ActiveXObject('WScript.Shell');
        var targetFile = shell.CurrentDirectory + '\\' + fileName;

        if (targetFile.indexOf('.json') === targetFile.length - 5) {
          // eslint-disable-next-line
          data = require(targetFile);
        }

        if (targetFile.indexOf('.txt') === targetFile.length - 4) {
          //var fileContents = fs.readFileSync(targetFile).toString();
          var fileSystem = new ActiveXObject('Scripting.FileSystemObject');
          var file = fileSystem.OpenTextFile(targetFile, 1);
          var fileContents = file.ReadAll();
          file.Close();

          data = fileContents.split('\n');
        }
      }

      if (!data) {
        data = tmpWordList;
      }
    }

    // if there's no data return false
    if (!data) {
      return false;
    }

    // remove empty
    for (var i = 0; i < data.length; i++) {
      var word = typeof data[i] === 'string' ? data[i].trim() : '';
      if (word.length) {
        rtn.push(word);
      }
    }

    return rtn;
  }
};

module.exports = function main(opts) {
  var o = h.processOpts(opts || {});
  var pattern = o.pattern.split('');
  var uppercase = (typeof o.transform === 'string' && o.transform.toLowerCase() === 'uppercase');
  var password = [];

  var wordList = defaultWordList;

  var customWordList = h.readCustomWordList(o.wordList);

  if (Array.isArray(customWordList) && customWordList.length) {
    wordList = customWordList;
  }

  pattern.forEach(function generatePasswordSegment(type) {
    var value;
    if (type === 'd') value = h.random(0, 9);
    if (type === 's') value = o.separator;
    if (type === 'w' || type === 'W') {
      value = h.getRandomWord(wordList);
      if (typeof o.transform === 'string' && o.transform.toLowerCase() === 'alternate') {
        uppercase = !uppercase;
      }
      if (uppercase || type === 'W') {
        value = value.toUpperCase();
      } else {
        value = value.toLowerCase();
      }
    }

    password.push(value);
  });

  return password.join('');
};

Note: I'm not sure if the changes I did will work as expected, anyway I don't require to read external word-list files in the readCustomWordList functionality, so I could completely remove that code in the worst case and to load only a string array with the words.

The second and current problem I'm having, is that I get an error when trying to get the "main" function to use it. The error message says: main is not defined.

This is the code I'm using:

Using engine As New V8ScriptEngine("xkpasswd_engine", V8ScriptEngineFlags.None)
    engine.ExecuteDocument("\generate.js", ModuleCategory.CommonJS)

    Dim mainFunction As ScriptObject =
        DirectCast(engine.Evaluate("main"), ScriptObject)

    Dim opts As Object = New With {
        .complexity = 3,
        .separators = "#.-=+_",
        .pattern = "wswswsdd",
        .transform = "lowercase",
        .wordList = Nothing
    }

    Dim password As String =
        CStr(mainFunction.Invoke(asConstructor:=False, opts))

    Console.WriteLine("Generated password: " & password)

End Using

What I'm doing wrong and how to fix it?.

I think maybe it is just a matter of figuring out the correct naming to retrieve "main"?. I tried to get "module.exports" but I get a module is not defined error too.


Solution

  • To access the exports of a CommonJS module, you have to call require from another CommonJS module.

    Instead of this:

    engine.ExecuteDocument("generate", ModuleCategory.CommonJS)
    Dim mainFunction = DirectCast(engine.Evaluate("main"), ScriptObject) 'ERROR
    

    Try this:

    Dim info As New DocumentInfo With {.Category = ModuleCategory.CommonJS}
    Dim mainFunction = DirectCast(engine.Evaluate(info, "return require('generate')"), ScriptObject) 'OK
    

    One other thing: Node.js can apparently treat JSON files as CommonJS modules, but ClearScript can't. To make this work, prepend module.exports = to your copy of xkpasswd-words.json. It should look like this:

    module.exports = ["aa","aah", ...