Restrict Routes in Javascript based on firebase authentication - javascript

I am building a javascript Application. This application is using a plugin called jq-router. When I do something like the following, the address bar changes but the view fails to change.
$.router.onRouteBeforeChange(function(e, route, params){
firebase.auth().onAuthStateChanged(function(user){
if(!user && route.protected) {
$.router.go('landing', {}); <!-- This line calls the plugin.
}
});
});
The function in the plugin that is called above looks like the following.
/**
* Navigates to given route name & params
* #params {string} routeName
* #params {object} params
* #return {object} this
*/
s.go = function(routeName, params) {
var s = this;
paramSrv.setParams(params);
window.location = s.href(routeName, params);
return s;
};
You can access the entire plugin here: https://github.com/muzammilkm/jq-router
Again, expected result is that the view and the address bar update. Currently the only thing updating is the address bar, the view still renders.

You should be using onViewChange/onRouteChanged events & also exclude the landing route avoid circular loop. onRouteBeforeChange event is intended to notify the route is about to change, so that if any clean up operation can be done.
$.router.onViewChange(function(e, viewRoute, route, params){
firebase.auth().onAuthStateChanged(function(user){
if(!user && route.name !== "landing" && route.protected) {
$.router.go('landing', {}); <!-- This line calls the plugin.
}
});
});

Related

IndexedDB syncronism issue when creating parallel stores in the same db

Whenever I try to archieve creating diffrent stores in the same database at the same time only one of them is created. Is there a way to resolve this syncronism issue.
I've already been able to solve this issue but I'll clarify what I ment in case it might help someone else. I have a GridView component which is mapped multiple times. This component saves the columns, how they are aranged and their specific behaviors to be stored inside indexedDB. The issue I had was that I used a function that created a Db named after the page and the stores (one for each GridView inside the same DB). In order to create all the stores at the same time (in this case I had to create 9 of them) I had to trigger a version change for each of the new stores in order to be able to persist the information. Inside the function I searched for the Db actual version and added 1 to trigger the version change event. The problem has that because they where searching the version synchronously inside this function all of the itterations were getting the same version and the result would be that only the first store was beeing created because only the first iteration of the map would trigger a version change. In order to resolve this issue I used index prop iside the map function and passed it as an order prop to my GridView component. Then instead of triggering the version change (version+1) inside the function I triggered by using version+order, this way all the stores where being created because it assures that all the versions were going to be higher than the previous ones.
I'll give some code to maybe help the explanation.
This is the map:
{status.map((status, index) => {
return (
<GridView
pageName={"Page"} // DB Name
gridName={status} // Store Name
order={index + 1} // index + 1 so that the order is never 0
//all the other props...
/>
);
})}
Inside the GridView component I have a function that triggers on first render to search for the store inside the db and if there is no information inside creates and then fills the store with the information needed. This the function:
/**
*
#param {String} DB_NAME
#param {String} STORE_NAME
#param {String} keyPath
#param {Int} order
#param {Object/Array} info
#returns {Object}: resolve: {success,message,storeInfo), reject:{error, message}
*/
const createAndPopulateStore = (
DB_NAME,
STORE_NAME,
keyPath,
order = 1,
info
) => {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME);
request.onsuccess = function (e) {
let database = e.target.result;
let version = parseInt(database.version);
database.close();
//This was the critical part in order to create multiple stores at the same time.
let secondRequest = indexedDB.open(DB_NAME, version + order);
secondRequest.onupgradeneeded = (e) => {
let database = e.target.result;
//Early return if the store already exist.
if (database.objectStoreNames.contains(STORE_NAME)) {
reject({
success: false,
message: `There is already a store named: ${STORE_NAME} created in the database: ${DB_NAME}`,
});
return;
}
let objectStore = database.createObjectStore(STORE_NAME, { keyPath });
if (info) {
// Populates the store within the db named after the gridName prop with the indexedColumns array.
if (Array.isArray(info)) {
info.map((item) => objectStore.put(item));
} else {
Object.entries(info).map((item) => objectStore.put(item));
}
}
};
secondRequest.onsuccess = function (e) {
resolve({
success: true,
message: `Store: ${STORE_NAME}, created successfully.`,
storeInfo: info ?? {},
});
let database = e.target.result;
database.close();
};
};
});
};
I hope this will help! Fell free to ask any questions regarding this issue and I'll try to answer them as soon as I can.

How to strip comments in javascript?

I have multiple files that start with comments like:
/*
* #title Force email verification
* #overview Only allow access to users with verified emails.
* #gallery true
* #category access control
*
* This rule will only allow access users that have verified their emails.
*
* > Note: It might be a better UX to make this verification from your application.
*
* If you are using [Lock](https://auth0.com/docs/lock), the default behavior is to log in a user immediately after they have signed up.
* To prevent this from immediately displaying an error to the user, you can pass the following option to `lock.show()` or similar: `loginAfterSignup: false`.
*
* If you are using [auth0.js](https://auth0.com/docs/libraries/auth0js), the equivalent option is `auto_login: false`.
*
*/
//jshint -W025
function (user, context, callback) {
if (!user.email_verified) {
return callback(new UnauthorizedError('Please verify your email before logging in.'));
} else {
return callback(null, user, context);
}
}
All files contains two types of comments i.e /**/ and // Now I am reading this file in my javascript code and want to remove comments and get the actual code in the variable e.g
function (user, context, callback) {
if (!user.email_verified) {
return callback(new UnauthorizedError('Please verify your email before logging in.'));
} else {
return callback(null, user, context);
}
}
I have tried using strip-comments and parse-comments npm but none of these work. Here is the code:
const fs = require('fs');
const path = require('path');
const strip = require('strip-comments');
module.exports = function (ruleFileName, globals, stubs) {
globals = globals || {};
stubs = stubs || {};
const fileName = path.join(__dirname, '../src/rules', ruleFileName + '.js');
const data = fs.readFileSync(fileName, 'utf8');
const code = strip(data);
console.log(code);
return compile(code, globals, stubs);
}
and with parse-comments I tried like:
const parsed = parseComments(data)[0];
const code = data.split('\n').slice(parsed.comment.end).join('\n').trim();
I think strip comment is not working because it takes string as an argument but fs.readFileSync doesn't return string. I have also tried data.toString()but that also didn't work. So how can I strip comments from the content? Is there any other solution?
try use regx to replace /\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm
var Text = `/*
* #title Force email verification
* #overview Only allow access to users with verified emails.
* #gallery true
* #category access control
*
* This rule will only allow access users that have verified their emails.
*
* > Note: It might be a better UX to make this verification from your application.
*
* If you are using [Lock](https://auth0.com/docs/lock), the default behavior is to log in a user immediately after they have signed up.
* To prevent this from immediately displaying an error to the user, you can pass the following option to "lock.show()" or similar: "loginAfterSignup: false".
*
* If you are using [auth0.js](https://auth0.com/docs/libraries/auth0js), the equivalent option is "auto_login: false".
*
*/
//jshint -W025
function (user, context, callback) {
if (!user.email_verified) {
return callback(new UnauthorizedError('Please verify your email before logging in.'));
} else {
return callback(null, user, context);
}
}`
console.log(Text.replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm,''))
like this
https://codepen.io/anon/pen/eQKrWP

Search with the URL Query Parameters on Flow Router

I use Search Source, and Flow Router from Arunoda. They both work great, but I'm just struggling to get them work together.
I have a helper that returns some documents rendered from the server-run SearchSource method like this:
Template.search.helpers({
things: function() {
var currentUserId = Meteor.userId();
var langParam = FlowRouter.current().queryParams.lang;
console.log(langParam);
return BookSearch.getData({
sort: {date_added: -1}
});
}
});
As you see, I'm just trying to search for things that are registered in the language entered at the URL (e.g. 'en') as a query parameter. Let's say in "english" as in this example:
http://localhost:3000/search?lang=en
I can perfectly read the "en" and log on the console by the below code, but it does not work to search with. I mean because of this code:
var langParam = FlowRouter.current().queryParams.lang;
console.log(langParam);
I get "en" printed on the browser console. But I don't get the things that are registered in "en" language.
So how can I achieve a proper search using the query parameters?
What I need to know is how to enter in the helper to render only data that fetches to the condition I want (in this case, english language - {lang: langParam}. For that one uses the Package.getData() API, but I could not locate exactly how.
First of all, searchsource sets up necessary data delivery for you so you don't have to, indeed should not set up publications or subscriptions for your search flow. There's tons of literature around for how pub/sub works in Meteor so I'll skip ahead to your searchsource problem.
I see that you want to scope your search to a certain language. Here's a basic set up that would get you going. You should also fine tune things like throttling, metadata handling, limiting, paging, input and query param sanitization, result transformations etc.
Template
<template name="booksearch">
<form name="booksearch"><input type="search"/></form>
<ul>
{{#each hits}}
<li>{{title}}</li>
{{#each}}
</ul>
</template>
Client: set up your helper
var options = {
// cache the search results for 5 minutes
keepHistory: 1000 * 60 * 5,
// allow fast local searches on the cache
localSearch: true
};
// feed the search to the title field only
var fields = ['title'];
// Set up your search
BookSearch = new SearchSource('books', fields, options);
/*
get the search results reactively. mind you, this is not an invocation.
you'll invoke the search within your event handler down below
*/
Template.booksearch.helpers({
hits : function() {
return BookSearch.getData();
}
})
Template.booksearch.events({
'submit form': function(e,t) {
// listen for the submit event
e.preventDefault();
var options = {
// this is your lang query param from the url
lang: FlowRouter.getQueryParam('lang')
};
// value of the search input from your template
var searchText = t.$('input').val();
// invoke the search using the input and the language
BookSearch.search(searchText,options);
}
})
Server: set up your search
SearchSource.defineSource('books', function(searchText, options) {
// make sure you do have a lang option or use a default one
var lang = options.lang || 'english'
if(searchText) {
var regExp = buildRegExp(searchText);
// use the input and lang to build your mongodb selector
var selector = {title: regExp, language: lang};
return Books.find(selector).fetch();
} else {
// don't return anything if nothing is searched for
return [];
}
});
function buildRegExp(searchText) {
// copied over from the naive github example
var parts = searchText.trim().split(/[ \-\:]+/);
return new RegExp("(" + parts.join('|') + ")", "ig");
}

Trying to get JavaScript code completion to work in netbeans with jsdoc

In my app.js I have the following:
angular.module('app').controller('userList',
['appSettings'
,function (/*#type {app.appSettings}*/appSettings) {
appSettings.<<== it shows a list here but nothing from autocomplete.js
In my autocomplete.js I have the following (generated by JavaScript printing out my services and their members):
var app={};
app.appSettings={};
app.appSettings.userFailMessages={};
app.appSettings.userFailMessages.invalidJson
="Invalid request, user sent is not valid json.";
NetBeans refuses to code complete appSettings for me and doesn't seem to know it's defined in autocomplete.js. Maybe I'm getting my js doc wrong but tried a mix of combination of #var, #type and #param without success.
It code completes when I type app.appSettings. and gives me a list from autocomplete.js but I would like to know how I can tell NetBeans that the passed argument to the function is app.appSettings.
Maybe I should have autocomplete contain constructor functions instead of object literals as #type suggests a certain type and not an instance.
This is NetBeans 7.3.1
Was close to the answer, to have NetBeans use type you have to define the type. Then to indicate that the parameters passed to your angular module (or any function) are of a certain type I use the #param jsdoc
The angular module:
angular.module('app').controller('userList'
, ['$scope','appRules','appSettings'
,/**
* #param {app.appRules} appRules
* #param {app.appSettings} appSettings
* */
function ($scope,appRules,appSettings,$timeout) {
//<== here both appRules and appSettings give suggestions
// from autocomplete
autocomplete.js (not included in my html file but just there for code suggest)
/*#typedef {Object} app*/
var app={};
app.appRules={};
app.appRules.userIsInRole=function (user,role){};
app.appRules.general={};
app.appRules.general.isEmpty=function (val){};
app.appRules.general.isEmail=function (val){};
app.appSettings={};
app.appSettings.userFailMessages={};
app.appSettings.userFailMessages.invalidJson
="Invalid request, user sent is not valid json.";
app.appSettings.userFailMessages.noPrivilege
="You do not have the privileges needed to change this user.";
I ran the following code in the console on a page that contains my app to generate autocomplete.js:
var inj;
function allServices(mod, r) {
if (!r) {
r = {};
inj = angular.element(document.querySelector('[data-ng-app]')).injector().get;
}
angular.forEach(angular.module(mod).requires, function(m) {
allServices(m, r)
});
angular.forEach(angular.module(mod)._invokeQueue, function(a) {
try {
r[a[2][0]] = inj(a[2][0]);
} catch (e) {
}
});
return r;
};
var output=[];
function addOutput(names,prop){
if(names.length===1){
output.push('var ');
}
output.push(names.join('.'));
if(typeof prop === 'object'){
output.push('={};\n');
for(thing in prop){
//TODO: no arrays or route paths
if(/[0-9\/\\]/.test(thing)){
continue;
}
names.push(thing);
addOutput(names,prop[thing]);
}
}else{
output.push('=');
output.push(
(typeof prop === 'function')?
prop.toString():
JSON.stringify(prop)
);
output.push(';\n');
}
names.pop();
}
function createOutput(){
allMyServices = allServices('app');
addOutput(['app'],allMyServices);
console.log(output.join(''));
}
createOutput();

Cancel route using Sammy.js without affecting history

I want to intercept all route changes with Sammy to first check if there is a pending action. I have done this using the sammy.before API and I return false to cancel the route. This keeps the user on the 'page' but it still changes the hash in the browsers address bar and adds the route to the browsers' history. If I cancel the route, I dont want it in the address bar nor history, but instead I expect the address to stay the same.
Currently, to get around this I can either call window.history.back (yuk) to go back to the original spot in the history or sammy.redirect. Both of which are less than ideal.
Is there a way to make sammy truly cancel the route so it stays on the current route/page, leaves the address bar as is, and does not add to the history?
If not, is there another routing library that will do this?
sammy.before(/.*/, function () {
// Can cancel the route if this returns false
var response = routeMediator.canLeave();
if (!isRedirecting && !response.val) {
isRedirecting = true;
// Keep hash url the same in address bar
window.history.back();
//this.redirect('#/SpecificPreviousPage');
}
else {
isRedirecting = false;
}
return response.val;
});
In case someone else hits this, here is where I ended up. I decided to use the context.setLocation feature of sammy to handle resetting the route.
sammy.before(/.*/, function () {
// Can cancel the route if this returns false
var
context = this,
response = routeMediator.canLeave();
if (!isRedirecting && !response.val) {
isRedirecting = true;
toastr.warning(response.message); // toastr displays the message
// Keep hash url the same in address bar
context.app.setLocation(currentHash);
}
else {
isRedirecting = false;
currentHash = context.app.getLocation();
}
return response.val;
});
When using the code provided within the question and answer you have to notice that the route you cancelled will also be blocked for all future calls, routeMediator.canLeave will not be evaluated again. Calling a route twice and cancelling it depending on current state is not possible with this.
I could produce the same results as John Papa did when he used SammyJS on the SPA/Knockout course.
I used Crossroads JS as the router, which relies on Hasher JS to listen to URL changes "emitted" by the browser.
Code sample is:
hasher.changed.add(function(hash, oldHash) {
if (pageViewModel.isDirty()){
console.log('trying to leave from ' + oldHash + ' to ' + hash);
hasher.changed.active = false;
hasher.setHash(oldHash);
hasher.changed.active = true;
alert('cannot leave. dirty.');
}
else {
crossroads.parse(hash);
console.log('hash changed from ' + oldHash + ' to ' + hash);
}
});
After revisiting an older project and having a similar situation, I wanted to share another approach, just in case someone else is directed here.
What was needed was essentially a modern "auth guard" pattern for intercepting pages and redirecting based on credentials.
What worked well was using Sammy.around(callback) as defined here:
Sammy.js docs: Sammy.Application around(callback)
Then, simply do the following...
(function ($) {
var app = Sammy("body");
app.around(checkLoggedIn);
function canAccess(hash) {
/* access logic goes here */
return true;
}
// Authentication Guard
function authGuard(callback) {
var context = this;
var currentHash = app.getLocation();
if (!canAccess(currentHash)) {
// redirect
context.redirect("#/login");
}
else {
// execute the route path
callback();
}
};
})(jQuery);

Categories