Uncaught ReferenceError in DevTools for global variables [duplicate] - javascript

This question already has answers here:
How can I use modules in the browser, but also refer to variables and functions from within DevTools?
(4 answers)
Closed yesterday.
Update:
Looks like by importing another JS file and using the ES6 Modules, devtools compiles a bit different (will need to dive into learning more). The variables I thought were global were not in the module. Was able to fix this by assigning them to:
window.variable_name = variable_name
This allowed me to call the variables from devtools.
I have a project I am working on to practice a few JS skills. For the most part I have everything up and running but have come across an issue I can not seem to remedy.
I have an app.js file that is linked in my index.html. When running the live server from VS code everything works as it should, but when I open devtools to check some of the variables (global) & functions I get "Uncaught referenceError: variable_name is not defined" with a VM#:# reference.
Up to today, I have been able to call the variables from console without a problem.
I have 12 global variables, and all but 1 (submit) have this issue.
App.js:
import { autocomplete } from "./autocomplete.js";
// ######## GLOBAL VARIABLES ############
//------- Selectors ----------
const container = document.querySelector('.search-container');
const searchElement = document.getElementById('search');
const searchInput = document.querySelector('input[name="search-bar"]');
const submit = document.getElementById('search-bar');
const loader = document.querySelector('.loader');
const results = document.querySelector('.results');
const resetButton = document.querySelector('.reset');
const errorMessage = document.querySelector('.error');
const heart = document.querySelector('#favorite');
//------- Arrays & Objects ----------
let names = [];
let pokemon = {};
let bgImages = ["./resources/images/forest_background.jpg", "./resources/images/field.jpg", "./resources/images/galar-scenery.png", "./resources/images/night.jpg", "./resources/images/training.jpg", "./resources/images/poke-background.webp"];
//########## Grab & Store Pokemon Names for Autocomplete ##########
async function loadPokeNames() {
try {
const response = await fetch ('https://pokeapi.co/api/v2/pokemon?limit=250');
if (response.ok) {
const jsonResponse = await response.json();
// console.log(jsonResponse)
for (const poke of jsonResponse.results){
names.push(poke.name);
}
}
// throw new Error('Request Failed!')
} catch(error){
console.log(error);
}
}
//############ Search Function ###############
async function searchPokemon(e) {
e.preventDefault();
let pokeSearchValue = e.srcElement[0].value.toLowerCase();
searchElement.hidden = true;
loader.hidden = false;
try {
const pokeResponse = await fetch(`https://pokeapi.co/api/v2/pokemon/${pokeSearchValue}`);
if (pokeResponse.ok) {
const pokeJSON = await pokeResponse.json();
// Assign Values to Pokemon Object
pokemon.name = pokeJSON["name"];
pokemon.img = pokeJSON["sprites"]["other"]["official-artwork"]["front_default"];
pokemon.hp = pokeJSON["stats"][0]["base_stat"];
pokemon.attack = pokeJSON["stats"][1]["base_stat"];
pokemon.speed = pokeJSON["stats"][5]["base_stat"];
pokemon.defense = pokeJSON["stats"][2]["base_stat"];
pokemon.special_attack = pokeJSON["stats"][3]["base_stat"];
pokemon.special_defense = pokeJSON["stats"][4]["base_stat"];
console.log(pokemon);
createPokeCard(pokemon);
} else {
throw new Error("Something Went Wrong.");
}
} catch (error) {
loader.hidden = true;
errorMessage.hidden = false;
resetButton.hidden = false;
console.log(error);
}
}
// ####### Generates the Pokemon Card #########
function createPokeCard(object) {
const pokeName = document.querySelector('#poke-name p');
const pokeHP = document.querySelector('#hp');
const pokeImg = document.querySelector('#poke-image img');
const pokeAttack = document.querySelector('#attack .num');
const pokeSpeed= document.querySelector('#speed .num');
const pokeDefense = document.querySelector('#defense .num');
const pokeSpecialA = document.querySelector('#special-attack .num');
const pokeSpecialD = document.querySelector('#special-defense .num');
const backgroundImage = document.querySelector('#poke-image')
// Assign values to Results Card
backgroundImage.style.backgroundImage = `url('${bgImages[Math.floor(Math.random() * 6)]}')`;
pokeName.textContent = object.name;
pokeHP.textContent = `${object.hp} HP`;
pokeImg.src = object.img;
pokeAttack.innerText = object.attack;
pokeDefense.textContent = object.defense;
pokeSpeed.textContent = object.speed;
pokeSpecialA.textContent = object.special_attack;
pokeSpecialD.textContent = object.special_defense;
setTimeout(() => {
loader.hidden = true;
results.hidden = false;
resetButton.hidden = false;
}, 3000)
}
// ####### Resets Search & Card #########
function resetSearch() {
searchInput.value = '';
resetButton.hidden = true;
results.hidden = true;
searchElement.hidden = false;
errorMessage.hidden = true;
for (const att in pokemon){
delete pokemon[att];
}
}
//######## Favorite Functions ###########
function hoverFav() {
this.src = '../resources/images/heartline-fill.png';
}
function hoverOutFav() {
this.src = '../resources/images/heartline.png';
}
// ########### EVENTS ##############
window.onload = loadPokeNames;
autocomplete(searchInput, names)
heart.addEventListener('mouseenter', hoverFav);
heart.addEventListener('mouseout', hoverOutFav);
resetButton.addEventListener('click', resetSearch);
submit.addEventListener('submit', searchPokemon);
Linked (correct path) script tag:
<script src="./script/app.js" type="module"></script>
Any help or a point in the right direction would be amazing.
Tried calling global variables from devtools. Got an uncaught referenceError message.

Alright, so I have made a bit of digging around and it turns out the reason this happens is indeed because you're setting the type of the script as a module. Scripts that are run as modules are under strict mode by default, thus they don't allow access to global variables.
Also, they are made to run as a separate scripts(modules) and so any variables defined or imported into them are private by default and are not accessible to the global scope.
As for the error, in normal standard JS scripts, the access of a variable not defined properly(without let/const) will not throw an error, under strict mode however, it converts it into an error and throws it.
Take a look here for more info on differences between standard & module scripts. Also here for info on strict mode.

Related

Spark AR script cannot merge Patches with Scene

I'm a beginner at Spark AR and I'm trying to build a blink counting game and I got the following error when I try to merge Scene and Patches.
Property 'text' does not exist on type 'Promise<SceneObjectBase>'.
here's my code
const Scene = require('Scene');
const Patches = require('Patches');
// Use export keyword to make a symbol available in scripting debug console
export const Diagnostics = require('Diagnostics');
(async function () { // Enables async/await in JS [part 1]
// To access scene objects
const [directionalLight] = await Promise.all([
Scene.root.findFirst('directionalLight0')
]);
var textScore = Scene.root.findFirst('score_number');
var patchScore = Patches.outputs.getScalar('ScoreJumps');
// To access class properties
const directionalLightIntensity = directionalLight.intensity;
// To log messages to the console
textScore.text = patchScore.toString();
Diagnostics.log('Console message logged from the script.');
})(); // Enables async/await in JS [part 2]
findFirst is a Promise so you need to await on it:
const textScore = await Scene.root.findFirst('score_number');
Finally,
I was able to solve my issue by adding #ts-ignore to my code as follows.
const Scene = require('Scene');
const Patches = require('Patches');
// Use export keyword to make a symbol available in scripting debug console
export const Diagnostics = require('Diagnostics');
// To use variables and functions across files, use export/import keyword
// export const animationDuration = 10;
// Use import keyword to import a symbol from another file
// import { animationDuration } from './script.js'
(async function () { // Enables async/await in JS [part 1]
// To access scene objects
const [directionalLight] = await Promise.all([
Scene.root.findFirst('directionalLight0')
]);
var countNum = await Scene.root.findFirst('number');
var scoreNumber = (await Patches.outputs.getScalar('score'));
// To access class properties
const directionalLightIntensity = directionalLight.intensity;
// #ts-ignore
countNum.text = scoreNumber.toString();
// #ts-ignore
Diagnostics.log("wererwerwe"+countNum.text);
// To log messages to the console
Diagnostics.log('Console message logged from the script.');
})(); // Enables async/await in JS [part 2]

At an object's instantiation time how does one handle best the asynchronous initialization of one of its properties?

I've never created a Javascript module/library before so this is a bit new to me so apologizes for my lack of knowing what to google.
I'm creating a library that will hold information from a URL that is provided by a user. I want to parse the URL's path (the part that comes after the domain) as well as retain a header value that's provided by the URL's response.
It's basic but here's what I have so far:
function Link(someURL) {
this.url = someURL;
this.urlPath = "";
this.uuid = "";
this.getPath = function (someURL) {
// do regexp parsing and return everything after the domain
};
this.getUUID = function (someURL) {
// fetch the URL and return what is in the response's "uuid" header
}
}
Ideally, I'd the module to automatically get all the information upon construction:
var foo = new Link("http://httpbin.org/response-headers?uuid=36d09ff2-4b27-411a-9155-e82210a100c3")
console.log(foo.urlPath); // should return "uuid"
console.log(foo.uuid); // should return the contents in the "uuid" header in the response
How do I ensure the this.urlPath and this.uuid properties get initialized along with this.url? Ideally, I'd only fetch the URL once (to prevent rate limiting by the target server).
After a lot of trial and error, I ended up doing something more like this:
class Link {
constructor (url_in) {
const re = RegExp("^https://somedomain.com\/(.*)$");
this.url = re[0];
this.linkPath = re[1];
}
async getUUID() {
const res = await fetch("https://fakedomain.com/getUUID?secret=" + this.linkPath);
this.uuid = res.uuid;
}
async getJSON() {
const res = await fetch("https://fakedomain.com/getJSON?uuid=" + this.uuid);
this.json = await res.json();
}
async initialize() {
await this.getUUID();
await this.getJSON();
}
}
const someLinkData = new Link("https://reallydumbdomain.com/2020/10/4/blog");
someLinkData.initialize()
.then(function() {
console.log(this.json); // this now works
});
I think a future iteration of this will require me to send a promise with the initialize function but for now, this works.

Websockets in Sapper

I have a readable store in Svelte that looks like this:
const state = {};
export const channels = readable(state, set => {
let st = state;
let socket = new WebSocket("ws://127.0.0.1:5999");
socket.onmessage = function (event) {
var datastr = event.data.split(':');
st[datastr[0]].value = datastr[1];
st[datastr[0]].timestamp = Date.now();
set(st)
};
return () => {
socket.close()
}
});
When I import it to my Svelte App works. But if I put that App.svelte as my index.svelte running on Sapper, it doesnt work at first. It says error 500 websocket is not defined. Once I reload the page in the browser start to work...
I have try to parse a function that creates the store instead:
export const getChannel = () => {
// here my store
return {...store}
}
and then creating the store inside a onMount() like this:
onMount( ()=> {
const channel = getChannel();
});
But doesnt seem to do the trick... What do I miss?
Note: If a just replace the store by a simple writable, and create the websocket onMount(), it works without any problem. I just only wanted to put all the communication inside the store as a readable...
In Sapper, code in components (or imported into components) is executed in Node during server-side rendering unless it's put inside onMount (which doesn't run on the server, because there's no 'mounting' happening) or an if (process.browser) {...} block, or something equivalent.
That includes things like references to $channels causing channels.subscribe(...) to be called during initialisation.
Since there's no WebSocket global in Node, creating that subscription will fail. The simplest workaround is probably a simple feature check:
const state = {};
export const channels = readable(state, (set) => {
if (typeof WebSocket === 'undefined') return;
let st = state;
let socket = new WebSocket("ws://127.0.0.1:5999");
socket.onmessage = function (event) {
var datastr = event.data.split(":");
st[datastr[0]].value = datastr[1];
st[datastr[0]].timestamp = Date.now();
set(st);
};
return () => {
socket.close();
};
});

Create global variable in utils.js file in node.js

Hi I'm currently trying to figure it out how to properly define global variable in node.js. I know it is not a good practice to do this, but in this particular screnario it's the only way to do this without using connection to database.
I'm getting data from github API to display some information, I'm trying to store them in the global variable. It should allow me to e.g pass specific object from first global list to new global list that display only chosen items.
I have file called utils.js that have this two empty arrays that should be global:
let repoItems = [];
let bookmarkedItems = [];
exports.repoItems = repoItems;
exports.bookmarkedItems = bookmarkedItems;
Then I have another file that fetch and should assign items to the first global variable, but it looks like it doesn't. Because in the moment I'm trying to chose one item & push it into second empty array it's impossible, I'm getting empty array. I'm not sure if the mistake is taken because of bad implementation of global variable from other file, or something else :/
Below I include the fetching part of code, with bold part that I'm confused with:
let utils = require('../utils');
let {
repoItems,
} = utils;
router.get('/', async (req, res) => {
try {
const result = await fetchGithubAPI(`searching word`);
const urls = result.map(url => url);
console.log(urls);
res.render('repositories.ejs', {
'data': urls
});
} catch (e) {
console.log(e);
}
});
async function fetchGithubAPI(search) {
const response = await fetch(`https://api.github.com/?q=${search}`, {
method: 'GET',
headers: {
'Accept': 'application/vnd.github.v3+json',
'Content-Type': 'application/json',
},
});
const data = await response.json();
**repoItems = data.items.map(item => item);**
return repoItems;
}
Try:
let repoItems = require('./utils').repoItems
instead of
let {
repoItems,
} = utils;
if you remove
let {
repoItems,
} = utils;
you can try using
utils.repoItems = data.items.map(item => item)
I tried an example setup for it
--utils.js
module.exports.someValue = 3;
module.exports.setSomeValue = (value) => {someValue = value}
--index.js
const utils = require('./utils');
console.log(utils.someValue);// 3
utils.someValue = 5
console.log(utils.someValue);// 5
Update after knowing about getter and setter methods in Js
You can refer to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set
this is another way to change value of any private properties in JS

Extending console.log without affecting log line

I would like to extend the 'console.log' function to add additional information to its output - but I dont want to affect the script name/line number information generated by the browser in the console window. See how if I create my own implementation, I get useless trace information, should I want to locate that region of code... (they all link to the log implementation, not the actual script that caused the log message)
Basically, my application is a very pluggable infrastructure, were any log output may occur within any number of frames.
As such, I want every log message to include a special unique identifier at the beginning of the log message.
I have tried replacing the console.log method with my own, but chrome complains with
Uncaught TypeError: Illegal invocation
this is how I override it
var orig = console.log;
console.log = function( message )
{
orig( (window == top ? '[root]' : '[' + window.name + ']') + ': ' + message );
}
Any ideas?
[EDIT]
Note: After fixing the 'illegal invocation' problem, it seems the filename/linenumber is still 'polluted' by the override...
[EDIT]
It looks like the general answer is - NO - despite some confusing goose chases, the desired functionality is NOT achievable in the current versions of browsers.
Yes, it is possible to add information without messing up the originating line numbers of the log invocation. Some of the other answers here came close, but the trick is to have your custom logging method return the modified logger. Below is a simple example that was only moderately tested that uses the context variant.
log = function() {
var context = "My Descriptive Logger Prefix:";
return Function.prototype.bind.call(console.log, console, context);
}();
This can be used with:
log("A log message...");
Here is a jsfiddle: http://jsfiddle.net/qprro98v/
One could get easily get creative and pass the context variable in, and remove the auto-executing parens from the function definition. i.e. log("DEBUG:")("A debug message"), log("INFO:")("Here is some info"), etc.
The only really import part about the function (in regards to line numbers) is that it returns the logger.
If your use case can deal with a few restrictions, there is a way that this can be made to work. The restrictions are:
The extra log content has to be calculated at bind time; it cannot be time sensitive or depend on the incoming log message in any way.
The extra log content can only be place at the beginning of the log message.
With these restrictions, the following may work for you:
var context = "ALIASED LOG:"
var logalias;
if (console.log.bind === 'undefined') { // IE < 10
logalias = Function.prototype.bind.call(console.log, console, context);
}
else {
logalias = console.log.bind(console, context);
}
logalias('Hello, world!');
http://jsfiddle.net/Wk2mf/
An acceptable solution can be to make your own log-function that returns a console.log function bound with the log arguments.
log = function() {
// Put your extension code here
var args = Array.prototype.slice.call(arguments);
args.unshift(console);
return Function.prototype.bind.apply(console.log, args);
}
// Note the extra () to call the original console.log
log("Foo", {bar: 1})();
This way the console.log call will be made from the correct line, and will be displayed nicely in the console, allowing you to click on it and everything.
It is actually possible in chrome at least. Here is the most relevant. This may vary depending on setup, and how i got the splits was to just log the whole stack, and find the information I needed.
var stack = new Error().stack;
var file = stack.split("\n")[2].split("/")[4].split("?")[0]
var line = stack.split("\n")[2].split(":")[5];
Here is the whole thing, preserving the native object logging.
var orig = console.log
console.log = function(input) {
var isChrome = navigator.userAgent.indexOf("Chrome") !== -1;
if(isChrome){
var stack = new Error().stack;
var file = stack.split("\n")[2].split("/")[4].split("?")[0]
var line = stack.split("\n")[2].split(":")[5];
var append = file + ":" + line;
}
orig.apply(console, [input, append])
}
You need to call the console.log with the correct context (console):
orig.call(console, message);
To complete your function allowing multiple arguments:
var orig = console.log;
console.log = function() {
var msgs = [],
prefix = (window== top ? '[root]' : '[' + window.name + ']');
while(arguments.length) {
msgs.push(prefix + ': ' + [].shift.call(arguments));
}
orig.apply(console, msgs);
};
Demo: http://jsfiddle.net/je2wR/
Remember that you loose the built-in object/array browser in the console when combining objects with strings using the + sign.
I just answered this on a post that helped me answer the original 'alias' question:
(http://stackoverflow.com/a/12942764/401735)
my_log_alias = console.log.bind(console)
Apparently the capacity to do this has been designed in. Tested. Works.
thereafter my_log_alias is the same as console.log and can be called in the same way; Calling this from inside the function will report the line number for that function call, including the line inside of an alias or advice function where applicable.
Specifically, the line number Chrome provides will tell you the file the line is in, so what you are doing may be unneccesary; Consider reporting this as a bug/feature request in chrome that it provide this info in console.log.
Christopher Currie provided an excellent solution. I've expanded it a bit for my needs. Here's the AMD module:
define([], function () {
var enableDebug = true;
var separator = ">";
function bind(f, thisArg, ctx) {
if (f.bind !== 'undefined') { // IE < 10
return Function.prototype.bind.call(f, thisArg, ctx);
}
else {
return f.bind(thisArg, ctx);
}
}
function newConsole(context, parentConsole) {
var log;
var debug;
var warn;
var error;
if (!parentConsole) {
parentConsole = console;
}
context = context + separator;
if (enableDebug) {
debug = bind(console.log, console, context + "DEBUG" + separator);
} else {
debug = function () {
// suppress all debug messages
};
}
log = bind(console.log, console, context);
warn = bind(console.warn, console, context);
error = bind(console.error, console, context);
return {
debug: debug,
info: log,
log: log,
warn: warn,
error: error,
/* access console context information */
context: context,
/* create a new console with nested context */
nest: function (subContext) {
return newConsole(context + subContext, this);
},
parent: parentConsole
};
}
return newConsole("");
});
By default this will output > {message}. You can also add nested context to you logging, e.g. console.nest("my").log("test") will output >my> test.
I've also added a debug function that will indent messages with >DEBUG>
Hope somebody will find it useful.
Not long ago Chrome introduced a feature that can solve your problem without code hacks. It is called "blackbox" which basically allows you to mark files which should be ignored with their tools.
https://gist.github.com/paulirish/c307a5a585ddbcc17242
Yes, this solution is browser specific, but if you are using Chrome you do want this solution.
The solutions with a huge hack around throwing an Error for each log can show the right line, but it will not be a clickable link in your console.
The solutions based on binding/aliasing only enables you to modify the printed text. You will not be able to forward the arguments to a third function for further processing.
I have looked into this several times and always found it was not possible.
My workaround if you are interested is to assign console to another variable and then wrap all my log messages in a function which lets me modify/style/whatever on the message.
It looks nice with CoffeeScript, not sure its practical with plain JS.
I just get into the habit of prefixing everything with x.
logger.debug x 'Foo'
log x 'Bar'
log x('FooBar %o'), obj
Unfrotuantly it's currenlty not possible, In the future we might be able to do it with the Proxy object in ECMAScript 6.
My use case was to auto-prefix console messages with helpful information like the arguments passed and executing method. at the moment the closest I got is using Function.prototype.apply.
A simple approach is to just write your debug statements as such:
console.info('=== LazyLoad.css(', arguments, '): css files are skipped, gives us a clean slate to style within theme\'s CSS.');
A complicated approach is to use helper function as per below, I personally now prefer the simple approach.
/* Debug prefixing function
* ===========================
*
* A helper used to provide useful prefixing information
* when calling `console.log`, `console.debug`, `console.error`.
* But the catch is that to utilize one must leverage the
* `.apply` function as shown in the below examples.
*
* ```
* console.debug.apply(console, _fDebugPrefix(arguments)
* .concat('your message'));
*
* // or if you need to pass non strings
* console.debug.apply(console, _fDebugPrefix(arguments)
* .concat('json response was:', oJson));
*
*
* // if you need to use strict mode ("use strict") one can't
* // extract the function name but following approach works very
* // well; updating the name is just a matter of search and replace
* var aDebugPrefix = ['fYourFunctionName('
* ,Array.prototype.slice.call(arguments, 0),
* ,')'];
* console.debug.apply(console,
* aDebugPrefix.concat(['json response was:', oJson]));
* ```
*/
function _fDebugPrefix(oArguments) {
try {
return [oArguments.callee.name + '('
,Array.prototype.slice.call(oArguments, 0)
, ')'];
}
catch(err) { // are we in "use strict" mode ?
return ['<callee.name unsupported in "use strict">('
,Array.prototype.slice.call(oArguments, 0)
, ')'];
}
}
Reusable class in TS/JS
// File: LogLevel.ts
enum LogLevel {
error = 0,
warn,
info,
debug,
verbose,
}
export default LogLevel;
// File: Logger.js
import LogLevel from "./LogLevel";
export default class Logger {
static id = "App";
static level = LogLevel.info;
constructor(id) {
this.id = id;
const commonPrefix = `[${Logger.id}/${this.id}]`;
const verboseContext = `[V]${commonPrefix}`;
if (console.log.bind === "undefined") {
// IE < 10
this.verbose = Function.prototype.bind.call(console.log, console, verboseContext);
} else {
this.verbose = console.log.bind(console, verboseContext);
}
if (LogLevel.verbose > Logger.level) {
this.verbose = function() {
return // Suppress
};
}
const debugContext = `[D]${commonPrefix}`;
if (console.debug.bind === "undefined") {
// IE < 10
this.debug = Function.prototype.bind.call(console.debug, console, debugContext);
} else {
this.debug = console.debug.bind(console, debugContext);
}
if (LogLevel.debug > Logger.level) {
this.debug = function() {
return // Suppress
};
}
const infoContext = `[I]${commonPrefix}`;
if (console.info.bind === "undefined") {
// IE < 10
this.info = Function.prototype.bind.call(console.info, console, infoContext);
} else {
this.info = console.info.bind(console, infoContext);
}
if (LogLevel.info > Logger.level) {
this.info = function() {
return // Suppress
};
}
const warnContext = `[W]${commonPrefix}`;
if (console.warn.bind === "undefined") {
// IE < 10
this.warn = Function.prototype.bind.call(console.warn, console, warnContext);
} else {
this.warn = console.warn.bind(console, warnContext);
}
if (LogLevel.warn > Logger.level) {
this.warn = function() {
return // Suppress
};
}
const errorContext = `[E]${commonPrefix}`;
if (console.error.bind === "undefined") {
// IE < 10
this.error = Function.prototype.bind.call(console.error, console, errorContext);
} else {
this.error = console.error.bind(console, errorContext);
}
if (LogLevel.error > Logger.level) {
this.error = function() {
return // Suppress
};
}
}
}
Usage (React):
// File: src/index.tsx
// ...
Logger.id = "MCA"
const env = new Env()
if (env.env == Environment.dev) {
Logger.level = LogLevel.verbose
const log = new Logger("Main")
log.info("Environment is 'Development'")
}
///...
// File: src/App/CookieConsent/index.tsx
import React, { useEffect } from "react";
import { useCookies } from "react-cookie";
import "./index.scss";
import Logger from "#lib/Logger" // #lib is just alias configured in webpack.
const cookieName = "mca-cookie-consent";
// const log = new Logger(CookieConsent.name) // IMPORTANT! Don't put log instance here. It is too early! Put inside function.
export default function CookieConsent(): JSX.Element {
const log = new Logger(CookieConsent.name) // IMPORTANT! Have to be inside function, not in global scope (after imports)
useEffect(() => {
log.verbose(`Consent is accepted: ${isAccepted()}`);
}, []);
const [cookie, setCookie] = useCookies([cookieName]);
function isAccepted(): boolean {
return cookie[cookieName] != undefined;
}
function containerStyle(): React.CSSProperties {
return isAccepted() ? { display: "none" } : {};
}
function handleClick() {
const expires = new Date();
expires.setFullYear(expires.getFullYear() + 1);
log.verbose(`Accepted cookie consent. Expiration: ${expires}`)
setCookie(cookieName, true, { path: "/", expires: expires, sameSite: "lax" });
}
return (
<div className="cookieContainer" style={containerStyle()}>
<div className="cookieContent">
<div>
<p className="cookieText">This website uses cookies to enhance the user experience.</p>
</div>
<div>
<button onClick={handleClick} className="cookieButton">
I understand
</button>
</div>
</div>
</div>
);
}
Output in browser console:
20:47:48.190 [I][MCA/Main] Environment is 'Development' index.tsx:19
20:47:48.286 [V][MCA/CookieConsent] Consent is accepted: false index.tsx:13
20:47:52.250 [V][MCA/CookieConsent] Accepted cookie consent. Expiration: Sun Jan 30 2022 20:47:52 GMT+0100 (Central European Standard Time) index.tsx:29
Hope this helps for some of your cases...
const log = console.log;
export default function middleWare(optionalStringExtension = '') {
console.log = (...args) => {
log(...args, optionalStringExtension);
}
}
Either run as middleware, top of file, or first line of function.
I ran into this issue as well about extending console.log() so that the application can extend, control and do fancy stuff with it in addition to logging stuff to the console. Losing the line number information was tantamount to failure, however. After wrestling with the issue, I came up with a long-winded workaround, but at least it's still a "1-liner" to use.
First, define a global class to use or add some methods to your main existing "app" class:
/**
* Log message to our in-app and possibly on-screen console, return args.
* #param {!string} aMsgLevel - one of "log", "debug", "info", "warn", or "error"
* #param {any} aArgs - the arguments to log (not used directly, just documentation helper)
* #returns args so it can be nested within a console.log.apply(console,app.log()) statement.
*/
MyGlobalClassWithLogMethods.prototype.debugLog = function(aMsgLevel, aArgs) {
var s = '';
var args = [];
for (var i=1; i<arguments.length; i++) {
args.push(arguments[i]);
if (arguments[i])
s += arguments[i].toString()+' ';
}
if (typeof this.mLog === 'undefined')
this.mLog = [];
this.mLog.push({level: aMsgLevel, msg: s});
return args;
};
MyGlobalClassWithLogMethods.prototype.log = function() {
var args = ['log'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
MyGlobalClassWithLogMethods.prototype.debug = function() {
var args = ['debug'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
MyGlobalClassWithLogMethods.prototype.info = function() {
var args = ['info'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
MyGlobalClassWithLogMethods.prototype.warn = function() {
var args = ['warn'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
MyGlobalClassWithLogMethods.prototype.error = function() {
var args = ['error'].concat(Array.prototype.slice.call(arguments));
return this.debugLog.apply(this,args);
};
//not necessary, but it is used in my example code, so defining it
MyGlobalClassWithLogMethods.prototype.toString = function() {
return "app: " + JSON.stringify(this);
};
Next, we put those methods to use like so:
//JS line done as early as possible so rest of app can use logging mechanism
window.app = new MyGlobalClassWithLogMethods();
//only way to get "line info" reliably as well as log the msg for actual page display;
// ugly, but works. Any number of params accepted, and any kind of var will get
// converted to str using .toString() method.
console.log.apply(console,app.log('the log msg'));
console.debug.apply(console,app.debug('the log msg','(debug)', app));
console.info.apply(console,app.info('the log msg','(info)'));
console.warn.apply(console,app.warn('the log msg','(warn)'));
console.error.apply(console,app.error('the log msg','(error)'));
Now the console gets log messages with their appropriate line information as well as our app contains an array of log messages that can be put to use. For example, to display your in-app log using HTML, JQuery and some CSS the following simplistic example can be used.
First, the HTML:
<div id="debug_area">
<h4 class="text-center">Debug Log</h4>
<ul id="log_list">
<!-- console log/debug/info/warn/error ('msg') lines will go here -->
</ul>
</div>
some CSS:
.log_level_log {
color: black;
background-color: white;
font-size: x-small;
}
.log_level_debug {
color: #060;
background-color: #80FF80;
font-size: x-small;
}
.log_level_info {
color: #00F;
background-color: #BEF;
font-size: x-small;
}
.log_level_warn {
color: #E65C00;
background-color: #FB8;
font-size: x-small;
}
.log_level_error {
color: #F00;
background-color: #FBB;
font-size: x-small;
}
and some JQuery:
var theLog = app.mLog || [];
if (theLog.length>0) {
var theLogList = $('#log_list');
theLogList.empty();
for (var i=0; i<theLog.length; i++) {
theLogList.prepend($('<li class="log_level_'+theLog[i].level+'"></li>').text(theLog[i].msg));
}
}
This is a simplistic use, but once you have the mechanism in place, you can do whatever your imagination can come up with, including leaving the log lines in the code, but setting a threshold so that only warnings and errors get through. Hopefully this helps others with their projects.
Today you have to use args with rest operator, because as the Mozilla docs says Function.arguments has been deprecated and is not accessible in arrow functions. So simply you can extend it like below:
//#1
const myLog= (...args) =>
console.log.bind(console, ...args);
//myLog("this is my new log")();
//#2
const myNewLog= (...args) =>{
const prefix = "Prefixed: ";
return console.log.bind(console, ...[prefix,...args]);
}
//myNewLog("test")()
And you can make a beautifulLog like this:
//#3
const colorizedLog = (text, color= "#40a7e3", ...args) =>
console.log.bind(
console,
`%c ${text}`,
`font-weight:bold; color:${color}`,
...args
);
//colorizedLog("Title:", "#40a7e3", "This is a working example")();
This snippet apply a prefix to logs for all levels (console.log console.debug console.info ...) :
export const makeConsole = (context: string, cons = console): Console =>
Object.getOwnPropertyNames(cons).reduce((c, lev) => {
if (typeof cons[lev] === "function") {
c[lev] = Function.prototype.bind.call(cons[lev], cons, context);
}
return c;
}, {});
console.debug("Hello world!")
// >> Hello world!
console = makeConsole("[logging is fun]")
// >> [logging is fun] Hello world!
Bonus, for React peeps:
export function useConsole(context: string): Console {
return React.useMemo(() => makeConsole(context), [context]);
}
Try setTimeout(console.log.bind(console,'foo'));

Categories

Resources