One of the most frequently requested features is local key/value storage. This is a sensitive problem since it's directly related to security. Browsers have sandboxing for many good reasons. This is why I've been delaying making a decision on this until I've come up with a couple of ideas.
Now I have, and wanted to share. Note that none of the ideas involve having a single global storage, but involves having a local storage sandbox per each URL and accessing them from one view to another.
Here are the two:
1. Readonly exports
If you are aware of javascript module system you can think of this as a similar method. We would introduce two new actions $state.export
and $state.import
. Users would need to first visit a URL to "export" all the values it wants to export, and then once that's done, other views can reference the value using $state.import
. If you try to $state.import
before it's been exported, it will not return the value;
I actually took a shot at a quick prototype over at $state branch The code is commented so it should be self-explanatory how it should work.
- Pros: it's pretty simple (conceptually as well as implementation-wise) and intuitive. Easy to implement.
- Cons: the states are "readonly" from other views. You can write to the state of any URL ONLY from itself.
An example code:
Exporting:
{
"type": "$state.export",
"options": {
"username": "Ethan",
"email": "[email protected]"
},
"success": {
"type": "$close"
}
}
Importing
{
"type": "$state.import",
"options": {
"db": "https://www.jasonbase.com/things/3nf.json"
},
"success": {
"type": "$set",
"options": {
"db": "{{$jason.db}}"
}
}
}
It works pretty similarly to how Javascript module system works. you can export and other modules can import to use them, but they are readonly (the similarity to Javascript module system is another reason why this feels simpler)
But like I said, not being able to write has some drawbacks, which is why I have another idea:
2. Read/Write state variables
Here, you not only can read from another URL's state, but also can write to it. Here the API would consist of: $state.set
and $state.get
.
Since being able to write to another view's sandbox is a big deal in terms of security, we also need another security measure. In this case we will also need to explicitly state the public state variables so that only those state variables can be set from outside of the view. This may look something like this:
{
"$jason": {
"head": {
"state": ["username", "email"],
...
}
}
}
Basically we are stating that anyone can set the state variable for this URL but ONLY the "username" and "email" variables.
I've attached below a full example of what it would look like. But before we go on, just a quick explanation on how this works:
- abc.json is the master view which transitions to 123.json
- User can select an option from 123.json
- When the user selects an item, it sets the
abc.json
's selected
state variable via $state.set
, and returns via $back
action.
- When we return to
abc.json
, the $show
event triggers $state.get
and renders the view based on the state we've just set.
abc.json
{
"$jason": {
"head": {
"state": ["selected"],
"actions": {
"$show": {
"type": "$state.get",
"options": {
"db": "https://www.jasonbase.com/things/abc.json"
},
"success": [{
"{{#if ('db' in $jason) && ('selected' in $jason.db)}}": {
"type": "$set",
"options": {
"selected": "{{$jason.db.selected}}"
},
"success": {
"type": "$render"
}
}
}, {
"{{#else}}": {
"type": "$set",
"options": {
"selected": "Nothing selected"
},
"success": {
"type": "$render"
}
}
}]
}
},
"templates": {
"body": {
"sections": [{
"header": {
"type": "label",
"text": "{{$get.selected}}"
},
"items": [{
"type": "label",
"text": "Select Items",
"href": {
"url": "https://www.jasonbase.com/things/123.json"
}
}]
}]
}
}
}
}
}
123.json
{
"$jason": {
"head": {
"data": {
"items": [{
"text": "Item 1"
}, {
"text": "Item 2"
}, {
"text": "Item 3"
}]
},
"templates": {
"body": {
"sections": [{
"items": {
"{{#each items}}": {
"type": "label",
"text": "{{text}}",
"action": {
"type": "$state.set",
"options": {
"selected@https://www.jasonbase.com/things/abc.json": "{{text}}"
},
"success": {
"type": "$back"
}
}
}
}
}]
}
}
}
}
}
- pros: more flexible
- cons:
- more complex, which means there can be some unforeseen edge cases.
- This one's more important: Because we rely on the "state" declaration on the view in order to determine what state variable is read/writeable, every time we access
$state.get
, we need to query the the state's owner URL (one network/file request) to check its $jason.head.state
attribute to make sure the attribute has been declared accessible. (The first solution I suggested above doesn't have this problem since the variable will simply be empty if it hasn't been already exported)
Feedback appreciated
I wanted to share here because I'm still trying to decide on this since people have been requesting this either directly or indirectly (a lot of the problems people talk about on the forum can be made easier by supporting this, for example almost all bugs/issues related to tab bar or $params will become irrelevant when we support this since we can deprecate $params and just use the $state
instead, which is much more robust and flexible).
I would appreciate any kind of feedback on either of the two. Especially if you have a better idea or improvement, or any security holes I missed, please feel free to share.
Thanks!