How safe is sending in functions to String.replace? - javascript

I am writing a string resolver function and I want it to take an object to resolve all "<%=variable%>" occurrences. I have the ability to set some of the object properties to functions and have those functions return values automatically. Is this safe? I originally planned on parsing out the tokens and getting return values from functions based on what I parsed from the token. Having String.replace do it seems a lot easier but I am not sure what the security risks are. Any ideas or comments? This will mainly be used in node.js so I don't imagine any user access to the function.
var data = {
name: "Mike",
job: "programmer"
},
funcs = {
date: function () {return new Date;},
timestamp: function () {return new Date().getTime();},
guid: function () {return "4FGGX3g";}
};
function resolve (str,resolvers) {
var regex,result;
result = str;
str.match(/(<%=.+?%>)/g).forEach(function (item) {
var token,itr,resolver;
token = item.split(/(\w+)/)[1];
for (itr = 0; itr < resolvers.length; itr += 1) {
resolver = resolvers[itr][token];
if (resolver) {
regex = new RegExp(item,'g');
result = result.replace(regex,resolver);
break;
}
}
});
return result;
}
console.log(resolve("My name is <%=name%> and I am a <%=job%>.\nThe date is: <%=date%>\nTimestamp: <%=timestamp%>\nUniqieID: <%=guid%>",[data,funcs]));

Related

Best practice to handle undefined variables dynamicaly in JavaScript/Nodejs

Ok, maybe is not the best title, but I lacked inspiration, so here goes:
Let's say you have a "global" (not really) variable to store temporary data and sub data as random users interact with your server. Normally on the first interaction with your server, the main variable will be undefined so you need to handle that case.
Now, what puzzled me about this, is what's the best practice performance wise to do this if there are a lot of users and a lot way more interactions with the variable.
Puzzled? Yeah, I know, words are not my strong point so let me show you in code
So you have
var user_data = [];
Then a function that handles user interaction to store data
function writeData(uid, data_name, data)
Now, on first interaction, user_data[uid][data_name] is undefined, and so it's user_data[uid]
I know you can handle this 2 ways:
With if -
if(!user_data[uid]) user_data[uid] = {}
user_data[uid][data_name] = data
With try/catch
try{user_data[uid][data_name] = data}
catch(e) {user_data[uid] = {}; writeData(uid, data_name, data)}
The if will check on every interaction, and like I said there are a lot.
Try catch will trigger once, but it has a cost as a block (afaik)
Which one is better? Or is there a another better way
#Nertan ,
There is a partiality in your proof :P . I have slightly tweeked the ternary way (same as the order of execution in if way). With this you can conclude.
//var present = require('present');
function test(val,ud,fun) {
var k = 10000000;
var t = Date.now();
for(var i=0; i<k;i++)
{
var uid = Math.ceil(Math.random()*1000);
fun(uid,ud,"value");
}
var tf = Date.now()-t;
return tf;
}
function setValue_Opp(uid,ud,value)
{
(!ud[uid] && (ud[uid] = {})) && (ud[uid].value = value);
}
function setValue_Try(uid,ud,value)
{
try{ ud[uid].value = value}
catch(e){ ud[uid] = {}; setValue_Try(uid,ud,value)};
}
function setValue_Cond(uid,ud,value)
{
if(!ud[uid]) ud[uid] = {}
ud[uid].value = value;
}
var k1=0;
var k2=0;
var k3=0;
for(var i=0;i<10;i++){
k1+=test(1,{}, setValue_Cond);
k2+=test(2,{}, setValue_Try);
k3+=test(3,{}, setValue_Opp);
}
console.log(k1,k2,k3)
I feel we can take advantage of ES6 ternaries as below:
let user_data = {}
const writeData = (uid, data_name, data) => {
((user_data[uid] || (user_data[uid] = {})) && (user_data[uid][data_name] = data ))
console.log(user_data)
// perform write action
}
writeData('1',"test","test1");
writeData('2',"test","test2");
writeData('1',"test","test3");
Ok, so I had to rewrite the test because it doesn't work fine in the Snippet
So I made this for node.js:
var present = require('present');
function test(val,ud,fun) {
var k = 10000000;
var t = present();
for(var i=0; i<k;i++)
{
var uid = Math.ceil(Math.random()*1000);
fun(uid,ud,"value");
}
var tf = present()-t;
console.log("END "+val+" at "+tf);
return tf;
}
function setValue_Opp(uid,ud,value)
{
(ud[uid] || (ud[uid] = {})) && (ud[uid].value = value);
}
function setValue_Try(uid,ud,value)
{
try{ ud[uid].value = value}
catch(e){ ud[uid] = {}; setValue_Try(uid,ud,value)};
}
function setValue_Cond(uid,ud,value)
{
if(!ud[uid]) ud[uid] = {}
ud[uid].value = value;
}
var k1=0;
var k2=0;
var k3=0;
for(var i=0;i<10;i++){
k1+=test(1,{}, setValue_Cond);
k2+=test(2,{}, setValue_Try);
k3+=test(3,{}, setValue_Opp);
}
console.log(k1,k2,k3)
And in the end:
3244.328997004777 3695.0267750024796 3437.6855720058084
Which means:
The best is the classical if
The second best is condintional operators method
And the worst is the try-catch
So it seems the classics win
Edited:
With further tests thanks to #CRayen the best method is :
(!ud[uid] && (ud[uid] = {})) && (ud[uid].value = value);

Firebase: Run a query synchronously

I am trying to set some user data depending on the no.of users already in my USERS COLLECTION. This even includes a userId which should be a number.
exports.setUserData = functions.firestore.document('/users/{documentId}')
.onCreate(event => {
return admin.firestore().collection('users')
.orderBy('userId', 'desc').limit(1)
.get().then(function(snapshot) {
const user = snapshot.docs[0].data();
var lastUserId = user.userId;
var userObject = {
userId: lastUserId + 1,... some other fields here
};
event.data.ref.set(userObject, {
merge: true
});
});
});
One issue I noticed here, quickly adding 2 users result in those documents having the same userId may be because the get() query is asynchronous?
Is there a way to make this whole setUserData method synchronous?
There is no way to make Cloud Functions run your function invocations sequentially. That would also be quite contrary to the serverless promise of auto-scaling to demands.
But in your case there's a much simpler, lower level primitive to get a sequential ID. You should store the last known user ID in the database and then use a transaction to read/update it.
var counterRef = admin.firestore().collection('counters').doc('userid');
return db.runTransaction(function(transaction) {
// This code may get re-run multiple times if there are conflicts.
return transaction.get(counterRef).then(function(counterDoc) {
var newValue = (counterDoc.data() || 0) + 1;
transaction.update(counterRef, newValue);
});
});
Solution
var counterRef = admin.firestore().collection('counters').doc('userId');
return admin.firestore().runTransaction(function(transaction) {
// This code may get re-run multiple times if there are conflicts.
return transaction.get(counterRef).then(function(counterDoc) {
var newValue = (counterDoc.data().value || 0) + 1;
transaction.update(counterRef, {
"value": newValue
});
});
}).then(t => {
admin.firestore().runTransaction(function(transaction) {
// This code may get re-run multiple times if there are conflicts.
return transaction.get(counterRef).then(function(counterDoc) {
var userIdCounter = counterDoc.data().value || 0;
var userObject = {
userId: userIdCounter
};
event.data.ref.set(userObject, {
merge: true
});
});
})
});

Mapping JSON to ES6 Classes

I have our staff in a json file, and had the idea to use that data with ES6 classes. The more I work with this, the more I feel as though I may be missing something. I had this working in coffeescript as such:
fetch = require('node-fetch')
domain = 'domain.com'
apiSrc = 'api.domain.com'
slug = 'people'
class Person
constructor: (json) -> {name: #name, title: #title, school: #school, bio: #bio} = json
email: (username) ->
username.replace(/\s/, '.').toLowerCase() + '#' + domain
profile: ->
content = []
if this.name then content.push("#{#name}")
if this.title then content.push("#{#title}")
if this.school then content.push(school) for school in "#{#school}"
if this.bio then content.push("#{#bio}")
if this.name then content.push(this.email("#{#name}"))
content.join('')
fetch('http://' + apiSrc + '/' + slug + '.json')
.then((response) -> response.json())
.then((api) ->
content = []
group = []
group.push(new Person(them)) for them in api[slug]
for them, index in group
content.push(them.profile())
console.log(content.join(''))
)
But then I thought it would be even better if I could convert it to ES6. I know the use case is simple, and classes certainly aren't necessary, since I'm just using the data for templating, however, for the sake of learning, I was attempt to do this. Am I going about this the wrong way? Right now, I feel like there should be a way to return all of the "people" that I put into the Person class. However, the only way I could figure out how to do that was to run a for loop and then write it to the document.
class Person {
constructor(data) { ({name: this.name, title: this.title, school: this.school, bio: this.bio, email: email(this.name)} = data); }
email(username) {
return username.replace(/\s/, '.').toLowerCase() + '#' + location.hostname.replace(/[^\.\/\#]+\.[^\.\/]+$/, '');
}
profile() {
return `${this.name} ${this.title}`;
}
}
var apiSrc = 'api.domain.com';
var slug = 'people';
fetch(`http://${apiSrc}/${slug}.json`)
.then(function(response) { return response.json() }) // .then(response => response.json())
.then(function(api) {
var content = [];
var group = [];
for (var i = 0; i < api[slug].length; i++) { var them = api[slug][i]; new Person(them); }
for (var i = 0; index < group.length; i++) {
var them = group[i];
content.push(them.profile());
console.log(content.join(''));
}
});
My ES6 conversion actually isn't even working right now. API returns the JSON but after that it gets messed up. Any suggestions would be really helpful as I'm trying to better myself as a coder and hope this sort of example could help others learn Classes in a real use case.
You might try using the reviver parameter of JSON.parse. Here is a simplified example:
class Person {
// Destructure the JSON object into the parameters with defaults
constructor ({name, title, school=[]}) {
this.name = name
this.title = title
this.school = school
}
}
var api = JSON.parse(a, function (k,v) {
// Is this a new object being pushed into the top-level array?
if (Array.isArray(this) && v.name) {
return new Person(v)
}
return v
})
var group = api["people/administration"]

Simple grammar checker program - optimal data structure

I want to created a simple game for English that checks someone's sentences. They are able to construct their sentence using a fixed set of words from a word bank.
The word bank might be something scrambled like:
[want, eat, I, hi, to]
Then they'd create their sentence in the correct order:
hi I want to eat.
I asked this question on English SO as it originally pertained to grammatical questions- the question has evolved into more of a data structures question. You can read more about it at that link. My original thought to check sentence grammar using a set of generic English rules seemed like it could quickly grow too complex. It was recommended I just match using hard coded checks, shown below.
Before I further define these checks, I was wondering if a better data structure/method was known to check grammar for this purpose.
if (input === the_answer) {
msg = correct!
} else {
msg = 'Try again: ' + this.grammarRules(input, the_answer));
}
Language_System.prototype.grammarRules = function(input, answer) {
var grammar_hints = {
quest1 : {
task1 : [
'The subject, Bob, needs to be first',
'The phrase is Hello there'
]
}
};
var grammar_rules = {
quest1 : {
task1 : function (input, answer) {
var error = -1;
if (input[0] !== answer[0]) {
error = 0;
} else if (input.indexOf('hello') > input.indexOf('there')) {
error = 1;
}
return grammar_hints.quest1.task1[error];
}
}
};
return grammar_rules.Lee.quest1.task1(input, answer);
};
It would be much easier if you'd consider a more declarative approach:
- define a standard quest structure
- define a standard task structure with generic input formats
- define generic validators and re-use them
You started on the right path with the grammar_hints object, but I would actually put all the properties portraying to one task in the same object.
Suggestion:
var quests = [
{
name: 'Quest 1',
tasks: [
{
name: 'Task 1',
solution: 'hi I want to eat',
validators: [
validators.first('hi'),
validators.verbAfterNoun('want', 'I'),
]
}
],
},
];
You will be able to re-use a lot of the validators in multiple tasks so you want them to be as generic as possible, here is one example:
var validators = {
first: function (input, term) {
if (input[0] !== term) {
return 'The sentence needs to start with: ' + term;
}
},
verbAfterNoun: function (input, verb, noun) {
if (input.indexOf(verb) < input.indexOf(noun)) {
return 'The verb, ' + verb + ', needs to come after the noun ' + noun;
}
}
};
Now because you want to have a declarative format (I went with actually initializing the validators with their input and passing the result in the validators array), we would need a validator factory that takes a generic validator and returns a helper method that can be re-used with only the input. This will help us down the line so our testing framework won't need to know how many inputs to pass to each of the validator callbacks
// This is a factory method that applies the given callback (with the given arguments)
function makeValidator (fn) {
return function inputFN () {
var args = [].slice.call(arguments);
return function validate (input) {
return fn.apply(null, [input].concat(args));
}
}
}
// Apply the makeValidator() method on all the validators
for (var key in validators) {
validators[key] = makeValidator(validators[key]);
}
And finally we also want a standard way of checking our tasks against input:
// This method provides the generic validation framework for any task given any input
function validate (task, input) {
var wordList = input.split(' ');
if (input === task.solution) return {success: true, errors: []};
var errors = [];
task.validators.forEach(function (fn) {
var error = fn(wordList);
if (error) errors.push(error);
});
return {success: false, errors: errors};
}
And some examples:
var task = quests[0].tasks[0];
console.log(validate(task, 'hi I want to eat'));
console.log(validate(task, 'I want to eat hi'));
console.log(validate(task, 'hi want I to eat'));
console.log(validate(task, 'want I to eat hi'));
Putting it all together:
// This is a factory method that applies the given callback (with the given arguments)
function makeValidator (fn) {
return function inputFN () {
var args = [].slice.call(arguments);
return function validate (input) {
return fn.apply(null, [input].concat(args));
}
}
}
var validators = {
first: function (input, term) {
if (input[0] !== term) {
return 'The sentence needs to start with: ' + term;
}
},
verbAfterNoun: function (input, verb, noun) {
if (input.indexOf(verb) < input.indexOf(noun)) {
return 'The verb, ' + verb + ', needs to come after the noun ' + noun;
}
}
};
// Apply the makeValidator() method on all the validators
for (var key in validators) {
validators[key] = makeValidator(validators[key]);
}
var quests = [
{
name: 'Quest 1',
tasks: [
{
name: 'Task 1',
solution: 'hi I want to eat',
validators: [
validators.first('hi'),
validators.verbAfterNoun('want', 'I'),
]
}
],
},
];
// This method provides the generic validation framework for any task given any input
function validate (task, input) {
var wordList = input.split(' ');
if (input === task.solution) return {success: true, errors: []};
var errors = [];
task.validators.forEach(function (fn) {
var error = fn(wordList);
if (error) errors.push(error);
});
return {success: false, errors: errors};
}
function printTask (input) {
var task = quests[0].tasks[0];
var result = validate(task, input);
document.body.innerHTML += '<div><b>checking:</b> ' + input + '<pre>' + JSON.stringify(result, null, 4) + '</pre><hr />';
}
// Lets look at some examples
printTask('I want to eat hi');
printTask('hi want I to eat');
printTask('want I to eat hi');
printTask('hi I want to eat');

Waiting to initialize untill data is loaded asynchronously

I am trying to design a personal app which loads data asynchronously and then displays a grid according to the windows 8.1 store apps.
i'm running into the issue that my ui is trying to execute before my data is loaded.
my current code:
(function () {
"use strict";
var asyncInProgress = true;
var groupedItems;
var list;
var observable;
var matches = new WinJS.Binding.List();
var matchGroups = new WinJS.Binding.List();
var BattleGrounds = new WinJS.Binding.List();
list = getData();
initGroups(list);
function initGroups(l) {
var groupedItems = list.createGrouped(
function groupKeySelector(item) { return item.group.key; },
function groupDataSelector(item) { return item.group; }
);
}
WinJS.Namespace.define("Data", {
Observable: WinJS.Class.define(function () {
this.dispatch = function () {
this.dispatchEvent("dataReady");
}
}),
getObservable: getObservable,
items: groupedItems,
groups: groupedItems.groups,
getItemReference: getItemReference,
getItemsFromGroup: getItemsFromGroup,
resolveGroupReference: resolveGroupReference,
resolveItemReference: resolveItemReference,
updateData: updateData,
getAsyncStatus: getAsyncStatus
});
WinJS.Class.mix(Data.Observable, WinJS.Utilities.eventMixin);
WinJS.Class.mix(Data.Observable, WinJS.Utilities.createEventProperties("dataReady"));
// Provides support for event listeners.
function getObservable() {
observable = new Data.Observable();
return observable;
}
// Get a reference for an item, using the group key and item title as a
// unique reference to the item that can be easily serialized.
function getItemReference(item) {
return [item.group.key, item.title, item.backgroundImage];
}
// This function returns a WinJS.Binding.List containing only the items
// that belong to the provided group.
function getItemsFromGroup(group) {
return list.createFiltered(function (item) { return item.group.key === group.key; });
}
// Get the unique group corresponding to the provided group key.
function resolveGroupReference(key) {
return groupedItems.groups.getItemFromKey(key).data;
}
// Get a unique item from the provided string array, which should contain a
// group key and an item title.
function resolveItemReference(reference) {
for (var i = 0; i < groupedItems.length; i++) {
var item = groupedItems.getAt(i);
if (item.group.key === reference[0] && item.title === reference[1]) {
return item;
}
}
}
function updateData() {
asyncInProgress = true;
BattleGrounds.splice(0, matches.length);
BattleGrounds._currentKey = 0;
groupedItems = null;
list = getData();
initGroups(list);
}
function getAsyncStatus() {
return asyncInProgress;
}
function getData() {
var darkGray = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXY3B0cPoPAANMAcOba1BlAAAAAElFTkSuQmCC";
var lightGray = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXY7h4+cp/AAhpA3h+ANDKAAAAAElFTkSuQmCC";
var mediumGray = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXY5g8dcZ/AAY/AsAlWFQ+AAAAAElFTkSuQmCC";
var url = 'https://api.guildwars2.com/v1/wvw/matches.json';
acquireSyndication(url).then(function (response) {
// Remove any invalid characters from JSONp response.
var fixedResponse = response.responseText.replace(/\\'/g, "'");
var jsonObj = JSON.parse(fixedResponse);
jsonObj.wvw_matches.forEach(function (battle) {
var anet_id = value.wvw_match_id;
// Create Group
var matchGroup = {
key: anet_id,
title: anet_id
};
matchGroups.push(matchGroup);
// Get Details
acquireSyndication("https://api.guildwars2.com/v1/wvw/match_details.json?match_id=" + anet_id).then(function (json) {
var fixedJson = json.responseText.replace(/\\'/g, "'");
var obj = JSON.parse(fixedJson);
fixedJson.maps.forEach(function (value) {
BattleGrounds.push({
group: matchGroup, key: matchGroup.title, title: value.type,
subtitle: value.type, map: "eb", description: "NA", content: "NA", "type": value.type,
"scores": value.scores, "objectives": value.objectives, "bonuses": value.bonuses, backgroundImage: lightGray
});
});
}, function (error) {
var x = error.getAllResponseHeaders();
var matchGroup = matchGroups[0];
for (var i = 0; i < matchGroups.length; i++) {
flickrPosts.push({
group: matchGroups[i], key: matchGroup.title, title: "Error loading",
subtitle: "Error", backgroundImage: lightGray, published: "N/A", description: "N/A"
});
}
asyncInProgress = false;
observable.dispatch();
});
});
}, function (error) {
var x = error.getAllResponseHeaders();
var matchGroup = matchGroups[0];
for (var i = 0; i < matchGroups.length; i++) {
flickrPosts.push({
group: matchGroups[i], key: matchGroup.title, title: "Error loading",
subtitle: "Error", backgroundImage: lightGray, published: "N/A", description: "N/A"
});
}
asyncInProgress = false;
observable.dispatch();
});
return BattleGrounds;
}
function acquireSyndication(url) {
return WinJS.xhr({
url: url,
headers: { "If-Modified-Since": "Mon, 27 Mar 1972 00:00:00 GMT" }
});
}
})();
This errors out on groups: groupedItems.groups. which says that groups is undefined.
i know this is because the data is still being processed.
How am i going to work around this?
i took a look at the promise object but the entire concept confuses me as i don't know enough about the infrastructure of a windows 8 app.
The core of your problem is in the getData() function - it is not returning your data because it uses asynchronous calls to get the data. The data is not yet available when it returns. It appears that that function makes several asynchronous calls to get data (using acquireSyndication()). When those asynchronous functions finish sometime in the future, you then put that data into matchGroups and then later into BattleGrounds after more calls to acquireSyndication().
What you're doing is quite messy so there isn't a simple fix. Conceptually, you need to process the BattleGrounds data from the completion handler of the asynchronous code and ALL code that uses it must continue from inside that completion handler, not after the getData() call. You cannot call getData() and use it like a synchronous function because it's asynchronous. This requires asynchronous programming techniques.
If you are doing multiple asynchronous calls and trying to carry out some action after all of them have completed (which I think is what you're doing), then you will need to code specifically for that condition too. You can either use promises or you can keep a counter of how many ajax calls there are and in each completion function, you increment the counter and see if this is the last one that just completed and, if so, then you can process all the data and continue executing the rest of your code.
I would also suggest that you don't use promises in one part of a function and then completion callbacks in the very next part. Use one of the other, not a mixture, to keep your code clean.

Categories

Resources