Migrating from localStorage to chrome.storage - google-chrome-extension

I'm in the process of migrating my Chrome extension's persistency repository, from localStorage to chrome.storage. An important difference between them is that chrome.storage is asynchronous, therefore a callback function needs to be passed.
How would you modify a loop that writes to localStorage, synchronously, to the async chrome.storage?
for (var i = 0; i < obj.length; i++) {
localStorage.setItem(obj[i].key, obj[i].val);
}
doThisWhenAllElementsAreSaved();
Thanks.

For this example, I'll use chrome.storage.local, but you can replace it with chrome.storage.sync if you prefer.
The goal is to use chrome.storage.local.set. The first step is to convert your obj into an object that contains the list of pair of key / value:
var keyValue = {};
for (var i = 0; i < obj.length; i++)
{
keyValue[obj[i].key] = obj[i].val;
}
Then we call the actual method:
chrome.storage.local.set(keyValue, function()
{
// Notify that we saved.
doThisWhenAllElementsAreSaved();
});
or, simply:
chrome.storage.local(keyValue, doThisWhenAllElementsAreSaved);
Note that the callback will be called on success, as well as on failure. If the storage failed, then chrome.runtime.lastError will be set.

You can save multiple items at once with the chrome.storage API, so I would use the following approach:
Before, using localStorage.setItem
for (var i = 0; i < obj.length; i++) {
localStorage.setItem(obj[i].key, obj[i].val);
}
doThisWhenAllElementsAreSaved();
After, using chrome.storage.local.set
var items = {};
for (var i = 0; i < obj.length; i++) {
items[obj[i].key] = obj[i].val;
}
chrome.storage.local.set(items, function() {
doThisWhenAllElementsAreSaved();
});
If you need to know whether the save operation succeeded, check the value of chrome.runtime.lastError in the callback.

The best practice would be just to change the code to use chrome.storage.local like others said.
But sometimes, it gets really messy and you may want some code to remain untouched.
I tried to use a library that highly incorporates window.localStorage in a packaged app environment(the library was angular-cached-resource) and didn't want to change the original source code. So I managed to make the following shim: https://github.com/stewartpark/chrome-localStorage-shim
Hope it helps! Happy hacking.

Related

protractor FOR loop using expectations

I made loop and now I want to use protractor expectation for every i.
Loop works ok, but expectations doesn't. If count is 4, there should be 4 expectations. If I run a test, I get pass, without any expectations(which should be false).
I found articles about that but I couldn't make it happen. I tried with Push, but there is only empty value.
Thanks for help.
myelement.count().then(function(count){
console.log("whatever", count);
for (var i=0; i<count; i++){
var o = location.get(i);
expect(o.getText()).toEqual("something");
};
});
Be aware. Almost all commands are promises. If you don't handle them correct it will never work. You will first need to resolve them before you can go to the next one.
Something like this can do the trick
let promise;
const promises = [];
myelement.count()
.then(function(count) {
console.log("whatever", count);
for (var i = 0; i < count; i++) {
var o = location.get(i);
promises.push(expect(o.getText()).toEqual("something"));
};
return Promise.all(promises);
});
Full test-case code would help, but I assume that you've forgot to either return a promise from 'it' or define and call 'done' callback, since what you're doing - is an async operation.
Your problem descrption is not clear. I'm assuming that location is list of elements. So i'm applying each() on set elements called 'location' . It will work perfectly.
//replace location with myelement if it the correct one
location.each(function(ele,index){
expect(ele.getText()).toEqual("something");
});

nodejs do something after multiple functions complete

I'm having a problem that I can't seem to get around.
Imaging the following
if (users) {
for (var i = 0; i < users.length; i++) {
apiData.users = getUserDetails(users[i]);
}
}
and
if(profiles) {
for (var i = 0; i < profiles.length; i++) {
apiData.profiles = getProfileDetails(users[i]);
}
}
now, only once both of those blocks of code have been completed (i.e. the API calls have returned the results that are required), would I like to do something like this
for (var i = 0; i < users.length; i++) {
saveUserProfile(apiData)
}
Normally I handle a situation like this by wrapping the first block of code in a function that returns a callback and then running the third function afterwards, but how can I do this when there are effectively 2 separate operations happening?
To be clear, I would like to avoid saving users and profiles separately, there are various reasons for this.
I would like to know if there is any standard way of handling a situation like this as there may or may not be users or profiles, so using callbacks seems like it will not work?
You can use async module to achieve this.
Use async.parallel() function since your first and second snippets do not depend on each other, and can run asynchronously, but you wish to make third snippet run when first and second are done executing.
Assuming users, profiles, and apiData are already declared, your code would look like
function func1(callback){
if (users) {
for (var i = 0; i < users.length; i++) {
apiData.users = getUserDetails(users[i]);
}
}
//invoke callback
}
function func2(callback){
if(profiles) {
for (var i = 0; i < profiles.length; i++) {
apiData.profiles = getProfileDetails(users[i]);
}
}
//invoke callback
}
var asyncfunc = [];
asyncfunc.push(func1);
asyncfunc.push(func2);
async.parallel(asyncfunc,function(err,result){
//result is an array which receives the second argument of each function's callback.
for (var i = 0; i < users.length; i++) {
saveUserProfile(apiData)
}
})
EDIT
PS: you can also use async.series() here. Both are equivalent in this case, since the functions handled by async.parallel() here do not use timers neither perform I/O tasks.
The callback in each function inside the array takes two arguments. First argument represents error, which is null if there are no errors, second represents the result of the function, which is passed to the second argument of the async.parallel() function's callback.

Is there a simple way to do an async forEach over an array without a library in node?

I don't want to have to npm anything. I'm just trying to create a script to process a few files but as I have to use streams to process the files I need to use some form of async forEach.
The issue is I just want a simple .js file I can run, I don't want to have to npm install a bunch of stuff.
If you want, for example, the forEach functionality of async (https://github.com/caolan/async), you can always look into its' source and reimplement it yourself.
A simplistic implementation could look like this:
function forEach(arr, fun, callback) {
var toDo = arr.length;
var doneCallback = function() {
--toDo;
if(toDo === 0) {
callback();
}
}
for(var i = 0, len = arr.length; i < len; ++i) {
fun(arr[i], doneCallback);
}
}
It assumes the function you want to run takes an array element and a callback. You could modify it to collect results, handle errors, etc. I encourage you to look into async's source.

I'm having issues passing an array from my node.js app to my javascript client

I have live chat on my website and right now it's just polling. I want to get with the times and replace this with a node.js version. I've been making good progress but am now stuck on something that appears to just be a syntax issue.
So what I'm doing is when the user first comes to my site, I want to show them the most recent chat that's in the mysql database. So it will be rows of
time user their message
I'm sending this data on user connect from my app.js file with
io.sockets.on('connection', function (socket) {
//Make connection to mysql
connection.query('select values from my_table limit 5', function(err, rows, fields) {
user_info = new Array();
for (i = 0; i < rows.length; i++) {
user_info[i] = new Array();
user_info[i]['time'] = rows[i].time;
user_info[i]['user'] = rows[i].user;
user_info[i]['their_message'] = rows[i].their_message;
}
io.sockets.emit('some_event_tag', user_info);
});
So this works, except that when I try and access this data under the function associated with "some_event_tag", it appears my syntax is off because I'm not getting the information. So on the client, I'm trying to access the data with
some_event_tag: function(data) {
var msg = '';
for (i = 0; i < data.length; i++) {
msg = $('<div class="msg"></div>')
.append('<span class="name">' + data[i]['user'] + '</span>: ')
.append('<span class="text">' + data[i]['their_message'] + '</span>');
$('#messages')
.append(msg);
msg = '';
}
},
but for whatever reason I'm getting "undefined". On the server side, if I change
io.sockets.emit('some_event_tag', user_info);
to something like
io.sockets.emit('some_event_tag', user_info[0]['user']);
I can access this value on the client (by just saying "data"). Of course, in this case, I'm only passing one value. Not what I want. On the client side, I can also correctly see that five array elements are being passed. In other words, data.length is correctly set to 5. However, I can't actually figure out the syntax to access the data on the client side. Isn't this just typical javascript arrays? I'm a little stumped at this point and google didn't help me this time. Any help you guys can give would be greatly appreciated.
JavaScript has a rather strong assumption that Arrays use numeric keys between 0 and length-1. While they can still have other keys, most functions for handling Arrays will ignore them -- including JSON.stringify() that Socket.IO uses.
So, if you need to set other keys and don't want them skipped, you'll want to use a plain Object instead:
var user_info = new Array();
for (var i = 0; i < rows.length; i++) {
user_info[i] = new Object();
// ...
}
I suspect producing an SSCCE would highlight your issue, among others:
// The time is the user and the user is the time? ;)
user_info[i]['time'] = rows[i].user;
user_info[i]['user'] = rows[i].time;
If rows[i].time is something date-like, then user_info[i]['user'] is likely to be something date-like. A question arises as to whether or not io.sockets.emit can emit date-like things:
io.sockets.emit('some_event_tag', user_info[0]['user']);
There appears to be no documentation for io.sockets.emit. That's not entirely helpful, is it? Ohh well. I'm confident. Keep us updated, right?

Using userscript GM_functions inside the page context

I want to "re-link" everything in a specific page through a XMLHTTPRequest to a local network domain. That would lead me to GM_xmlhttpRequest in GreaseMonkey/NinjaKit except that I want to run it when the link is clicked, not when the userscript actually runs...
So I have something like:
links = document.getElementsByTagName('a');
for (i = 0; i < links.length; i++) {
oldhref = links[i].getAttribute('href');
links[i].setAttribute('href', 'javascript:loadLink(' + oldhref + ')');
}
I understand I can either use unsafeWindow or add a script element to document to inject loadLink function.
But how can I use GM_xmlhttpRequest in loadLink?
I've looked at 0.7.20080121.0 Compatibility page but I'm not sure if that is for what I need...
I've also considered adding an iframe to the page and the modified links would load inside the iframe (triggering the userscript again), but I'd prefer a cleaner solution...
You almost never need to use GM functions inside the page context, and from the code posted so far, you don't need unsafeWindow in this case either.
Also, it is not necessary to rewrite the href for what is posted so far.
Something like this will accomplish what you want:
var links = document.getElementsByTagName ('a');
for (var J = 0, len = links.length; J < len; ++J) {
links[J].addEventListener ("click", myLoadLink, false);
}
function myLoadLink (zEvent) {
zEvent.preventDefault();
zEvent.stopPropagation();
var targetHref = zEvent.currentTarget.getAttribute ('href');
GM_xmlhttpRequest ( {
//wtv
} );
return false;
}
Or with jQuery:
$("a").click (myLoadLink);
function myLoadLink () {
var targetHref = $(this).attr ('href');
GM_xmlhttpRequest ( {
//wtv
} );
return false;
}
Ok, so I managed to get that GreaseMonkey official workaround working (dunno what I did wrong the first time) with:
unsafeWindow.loadLink = function(href) {
setTimeout(function(){
GM_xmlhttpRequest({
//wtv
});
},0);
}
But I'd still prefer a solution without using unsafeWindow if there is one... (especially since this one feels so wrong...)

Resources