How to check for unique values in chrome.storage array - javascript

I am really struggling with this:
What I am trying to do is create an array that is stored in chrome.sync with the url of unique pages a user visits. Adding the url to the array works fine - what I am struggling with is how to check if that url already exists in the array. This is what I have currently:
function getURL(array) {
chrome.tabs.query({active: true, lastFocusedWindow: true, currentWindow: true}, tabs => {
let tabUrl = tabs[0].url;
addArr(tabUrl, array);
});
}
function addArr(tabUrl, array) {
array.map((x) => {
let urlCheck = x.url;
console.log(urlCheck);
if(urlCheck != tabUrl) {
array.push(tabUrl);
chrome.storage.sync.set({data: array}, function() {
console.log("added")
});
}
})
}
The annoying thing I can't work out is that it works when I remove anything to do with the the map and only keep the:
array.push(tabUrl);
chrome.storage.sync.set({data: array}, function() {
console.log("added")
});
This makes even less sense because when I inspect the array by inspecting the popup and then looking at console, there is no error only an empty data array. The error that it gives me, however, when I inspect the popup (actually right click on extension) is
Error handling response: TypeError: Cannot read properties of
undefined (reading 'url') at
chrome-extension://jkhpfndgmiimdbmjbajgdnmgembbkmgf/getURL.js:31:30
That getURL.js line 31? That is part of the code that gets the active tab. i.e
let tabUrl = tabs[0].url;
which isn't even in the same function.
Any ideas? I am totally stumped.

Use includes method:
function addArr(tabUrl, array) {
if (!array.includes(tabUrl)) {
array.push(tabUrl);
chrome.storage.sync.set({data: array});
}
}
However, chrome.storage.sync has a limit of 8192 bytes for each value which can only hold in the ballpark of 100 URLs, so you'll have to limit the amount of elements in the array e.g. by using array.splice or use several keys in the storage or use chrome.storage.local. There are also other limits in the sync storage like the frequency of write operations, which you can easily exceed if the user does a lot of navigation. An even better solution is to use IndexedDB with each URL as a separate key so you don't have to rewrite the entire array each time.

Related

cy.wrap().its()... doesn't work when the value in .its() contains a period

I am looking to extract a URL parameter from the current URL I'm testing with Cypress. I was able to basically get the answer from this SO post, however, my extracted values are not available to me when I use Cypress's .its() command.
The parameters in the url all have periods in them, and I believe this is the cause for my error.
Here is my custom Cypress Command I'm building:
Cypress.Commands.add('getParmsCommand', function(value) {
cy.url().as('url')
cy.then( () => {
cy.log(this.url)
const kvPairArray = this.url.toString().split('?')[1].toString().split('&')
const paramObj = {}
kvPairArray.forEach(param => {
cy.log(param)
//default 'value' to 0 if it doesn't exist
const [ key, value="0" ] = param.split('=')
paramObj[key] = value
})
//forcefully adding a debug element to the key value store for testing
paramObj['beverage'] = 'soda'
cy.wrap(paramObj)
.its('timeline.ws') //doesn't work
// .its(`${Cypress.$.escapeSelector('timeline.ws')}`) doesn't work
// .its('timeline\.ws') doesn't work
// .its('"timeline.ws"') doesn't work
// .its('beverage') this DOES work!
.then(parmVal => {
cy.log(parmVal)
})
Here is the relevant part of the URL that I'm trying to extract from:
timeline.ws=3600000&timeline.to&timeline.fm&timeline.ar=false
You can see from the error that Cypress is only looking for the id timeline, NOT timeline.ws; it completely ignores everything after the period, and thus, never finds my parameter.
I saw there was a similar error with Cypress's .get() function back in 2018.
I am new to both javascript and Cypress, so I hope it's just a weird easy thing I'm overlooking. Any advice or educated guesses are greatly welcome at this point!
Thank you.
.its() is just a shorthand for property extraction. Since it fails with the period, you could instead use bracket notation in a .then().
cy.wrap(paramObj)
.then(paramObj => paramObj['timeline.ws'])
or just
cy.wrap(paramObj['timeline.ws'])
Playing around with the URL constructor
const urlString = 'http://example.com?timeline.ws=3600000&timeline.to&timeline.fm&timeline.ar=false'
const url = new URL(urlString)
cy.wrap(url.searchParams.get('timeline.ws'))
.should('eq', '3600000')
cy.wrap(url.searchParams.get('timeline.to'))
.should('be.empty')
cy.wrap(url.searchParams.get('timeline.ar'))
.should('eq', 'false')

Reference changed before change of reference

What I am trying to do is to switch out an object's property (a string) with a matching (keyed) object from another object where the values are keyed.
So for example...
const dataRefs = {
'idkey1': { title: "Key1", /*...etc... */ },
'idkey2': { title: "Key2", /*...etc... */ },
'idkey3': { title: "Key3", /*...etc... */ },
// ...etc...
};
const pages = [
{ title: "A Page", data: 'idkey1' },
// ...etc...
];
Using the below code I want to switch out pages[n].data with the matching property in dataRefs. So using a forEach on the pages...
pages.forEach(page => page.data = dataRefs[page.data])
Doing this results in page.data property becoming undefined, even though it should match.
If I try to debug by outputting it to console, I get some unusual effect of seeing the undefined only when the code is added after the output....
// This works and does the match exactly as I want it.
pages.forEach(page => console.log("%s: ", page.data, dataRefs[page.data]));
// Output:
// idkey1: undefined
// This however results in bizzare behaviour and ends up with "undefined".
pages.forEach(page => {
// using console.log to see what's going on...
console.log("%s: ", page.data, dataRefs[page.data]);
page.data = dataRefs[page.data];
});
// Output:
// [Object object]: undefined
// Trying this alternative, just in case how the DOM inspector
// might be using references, but still the same issue...
pages.forEach(page => {
console.log(page.data + ": ", dataRefs[page.data]);
page.data = dataRefs[page.data];
});
// Output:
// [Object object]: undefined
Have checked spelling of variables and gone over and over the code trying so many variants but it seems that no matter what I do, calling page.data = dataRefs[page.data] does not work. Would this be some sort of complex race-condition or have I been watching too much Matrix of late?
This is being called in the Component's render() method.
Using Safari 14.1.2, if that helps.
The issue was related with Next.JS. Best guess is that Next.JS was pre-rendering the data, storing it in some JSON cache file and passing that to the component render function....or something like that.
Using the browser's inspector, a breakpoint at the problematic line page.data = dataRefs[page.data] was only ever triggered once, and showed the data had already been manipulated by the function, before it had been called. Which is simply odd. Removing the line, the breakpoint would trigger and the data not converted.
This leads me to believe it to be some sort of NextJS pre-lifecycle thing, possibly as part of the SSG process.
To resolve the issue and move on, I used a check if (page.data instanceof Object) return; to stop it from running twice, which seemed to do the trick. Not ideal, but without a better reason, this will have to suffice. So the code ultimately went like....
pages.forEach(page => {
// skip the loop if the data has already been converted
// could have also used a string check, but both should work for now.
if (page.data instanceof Object) return;
// now change the data if it's still a string referrence
page.data = dataRefs[page.data]));
});
Again, appologies that I don't have the best answer, but this was the only way to resolve it, and since Javascript does not do this normally (have done this sort of thing so many times without issue), it will have to be chalked up to a NextJS/SSG (or some other processor) issue.
Would love to get any NextJS expert's knowledge on how this could happen.

Can't overwrite object saved with rhaboo in javascript

I'm trying to save an object with rhaboo in javascript. The first time after initialising it is working but when I'm trying to save it again it gives me the
rhaboo.min.js:1 Uncaught TypeError: Cannot read property 'refs' of undefined error. I pinned down the error to the line where I save the keyArray with notes.write('presentationNotes', keyArray);
How I get the error in detail:
I open my webapplication with a clean localStorage (nothing is saved) and rhaboo gets initialised. After that I navigate to a document and open the notes-div with the notes-button. I write something in the notes-area and hit the notes-submit button to save the notes with rhaboo to localStorage. I do the same for a second document. For now everything works. Both notes get saved correctly so that I have an object like this:
keyArray = {activeDoc1: ['note1', 'note2'], activeDoc2: ['note1', 'note2']}
saved in rhaboo in notes.presentationNotes. Then I reload my webapplication and rhaboo is already initialised. I navigate to the documents as before and check if I can load the saved notes. This works as expected but when I try to hit the notes-submit button again it gives me the aforementioned error. What am I doing wrong?
var notes = Rhaboo.persistent('Presentation Notes');
$(document).ready(function(event) {
var keyArray, activeDoc;
if (!notes.initialised) {
notes.write('initialised', true);
notes.write('presentationNotes', {});
console.log('Rhaboo Initialised');
keyArray = {};
} else {
console.log('Rhaboo already initialised');
keyArray = notes.presentationNotes;
console.log('notes.presentationNotes onLoad = ');
console.log(notes.presentationNotes);
}
//Notes open
$(document).on('click', '#notes-button', function() {
$('.notes-div').show();
activeDoc = $('.node.active').attr('id');
if (notes.presentationNotes[activeDoc] != null) {
//Iterate notes
$.each(notes.presentationNotes[activeDoc], function(index, value) {
$('#notes-area').append(value + '\n');
});
}
});
//Notes save
$(document).on('click', '#notes-submit', function() {
$('.notes-div').hide();
var str = $('#notes-area').val();
var array = str.split("\n");
keyArray[activeDoc] = array;
//Save notes
notes.write('presentationNotes', keyArray);
//Clear textarea
$('#notes-area').val('');
});
}
Without the HTML I haven't been able to try this, so I'm just guessing here, but I suspect your problem will go away if you stop using keyArray and activeDoc. The whole point of rhaboo is that it is not a place to store your data. It IS your data.
I see no transient data in your program, i.e., no data which you actively want to delete when the user goes away and comes back. All the data is supposed to be persistent, therefore it should all be under the Rhaboo.persistent.
That's the philosophy, but to be more specific, I think your problem is here:
keyArray[activeDoc] = array;
When I wonder what keyArray is is find:
keyArray = notes.presentationNotes;
so the earlier line actually says:
notes.presentationNotes[activeDoc] = array;
but it says on the tin that that should read:
notes.presentationNotes.write(activeDoc, array);
The upshot is that that the hooks that make rhaboo work have not been inserted into array, as notes.presentationNotes.write would have done.
When you next said:
notes.write('presentationNotes', keyArray);
it meant:
notes.write('presentationNotes', notes.presentationNotes).
which is clearly not what you meant. Rhaboo doesn't suspect that array has no hooks yet because it can see that notes.presentationNotes does have hooks.
I also forget to use write sometimes, and it really bugs me that JS offers no way to hook into the creation of a NEW key within an object X, no matter what you've done to X. Without that limitation, there'd be no need for write and it could be foolproof.

How do I store multiple options for a Chrome extension?

I am working on a Chrome extension that replaces words on websites with different words.
I allow users to input their own words that they would like to be replaced, and I save these like this:
function storeOptions(){
var words = new Object(); // storageArea.set() method takes an object that contains all the items to be stored. This Object contains those.
$("option").each(function(key,value){ // .each(index, element)
words[value.id] = value.text;
chrome.storage.sync.set(words, function(){console.log("Stored: "+value.text);});
});
}
Before this was implemented I was successfully enabling and disabling the extension with a browseraction that used a setting stored in the same storagearea in a similar way:
chrome.storage.sync.set({"status":"enabled"});
The problem I am facing is that after implementing the option to add words, the status either isn't being stored properly or is affected by the options, as when I try to retrieve it it doesn't have the values "enabled" or "disabled" as shown here:
chrome.storage.sync.get("status", function(result) {
console.log("status: "+result["status"]); // status: status
});
I was thinking that perhaps I could store the words to replace as an array called in a way like:
chrome.storage.sync.set({"words" : words});
And I would then be able to differentiate the two by getting "status" or "words", but that did not work.
How can I store status and the words without them interfering with each other?
The only reason for what you describe to happen is if there is an <option> element with id status and value status (in which case it would overwrite the original status).
In any case, it is (as you suggested) a good idea to "encapsulate" all option-related key-value pairs in an object (not necessarily an array) inside storage.
(Finally, there is no need to store the values one-by-one. It would be more efficient to first create the whole object and then store it with a single call to chrome.storage.sync.set().)
function storeOptions() {
const words = {};
$('option').each((idx, element) => words[element.id] = element.text);
chrome.storage.sync.set({words: words});
}
Now your storage.sync will be looking like this:
{
"status": "enabled",
"words": {
"option_id1": "option_text1",
"option_id2": "option_text2",
...
}
}
You can retrieve the values like this:
// Retrieve the extension status:
chrome.storage.sync.get('status', items => {
const status = items.status;
...
});
// Retrieve the words:
chrome.storage.sync.get('words', items => {
const words = items.words;
Object.keys(words).forEach(key => {
console.log(`Replacing '${key}' with '${words[key]}'...`);
...
});
});

Fields with known non-empty values of JSON object return empty string

I have an object containing share counts for various social metrics. The object looks like this:
Object{
delicious: 0,
facebook: {
comments: 0,
likes: 0,
shares: 0,
total: 0,
},
linkedIn: 1,
pinterest: 0,
twitter: 9
}
Here is some testing code I am using to try to access my object:
console.log(bg.results);
console.log(bg.results["facebook"]);
console.log(bg.results.facebook);
Object.keys(bg.results).forEach(function(key){
console.log(bg.results);
console.log(key + "->" + bg.results[key]);
});
The object above is what I am seeing in the console, so I know that the fields in bg.results contain data. The problem is, when I try to access any of these fields using either dot syntax or by using object["key"] I get an empty string as the result. I searched and could not find anyone else experiencing the same problem. Does anyone have any ideas why this may be happening?
Some additional info:
My code is being run within the context of a chrome extension. I'm accessing this object from a popup page, and the object's data is being supplied from my background page.
Thank you for your assistance.
UPDATE
Something funny is going on with how Chrome is handling my code. The data in bg.results is supplied by a function on my background page called update. In my program there are two ways that update is called depending on the user's settings. When update() is called from within the context of my background page, everything works fine and behaves as expected. When update() is called from my popup page, I get empty strings when I try to access any fields.
This seems like a bug very particular to how chrome extensions are handled, so any input from experts in this sort of thing would be awesome. Thanks for the help everyone!
1.) your code should work. facebook is an object itself so you need to think about that, and maybe use a recursive function!
2.) why dont you just loop over your objects properties with the for-in-loop?
for(key in bg.results) {
console.log(key);
}
3.) example recursive function
function recursiveExample(obj) {
var current;
for(key in obj) {
current = obj[key];
if(typeof current === 'object' && !Array.isArray(current)) {
recursiveExample(current);
}
console.log("key: " + key + " - value" + current);
}
}
I've concluded that my values were not completely loaded into the results object when I was trying to access them. I can only speculate as to why I could see the values when I printed the results object to the console (and could not access them at that point) but was not able to access them until later. Below is the solution I used for this problem.
function checkLoaded(results){
var isLoaded = true;
$.each(results, function(key, value){
if(value === "" || value === undefined){
isLoaded = false;
}
});
setTimeout(function(){
if(!isLoaded){
checkLoaded(results);
}
else{
displayHandler(results);
}
}, 500);
}
If anyone knows why I could see the values in the console but could not access them, I'd appreciate an explanation. But otherwise, if anyone else encounters a problem like this, try setting up a delay to make sure that your values are completely loaded into your collection.
This is just a guess (since you haven't posted any of the relevant data-loading code), but I suspect you may be doing something like this:
chrome.extension.sendMessage({myAction: "update"}, function(response) {
bg.results = response;
});
// we try to use bg.results, but the sendMessage callback won't be run until later on
Object.keys(bg.results).forEach(function(key){
console.log(bg.results);
console.log(key + "->" + bg.results[key]);
});
The ansychronous callback passed to sendMessage runs after the current function stack has cleared (i.e., once the JavaScript thread has run out of other things to do), so bg.results = response won't happen until after you try to use it. Instead, use the results inside the callback:
chrome.extension.sendMessage({myAction: "update"}, function(response) {
bg.results = response;
// now it works, because this code is inside the callback
Object.keys(bg.results).forEach(function(key){
console.log(bg.results);
console.log(key + "->" + bg.results[key]);
});
});
Maybe you don't actually use chrome.extension.sendMessage, but you must be using some asynchronous operation (maybe Ajax, maybe another Chrome API call). I don't know what that method is for certain, since you didn't post any of your data-loading code.

Categories