I found the solution by using global.consoleLog = v => this.log(v));. Now the variable consoleLog is available anywhere.
ORIGINAL QUESTION
Currently I am participating in the battlecode challenge. My question is about Javascript though and accessing a global variable.
I have a minimal working example with two files.
// === robot.js ===
import { BCAbstractRobot } from 'battlecode';
import Test from './test.js';
class MyRobot extends BCAbstractRobot {
constructor() {
super();
this.log("asdf"); // the log function does work
// Test.setConsoleLog(this.log); // way 1
// console.log = this.log; // way 2
}
turn() {
Test.testFunction("hello");
return this.move(0, 1);
}
}
// === test.js ===
let consoleLog = undefined;
function setConsoleLog(c) {
consoleLog = c;
}
function testFunction(s) {
// consoleLog(s); // way 1
// console.log(s); // way 2
}
export default { testFunction, consoleLog, setConsoleLog };
Since battlecode gives you the log function, you cannot use console.log for security reasons. However I want to be able to log from any file. The log function is a property of the MyRobot class, but I want to be able to access the function from other files and functions without passing parameters every time.
I tried a couple ways. First I tried creating an export from a util file (test.js) which would start out as undefined and then be set by setConsoleLog. However, when I attempt to use consoleLog, it is still undefined.
I also tried overwriting the console.log reference, but this only works within one file and the reference is still to console.log in other files.
What would be the recommended way to go about creating a global reference or import to this.log such that any file could import it and access it?
The bot is run within a node vm, but with a bundler as far as I know.
Related
I'm trying to write a small Vue plugin that just returns the env variable content. Similar to PHPs env() method.
Context: I need a url in multiple components, obviously I put this in the .env file because it could possibly change in the future. You cannot use process.env inside of the components template though because: Property or method "process" is not defined on the instance but referenced during render.
This is what I've tried to far:
I call the prototype function in a mounted hook like this:
console.log('test: ' + this.env('MIX_COB_PARTNER_URL'));
import _get from "lodash/get";
const Env = {
// eslint-disable-next-line
install(Vue, options) {
Vue.prototype.env = function (name) {
console.log(name); // "MIX_VARIABLE"
console.log(typeof name); // string
// variant 1
return process.env[name]; // undefined
// variant 2
return process.env["MIX_VARIABLE"]; // "works" but isnt dynamic obviously
// variant 3-5 with lodash
return _get(process.env, name); // undefined
// or
return _get(process, 'env[' + name + ']'); // undefined
// or
return _get(process.env, '[' + name + ']'); // undefined, worth a try lol
// variant 6
const test = "MIX_VARIABLE"
return process.env[test]; // again just for the sake of trying
}
}
}
export default Env;
I know that usually object[variable] works fine. But somehow it doesnt in this case, maybe it has to do with the fact that the process.env is empty when accessed without a key and the [name] ist as "direct" as .MIX_VARIABLE would be.
Does this just not work?
Ive researched and found some people (e.g. here on SO) suggesting this type of accessing (process.env[variable]) so I'm not sure.
I believe your environment variable name needs to start with VUE_APP_ in order to be picked up by Vue's environment variable processing. So, maybe rename MIX_VARIABLE to VUE_APP_MIX_VARIABLE? (See https://cli.vuejs.org/guide/mode-and-env.html#environment-variables)
I have a file containing the definition of a Object and in that same file I have a function that is part of this object like so:
export function ARScene(_callbacks) {
this.callbacksObject = _callbacks;
// more fancy code..
}
ARScene.prototype.changeCar = function() {
//some fancy code here
this.loadHDCar(); // THIS LIKE GENERATES A ERROR.
}
now I have a different file containing an other method that is part of the Object called ARScene like so:
import { ARScene } from './arScene';
ARScene.prototype.loadHDCar = function() {
//some more fancy code..
}
What is happening when I build this with webpack and run it in the browser I get the error that this.loadHDCar(); is undefined I guess this happens because webpack doesnt add a file if it is not imported. But how do I make sure that ARScene.prototype.loadHDCar is added to the object in the final output?
I am a complete newbie to webpack and modules. I have found answers on stackoverflow about this but they had slightly different scenarios then me. So their solutions didnt work (or maybe I didnt understand it).
If more context or information is needed please let me know.
How do I make sure that ARScene.prototype.loadHDCar is added to the object in the final output?
You should import it in the arScene module, and you should even create the prototype method in there (where you are defining the class) for visibility.
export function loadHDCar() {
… //some more fancy code
}
import { loadHDCar } from './loadHDCar';
export function ARScene(_callbacks) {
…
}
ARScene.prototype.loadHDCar = loadHDCar;
ARScene.prototype.changeCar = function() {
… // some fancy code here
this.loadHDCar();
};
In my current project with lots of dependencies I need a way to disable console access for specific libraries so that those files can't use any of the console functionality.
I could of course disable console functionality by simply finding and replacing it in the library bundle, but as this project has a lot of dependencies that would make updating libraries a huge hassle.
I'm aware that I can disable console functionality by overwriting it with an empty function block:
console.log = function(){};
But that disables the console functionality for the entire project. So im looking for an implementation, or a line of code with which I can disable console functionality for a specific file or code block.
Write a white-listing "middleware" for console.log
// Preserve the old console.log
const log = console.log;
// Used a dictionary because it's faster than lists for lookups
const whiteListedFunctions = {"hello": true};
// Whitelisting "middleware". We used the function's name "funcName"
// as a criteria, but it's adaptable
const isWhitelisted = callerData => callerData.funcName in whiteListedFunctions;
// Replacing the default "console.log"
console.log = arg => {
const stack = new Error().stack.split("at")[2].trim().split(' ');
const fileParts = stack[1].substr(1, stack[1].length - 2).split(':');
const callerData = {
funcName: stack[0],
file: fileParts.slice(0, fileParts.length - 2).join(':'),
lineColNumber: fileParts.slice(fileParts.length - 2).join(':')
};
if (isWhitelisted(callerData)) { // Filtering happens here
log(arg);
}
};
// Define the calling functions
function hello() { console.log("hello"); }
function world() { console.log("world"); }
hello(); // => Prints hello
world(); // => Doesn't print anything
Method explanation
You can do this by creating a whitelist (or blacklist) that will contain your filtering criteria. For example it may contain the name of the functions that call console.log or maybe the file name, or even the line and column numbers.
After that you create your whitelisting "middleware". This will take the caller function data and decide if it can log stuff or not. This will be done based on the previously defined whitelist. You can choose your preferred criteria in this "middleware".
Then you actually replace console.log by overriding with your new logger. This logger will take as an argument the message to log (maybe multiple arguments?). In this function you also need to find the data relating to the caller function (which wanted to call console.log).
Once you have the caller data, you can then use your whitelisting middleware to decide if it can log stuff
Getting information about the caller function
This part is a little "hacky" (but it got the job done in this case). We basically create an Error and check its stack attribute like this new Error().stack. Which will give us this trace
Error
at console.log.arg [as log] (https://stacksnippets.net/js:25:7)
at hello (https://stacksnippets.net/js:41:11)
at https://stacksnippets.net/js:48:1
After processing (split, map, etc...) the trace we get the caller function data. For example here we have
The caller function's name: hello
The file name: https://stacksnippets.net/js
The line and column number: 41:11 (watch out for minifiers)
This bit was inspired by VLAZ's answer in How to disable console.log messages based on criteria from specific javascript source (method, file) or message contents, so make sure to check it out. Really good and thorough post.
Note
To make sense of the trace we can do new Error().stack.split("at")[INDEX].trim().split(' ') where INDEX is the position of the function call you want to target in the stack trace. So if you want to get a different "level" that the one used in this example, try changing INDEX
Just redefine the console to log over a condition, your condition of course will be a check over which library is accessing the function:
// Your condition, could be anything
let condition = true;
/* Redefine the console object changing only the log function with your new version and keeping all the other functionalities intact
*/
let console = (old => ({
...old,
log: text => { if (condition) old.log(text) }
}))(window.console)
// Redefine the old console
window.console = console;
console.log('hello!')
Hope it helped :)
Yes, you can disable console logs from files based on their path! Here's a solution:
// in ./loud-lib.js
module.exports = {
logsSomething: () => console.log('hello from loud-lib')
}
// in ./silent-lib.js
module.exports = {
logsSomething: () => console.log('hello from silent-lib')
}
// in ./index.js
const loud = require('./loud-lib');
const silent = require('./silent-lib');
// save console.log
const log = console.log;
// redefinition of console.log
console.log = (...params) => {
// define regexp for path of libraries that log too much
const loudLibs = [/loud-lib/];
// check if the paths logged in the stacktract match with at least one regexp
const tooLoud = !!loudLibs.find(reg => reg.test(new Error().stack));
// log only if the log is coming from a library that doesn't logs too much
if (!tooLoud) log(...params);
};
loud.logsSomething();
silent.logsSomething();
$ node ./index.js
hello from silent-lib
This is based on the fact that new Error() produces a stack trace that identifies from which file is the error coming from (recursively).
Based on this observation, you can define an array of regular expression that match the name of libraries you don't want to hear logs from. You can get really specific and creative with the re-definition of console.log, but I kept it simple.
However, be aware of this (especially when using Webpack): if you bundle all your JS assets into one single bundle.js, the stacktrace will always point to bundle.js, thus logging everything. You'll have to go further from my code, for example by using stack-source-map, but I don't have sufficient details on your project to deliver a solution. I hope the ideas above are sufficient for you.
Is there a way to get the file name from a reference to a class? Please note this example is over simplified to illustrate what I'm trying to do (don't start suggesting logging libraries please!)
//Logger1.js
class Logger1 {
}
//MainProcess.js
class MainProcess {
startChildProcess(Logger) {
//This extension doesn't work, but looking to find something similar that does:
Logger.fileName = () => {
return __filename
}
let loggerFileName = Logger.fileName() //Returns "Main.js" not "Logger1.js", so no good...
childProcess.fork(processFileName, [loggerFileName] )
}
}
//In a child process spawned by main:
loggerPath = process.argv[2]
let Logger = require(loggerPath)[path.basename(loggerPath).replace(".js","")]
let logger = new Logger()
I can obviously add a property with a string value, or a method to return __filename the Logger1 class, but I'd rather avoid it. Is there a way to do this from inside the MainProcess class, keeping Logger1 and any other external code clean?
The reason I don't pass the instance of the logger, is the main process then creates child processes, and these child processes instantiate their own loggers. And there is no way to pass object references down to child processes as far as I'm aware.
So I'm writing a multiplayer game with Socket.io and most of the socket calls are handled in the main file (app.js) including storing usernames and the sockets they're connected to.
But I'd like to create a separate file (game.js) that handles all the game code including socket emissions to certain rooms. However to do that I'll need access to my array with the users/sockets stored in it (which is in app.js)
So I'm wondering what the best way to share the variable would be? Should I pass the the array reference through every function that I need it in?
Or should I write a function that is called once and creates a global variable (or the scope that I need it in) with a reference to the array?
Also If I should ever need to share the same dependency across multiple files should I call require in each one of them?
About Modules and the use Global/Shared State
An interesting aspect of modules is the way they are evaluated. The module is evaluated the first time it is required and then it is cached. This means that after it has been evaluated no matter how many times we require it again, we will always get the same exported object back.
This means that, although Node provides a global object, it is probably better to use modules to store shared stated instead of putting it directly into the global object. For instance, the following module exposes the configuration of a Mongo database.
//module config.js
dbConfig = {
url:'mongodb://foo',
user: 'anakin',
password: '*******'
}
module. exports = dbConfig;
We can easily share this module with as many other modules as we want, and every one of them will get the same instance of the configuration object since the module is evaluated only once, and the exported object is cached thereon.
//foo.js
var dbConfig1 = require('./config');
var dbConfig2 = require('./config');
var assert = require('assert');
assert(dbConfig1==dbConfi2);
So, for your specific problem, the shared state that you want to share can reside in a singleton object exposed by whatever module you have. Just make sure your singleton object is the one being exposed in your module and you will always get a reference back to it every time you require it.
If by 'variable' you mean reference to the socket - you may want to consider passing a callback or module to game.js which handles the emission - but that game.js calls when necessary.
Like Edwin Dalorzo mentioned, having a separate file for all your variables seems the best.
I've had a similar problem for a few hours now because I didn't know that variables were persistent. The scenario that I had was:
I have two files cli.ts and main-lib.ts. cli.ts reads user input, and depending on the input, runs functions in main-lib.ts. While main-lib.ts is busy validating the input, cli.ts uses some global variables that main-lib.ts generates when a test passes. The only limitation is that I can't mix the main-lib.ts code with the cli.ts, I can only share the function callValidateFunction.
The issue that I had originally thought of was: if I were to make a global-vars.ts file, the variables' data will still be different for each call of require (i.e., calling setVar(...) will only change the variable value that was imported.
However, thanks to Edwin's answer, I managed to implement a bridge:
// cli.ts
import { setVar, getVar } from "./var-bridge";
import { callValidateFunction } from "./main-lib";
function run(args: string[]): void {
// ...
if (arg == "--email") {
// Set the test status.
setVar("testStatus", "pending");
// Validate the input email address.
callValidateFunction("validateEmail", nextArg());
// Get the testStatus.
const testStatus: string = getVar("testStatus");
}
// ...
}
// main-lib.ts
import { setVar, getVar } from "./var-bridge";
const funcs: {name: string, func: (arg: string) => boolean} = {};
funcs.validateEmail = function(arg: string): boolean {
let passed: boolean = false;
// ...
return passed;
};
function callValidateFunction(functionName: string, arg: string): void {
// ...
const passed = funcs[functionName](arg);
if (passed) setVar("testStatus", "passed");
}
// ...
// var-bridge.ts
const variables: {name: string, value: any} = {
"testStatus": "",
// ...
};
function setVar(varName: string, varValue: any): void {
variables[varName] = varValue;
}
function getVar(varName: string): any {
return variables[varName];
}
export { setVar, getVar };