神刀安全网

Hacking Sunday – JavaScript Metaprogramming – Overwriting LocalStorage

One more Sunday post and another attempt to hide weird looking, overcomplicated code under the “hacking” label. That’s what we use it for right? Maybe not but here it goes!

Intro – Metaprogramming

I am not gonna give a (sucky) definition of what metaprogramming is, instead i’ ll leave some links:

Interesting StackOverFlow thread

Some wikipedia

What is homoiconicity(a good read!)

I got my initial feel on what all these people going on about through ruby with help from these two excellent resources:

Dave Thomas’ Screencast: The Ruby Object Model and Metaprogramming

Metaprogramming Ruby 2

My take on javascript’s metaprogramming abilities

Javascript gives us a lot of freedom to inspect our program at runtime and even modify almost any core aspect of the environment.

In fact these operation can be taken to such an unimaginable level of control that could completely destroy any kind of semantics and standard behavior( check this! ). I really dig that, a lot of discipline is needed of course but i believe understanding these kind of things and more importantly when or not to use them really improves our out of the box thinking and creativity.

So, what’s wrong then?

Using these techniques without bringing total calamity for your program with eval() ( or eval()-like ) operations, dirty checking loops(as you will see below) and more.

ES6 brings new features that improve the situation a lot!

Proxies are on of them and i will some time soon write a part 2 of this post using them.

A great explanation for proxies and metaprogramming in general can be found in chapter 28. Metaprogramming with proxies from the book Exploring ES6 by Dr. Axel Rauschmayer, must read(and free to read online!). Also i have to recommend his previous book Speaking JavaScript as a complete javascript book(ES5).

Overwriting localStorage

Recently, there was a need for our internal testing tool to provide a kind of “session-like” behavior on localStorage values(but keeping them on localStorage, not on session) and without modifications on the core program.So, i thought i could auto-append a specific identifier(session id, timestamp, whatever) in front of the localStorage keys and auto-remove it for retrieval, here is how i did it.

First step: Overwriting setItem() and getItem()

//our identifier to be added in front of the localstorage keys var myId = "myIdentifier__"  //save the original functions var _setItem = Storage.prototype.setItem; var _getItem = Storage.prototype.getItem;  //overwrite their function bodys forcing javascript to create new function copies Storage.prototype.getItem = function(item) {   //add the identifier infront of the key   item = myId + item   //and call the original function   return _getItem.call(this, item) } Storage.prototype.setItem = function(key, val) {   //add the identifier infront of the key   key = myId + key   //and call the original function   _setItem.call(this, key, val)   //return the value to comply with the standard behaviour   return val }

Nothing weird here.

But here is an interesting trivia: Functions in javascript are objects, so saving them to another variable doesn’t make a copy of it but instead just passes the reference.

So you usually have to clone them(for functions var _myFunc = myFunc.bind({}) is a trick i like), but when you modify the function body you are actually forcing javascript to create a new copy of the function and the original remains intact( read more here ), cool stuff!

So now we end up with something like this:

localStorage.setItem("greeting", "hello") // becomes "myIdentifier__greeting": "hello" on our storage //but you can retrieve it normally localStorage.getItem("greeting") // => "hello"

What about the alternative forms: localStorage.myKey= and localStorage.myKey ?

This is where it gets a little messy. There might be better ways but this is what i came up with:

In an interval of 100ms check for new keys, re-save them with a modified key, keep track of the modification, remove the original and create a getter for retrieval.

Here is what it looks like:

//dirty checking loop every 100ms setInterval(function() {   for (var prop in localStorage) {     //if we have a new key or an existing key has an updated value     if(localStorage.hasOwnProperty(prop) && ((prop.indexOf(myId) === -1 && !watchList[prop]) ||       (prop.indexOf(myId) === -1 && watchList[prop] && localStorage[prop] !== watchList[prop]))) {       console.log("Saving " + prop + " = " + localStorage[prop] + " as " + myId + prop + " = " + localStorage[prop]);       //add it to the watchlist       watchList[prop] = localStorage[prop]       //use our modified setter to add it with an identifier on the front       localStorage.setItem(prop, watchList[prop])       //remove the original       localStorage.removeItem(prop)       //define a custom getter that points to our modified getItem       Storage.prototype[prop] = localStorage.getItem(prop)     }   } }, 100)

And here is one more detail, on page reload we need to re-track them and re-define the getters:

//on reload we need to re-track our keys and re-define their getters var watchList = {} for (var prop in localStorage) {   //if it's not tracked   if(localStorage.hasOwnProperty(prop) && prop.indexOf(myId) > -1 && !watchList[prop]) {     //add it to the watchlist with its original key name     watchList[prop.replace(myId, "")] = localStorage[prop]     //define a custom getter that points to our modified getItem     Storage.prototype[prop.replace(myId, "")] = localStorage.getItem(prop.replace(myId, ""))   } }

Here is a complete gist

Conclusion

It works fine for my usecase, not the most elegant piece of code but it fits its purpose(looking forward to rewrite it with proxies!).

Give it a try, mess with localStorage or anything else you thought was impossible!

Have fun!

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Hacking Sunday – JavaScript Metaprogramming – Overwriting LocalStorage

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址