Security News
Fluent Assertions Faces Backlash After Abandoning Open Source Licensing
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
baobab-router
Advanced tools
Baobab-router is a JavaScript router for Baobab, that binds the URL of a page to the application's state. It is released under the MIT license.
First, baobab-router has to be instanciated with a baobab instance and a tree of routes. A route is mainly a pair of some state constraints and a path string.
A route can also have children routes, and possibly can specify a default path, when the route's path matches but none of its children does.
When the router is instanciated, it will listen to hash and state updates:
Assume we have a very small application, with two generic pages (a homepage and a settings page), and two project specific pages. A project is identified by the "projectId"
value in the state, and the view is described by the value "view"
in the state.
Here is how to instanciate the state tree and the related router:
import Baobab from 'baobab';
import Router from 'baobab-router';
// Instanciate Baobab tree:
const tree = new Baobab({
view: null,
projectId: null,
projectData: null,
});
// Instanciate router:
const router = new Router(tree, {
defaultRoute: '/home',
routes: [
{
path: '/home',
state: {
view: 'home',
projectId: null,
},
},
{
path: '/settings',
state: {
view: 'settings',
projectId: null,
},
},
{
path: '/project/:pid',
defaultRoute: '/dashboard',
state: {
projectId: ':pid',
},
routes: [
{
path: '/settings',
state: {
view: 'project.settings',
},
},
{
path: '/dashboard',
state: {
view: 'project.dashboard',
},
},
],
},
],
});
Once the router is instanciated, it will check the hash to update the state:
console.log(window.location.hash === '#/home');
console.log(tree.get('view') === 'home');
console.log(tree.get('projectId') === null);
In the following examples, the state does exactly match a route:
() => {
tree.set('view', 'settings');
tree.set('projectId', null);
tree.commit();
setTimeout(() => console.log(window.location.hash === '#/settings'), 0);
}
() => {
tree.set('view', 'project.settings');
tree.set('projectId', '123456');
tree.commit();
setTimeout(() => (
console.log(window.location.hash === '#/project/123456/settings')
), 0);
}
() => {
window.location.hash = '/settings';
setTimeout(() => {
console.log(tree.get('view') === 'settings');
console.log(tree.get('projectId') === null);
}, 0);
}
() => {
window.location.hash = '/project/123456/dashboard';
setTimeout(() => {
console.log(tree.get('view') === 'project.dashboard');
console.log(tree.get('projectId') === '123456');
}, 0);
}
In the three following examples, the state does not match any route, so the router will fallback on the default route, and update the state in consequence:
() => {
tree.set('view', 'something irrelevant');
tree.set('projectId', null);
tree.commit();
setTimeout(() => {
console.log(window.location.hash === '#/home');
console.log(tree.get('view') === 'home');
console.log(tree.get('projectId') === null);
}, 0);
});
() => {
tree.set('view', 'something irrelevant');
tree.set('projectId', 123456);
tree.commit();
setTimeout(() => {
console.log(window.location.hash === '#/project/123456/dashboard');
console.log(tree.get('view') === 'project.dashboard');
console.log(tree.get('projectId') === 123456);
}, 0);
});
() => {
window.location.hash = '/something/irrelevant';
setTimeout(() => {
console.log(window.location.hash === '#/home');
console.log(tree.get('view') === 'home');
console.log(tree.get('projectId') === null);
}, 0);
}
() => {
window.location.hash = '/project/123456/irrelevant';
setTimeout(() => {
console.log(window.location.hash === '#/project/123456/dashboard');
console.log(tree.get('view') === 'project.dashboard');
console.log(tree.get('projectId') === 123456);
}, 0);
}
Let's take the exact same application, but with a logged
flag in the state tree, specifying whether the user is logged in or not. To prevent any user to land on a page with the flag as true
and receive some 403 errors or some similar undesired effects, the router will be forbidden to update the logged
flag, using the readOnly
root property:
import Baobab from 'baobab';
import Router from 'baobab-router';
// Instanciate Baobab tree:
const tree = new Baobab({
logged: false,
view: null,
projectId: null,
projectData: null,
});
// Instanciate router:
const router = new Router(tree, {
defaultRoute: '/home',
// The readOnly property is an array of paths:
readOnly: [
['logged'],
],
routes: [
{
path: '/login',
state: {
logged: false,
view: 'logged',
projectId: null,
},
},
{
path: '/home',
state: {
logged: true,
view: 'home',
projectId: null,
},
},
{
path: '/settings',
state: {
logged: true,
view: 'settings',
projectId: null,
},
},
{
path: '/project/:pid',
defaultRoute: '/dashboard',
state: {
logged: true,
projectId: ':pid',
},
routes: [
{
path: '/settings',
state: {
view: 'project.settings',
},
},
{
path: '/dashboard',
state: {
view: 'project.dashboard',
},
},
],
},
],
});
Once the router is instanciated, it will check the hash to update the state.
Since the router cannot write the logged
flag, it will search for the first route that matches all and only the readOnly
sub-state.
The only route that matches is the /login
route, so it will update the URL and the state in consequence:
console.log(window.location.hash === '#/login');
console.log(tree.get('logged') === false);
console.log(tree.get('view') === 'login');
console.log(tree.get('projectId') === null);
So basically, the readOnly
paths are here to apply some more constraints to the state.
Here is how the updated application behaves on some specific cases:
() => {
tree.set('logged', false);
tree.set('view', 'project.settings');
tree.set('projectId', '123456');
tree.commit();
setTimeout(() => {
console.log(window.location.hash === '#/login');
console.log(tree.get('view') === 'login');
console.log(tree.get('projectId') === null);
}, 0);
}
() => {
tree.set('logged', true);
tree.set('view', 'project.settings');
tree.set('projectId', '123456');
tree.commit();
setTimeout(() => {
console.log(window.location.hash === '#/project/123456/settings');
console.log(tree.get('view') === 'project.settings');
console.log(tree.get('projectId') === 123456);
}, 0);
}
() => {
tree.set('logged', false);
tree.commit();
setTimeout(() => {
window.location.hash = '/project/123456/settings';
setTimeout(() => {
console.log(window.location.hash === '#/login');
console.log(tree.get('view') === 'login');
console.log(tree.get('projectId') === null);
}, 0);
}, 0);
}
() => {
tree.set('logged', true);
tree.commit();
setTimeout(() => {
window.location.hash = '/project/123456/settings';
setTimeout(() => {
console.log(window.location.hash === '#/project/123456/settings');
console.log(tree.get('view') === 'project.settings');
console.log(tree.get('projectId') === 123456);
}, 0);
}, 0);
}
The router has to be instanciated with a Baobab instance and the routes tree definition:
The tree's root must respect the following schema:
{
routes: 'Array<Route>',
defaultRoute: 'string',
readOnly: '?Array<BaobabPath>'
}
Each other route must respect the following schema:
{
path: '?string',
state: 'object',
defaultRoute: '?string',
routes: '?Array<Route>'
}
Also, here are some rules each route must respect:
defaultRoute
value must have a path
value.defaultRoute
value must match a child's route if provided.state
object must have at least one constraint.Finally, two baobab-router instances cannot be bound to the same baobab instance.
It is possible to kill a baobab-router instance, by using its baobabInstance.kill
method. This will remove the state and URL listeners, and unbind the instance from the related baobab tree, to allow an eventual reinstanciation with the same tree.
It is possible to give the router's constructor an object of specific settings as a third argument. Currently, the only recognized key is "solver"
, to give the instance a custom RegExp to solve dynamic values from paths.
For instance, giving { "solver": /\{([^\/\}]*)\}/g }
will find dynamic values shaped as "a/{b}/c"
instead of "a/:b/c"
.
If a child has some state constraints that are overriding its parent's, the router will detect it and anytime the parent will not match the actual state, then the children will have to be checked as well, which will decrease the router's efficiency.
As previously introduced in the advanced use case, it is possible to prevent the router to update some specified paths in the state tree, by using the readOnly
key on the routes tree's root node. This feature has been developed especially thinking about the case presented in the advanced example, and should remain to be used with caution:
readOnly
if used by the routerA route with children can have a defaultRoute
path. If the route matches but none of its child does, then it will fallback on the default path, and recheck everything. If the default route does not match any of the children paths, then an error will be throws at the instanciation.
Also, it is noticeable that a route with a defaultRoute
value will never be the selected node of the tree, while a route with children and no default route can be selected as the end node.
Finally, since routes with a valid defaultRoute
value are never selected as the end node, it is not necessary to specify a path, and they can be used just to group other routes under some common state constraints, for instance.
This project is currently in an experimental state, and feedbacks, advice and pull requests are very welcome. Be sure to check code linting and to add unit tests if relevant and pass them all before submitting your pull request.
# Install the dev environment
git clone git@github.com:jacomyal/baobab-router
cd baobab-router
npm install
# Check the code and run the tests
npm test
FAQs
A router for Baobab
The npm package baobab-router receives a total of 24 weekly downloads. As such, baobab-router popularity was classified as not popular.
We found that baobab-router demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
Research
Security News
Socket researchers uncover the risks of a malicious Python package targeting Discord developers.
Security News
The UK is proposing a bold ban on ransomware payments by public entities to disrupt cybercrime, protect critical services, and lead global cybersecurity efforts.