Persist a page action's state in a chrome extension - javascript

So, I have the following code:
var clicks = 0; // click counter
// Make sure this only runs on facebook
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if (tab.url.indexOf("facebook.com") > -1) {
chrome.pageAction.show(tabId);
}
});
// Called when the user clicks on the page action.
chrome.pageAction.onClicked.addListener(function(tab) {
if (clicks == 0) {
chrome.pageAction.setIcon({path: "dontlike.png", tabId: tab.id}); // Update icon
chrome.pageAction.setTitle({title: "idontlike", tabId: tab.id}); // Update title
chrome.tabs.executeScript({ // Hide like buttons
code: 'var like = document.getElementsByClassName("UFILikeLink"); for (index = 0; index < like.length; ++index) { like[index].style.display="none"; }'
});
}
else {
chrome.pageAction.setIcon({path: "like.png", tabId: tab.id}); // Update icon
chrome.pageAction.setTitle({title: "like", tabId: tab.id}); // Update title
chrome.tabs.executeScript({ // Show like buttons
code: 'var like = document.getElementsByClassName("UFILikeLink"); for (index = 0; index < like.length; ++index) { like[index].style.display=""; }'
});
}
// wrap coutner around
clicks++;
if (clicks > 1)
clicks = 0;
});
for a chrome extension that hides all "Like" buttons on facebook when a pageaction icon is clicked. This works; however, any time a new facebook url is loaded, the state of the extension is lost, e.g. if the button is in dislike mode (hide all likes), if I go to a new page, it is reset to like mode.
I had an idea to persist the state of the extension using the click counter, and to make the code more functional with something like the following
var clicks = 0; // click counter
function like() {
chrome.pageAction.setIcon({path: "like.png", tabId: tab.id}); // Update icon
chrome.pageAction.setTitle({title: "like", tabId: tab.id}); // Update title
chrome.tabs.executeScript({ // Show like buttons
code: 'var like = document.getElementsByClassName("UFILikeLink"); for (index = 0; index < like.length; ++index) { like[index].style.display="none"; }'
});
clicks++;
if (clicks > 1) {
clicks = 0;
}
}
function dislike() {
chrome.pageAction.setIcon({path: "like.png", tabId: tab.id}); // Update icon
chrome.pageAction.setTitle({title: "like", tabId: tab.id}); // Update title
chrome.tabs.executeScript({ // Show like buttons
code: 'var like = document.getElementsByClassName("UFILikeLink"); for (index = 0; index < like.length; ++index) { like[index].style.display=""; }'
});
clicks++;
if (clicks > 1) {
clicks = 0;
}
}
// Make sure this only runs on facebook
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if (tab.url.indexOf("facebook.com") > -1) {
chrome.pageAction.show(tabId);
if (clicks == 0) {
like();
}
else {
dislike();
}
}
});
// Called when the user clicks on the page action.
chrome.pageAction.onClicked.addListener(function(tab) {
if (clicks == 0) {
like();
}
else {
dislike();
}
});
But that code doesn't work at all (when I click on the page action icon, nothing happens and no error messages appear in the chrome console).
I'm new to JS and Chrome Extensions. Is there an easy way to persist the state of my extension, and a better way to execute the script I need to hide all like buttons?
Thank you!

The question of states in chrome extension can have several answers. The choice depend of the situation. Whet I have understand in your case is that you only have tow states, so I will give you some idea.
1. Persistent background script
By default, background script is loaded at chrome startup, so it lives during the whole execution of chrome, until the user explicitly close chrome. In combination with Content Script, you can have a state full system.
So You can use this background script to save a state during the execution and inform listening content scripts of the changes :
background.js
var state = 0;
chrome.pageAction.onClicked.addListener(function(tab) {
if (state = 0) {
state = 1;
state0Actions(); //Do what you want
}
else {
state = 0;
state1Actions(); //Do what you want
}
//Inform content scripts that the state have changed
chrome.tabs.sendMessage(tab.id, {state : state});
});
//At initialisation, Content scipts will request the current state to background script.
chrome.runtime.onMessage(function(message, sender, callback){
if(message.getState) callback({state : state});
});
You can then inject a content script to all facebook pages by adding this to your manifest.json file
"content_scripts" :
[
{
"matches": ["https://www.facebook.com/*","http://www.facebook.com/*"],
"all_frames": true,
"js": ["contentScript.js"]
}
]
It will automatically inject the contentScipt.js script to all page beginning with http(s)://www.facebook.com.
contenScript.js
//the actions to do for each states
function state0Actions()
{
//Do what you want for the state 0
}
function state1Actions()
{
//Do what you want for the state 1
}
//Message will be received at each update of state in the background page
chrome.runtime.onMessage.addListner(function(message, sender, callback))
{
//Check the message is valid
if(message.state == null)
{
console.log("Unreconized message");
return;
}
//Do actions for the right state
//You also can use if statements here... Switch are more used when there is lots of states
switch(message.state) {
case 0 : state0Actions(); break;
case 1 : state1Actions(); break;
}
}
//Request the current state to initialise the script
chrome.runtime.sendMessage({getState: true});
Here, the onMessage handler will be call a first time when he is loaded and then each time the background change the state.
Pay attention that the state will be reset at the chrome startup.
2. Chrome storage
You can use chrome.storage API to manage the state. The main point of this is that the state will be saved and will not be reset at chrome startup.
To do this, you have pretty the same background code :
chrome.pageAction.onClicked.addListener(function(tab) {
chrome.storage.local.get("state", function(result)
{
//First initialisation of the state in the local storage
if(result.state == null)
{
chrome.storage.local.set({state: 0});
state0Actions(); //Do what you want
}
else if (result.state == 0) {
result.state = 1;
state0Actions(); //Do what you want
}
else {
result.state = 0;
state1Actions(); //Do what you want
}
//Save the new state to the storage
chrome.storage.set({state: result.state});
}
});
And the content script will listen changes of the local storage instead of wating update notification from the background page :
//the actions to do for each states
function state0Actions()
{
//Do what you want for the state 0
}
function state1Actions()
{
//Do what you want for the state 1
}
chrome.storage.local.onChanged.addListener(function(changes, areaName)
{
if(areaName != "local" || changes.state == null) return;
switch(changes.state)
{
case 0 : state0Actions(); break;
case 1 : state1Actions(); break;
}
})
chrome.storage.local.get("state", function(result){
if(result.state == null) state0Actions(); //Do what you want if the state is not yet initialised
else if (result.state == 0) state0Actions(); //Do what you want
else if (result.state == 1) state1Actions(); //Do what you want
})
You also can use chrome.storage.sync instead of chrome.storage.local for a shared state with all user's devices.
This are to way to play with state. you have to compare what is the better for your use case. The code I have written is not tested, they are only example to illustrate my explanation.
Don't forget to check Chrome API documentation

When the extension is using a non-persistent background page aka Event page it is unloaded after ~5 seconds of inactivity. And every time it's reloaded the code runs again and all variables are re-initialized, thus losing the previous state.
The solution is to store the state in localStorage which doesn't require any additional permissions in manifest.json:
Initialization:
var clicks = localStorage.clicks || 0; // click counter
Toggling and storing (no need for ++ and if):
var clicks = localStorage.clicks = 1 - clicks;
The value will be stringified and stored as "0" or "1" but for the above arithmetic it's not a problem.

Related

Angular - window - EventListener - refresh main tab when other close

I use Angular 7. I deal with documents When I click on "INDEXER" button, a second tab is opened with content of the first document and a form.
When the form is filled, the second document appears on this second tab etc...
When all the documents are ok, this second tab close and and the first main tab must be refreshed.
Code for second tab :
iterate(index) {
this.loadingAction = false;
if (index < this.workflowList.length) {
this.getDetails(this.workflowList[index]);
} else {
localStorage.setItem('actualise', 'true');
window.close();
}
}
Code for the first tab
ngOnInit() {
const that = this;
// COMMUNICATION INTER ONGLETS
window.addEventListener('storage', (event) => {
console.log(event.key)
// console.log('event storage = LocaleStorage : ' + event.storageArea !== localStorage);
if (event.storageArea !== localStorage) {
return;
}
if (event.key === 'actualise') {
// if (event.storageArea.getItem('actualise') === 'true') {
console.log('appel backend')
this.refresh();
} else {
console.log(localStorage.getItem('actualise'));
}
});
}
Problem is that backend is called multiple times and when there are a lot of documents, it's very long.
I updated code in order the backend is called one time, it works but the screen is not refreshed
Original : Backend called multiple time and screen refresh
Update: backend called one time but screen is not refreshed
How can I solve that ?

localStorage results in function being loaded infinite times

I am filtering what users see based on a selected value. Users can choose between consumer and commercial with the code supposed to be adding "/commercial" to the URL of commercial users and "/consumer" being added to the URL of consumer users.
This is the current code I am using, which results in the below functions being run every time the page is loaded, resulting in an infinite loop.
/* Hides non-commercial products */
function commercial() {
window.location.assign(window.location.origin + "/collections/{{ collection.handle }}/commercial");
localStorage.setItem("saved", "0");
}
/* Hides non-consumer products */
function consumer() {
window.location.assign(window.location.origin + "/collections/{{ collection.handle }}/consumer");
localStorage.setItem("saved", "1");
}
/* Shows all products */
function reset() {
window.location.assign(window.location.origin + "/collections/{{ collection.handle }}");
localStorage.removeItem("saved");
}
UPDATED CODE: LocalStorage results in the above functions being run every time the page is loaded, resulting in an infinite loop. Below is mt LocalStorage get the saved value of the key code:
/* Remember last clicked button and store it in LocalStorage */
window.addEventListener('load', (event) => {
const value = localStorage.getItem("saved");
if (value == "0") {
commercial();
} else if (value == "1") {
consumer()
} else {
reset()
}
});
This is what the code outputs: (DJI is just an example of a collection)
saved == "0" outputs /collections/dji/commercial
saved == "1" outputs /collections/dji/consumer
saved == "2" outputs /collections/dji/
window.location.href holds the complete URL thus it's appending to previous URL, instead use window.location.origin.
window.location.assign(window.location.origin + "/commercial");

how toggle between two functions automatically without button

I want to toggle between interstitial ads and rewarded video ads in my game html use it construct 2 every time loading layout like if first runtime show interstitial and if loading again show rewarded video ads and repeat this every time .
SysActs.prototype.GoToLayout = function(to) {
showInterstitialAd();
showRewardedVideoAd();
if (this.runtime.isloading)
return; // cannot change layout while loading on loader layout
if (this.runtime.changelayout)
return; // already changing to a different layout
;
this.runtime.changelayout = to;
};
my testcode aftert toggle between two functions automatically
SysActs.prototype.GoToLayout = function (to)
{
if($(this).data('toggleAds') == 1) {
toggleAds = 0;
if (this.runtime.isloading || showRewardedVideoAd())
return;
if (this.runtime.changelayout )
return;
;
this.runtime.changelayout = to;
}
else {
toggleAds = 1;
if (this.runtime.isloading || showInterstitialAd() )
return;
if (this.runtime.changelayout )
return;
;
this.runtime.changelayout = to;
showInterstitialAd();
}
$(this).data('toggleAds', toggleAds);
return false;
};
i try this but is not work?
It doesn't work because you're not persisting anything on page reload, so you get the exact same page and same code, so the behaviour is exactly the same. You can't toggle this way. Store a state in the localStorage and read it on page load.
const previousState = localStorage.getItem("state"); // null, "interstitial" or "rewarded"
let currentState;
if(previousState){
currentState = previousState === "interstitial" ? "rewarded" : "interstitial";
} else { // First page load ever
currentState = "rewarded"; // or "interstitial", initialize it like you want
}
localStorage.setItem("previousState", currentState); // It's saved for next reload
// Now do something with currentState

Content script unloads when page is not visited for a long time

I'm working on a browser extension that needs to be constantly running, even after an automatic refresh in the background. The problem is, that the page randomly automatically unloads and the script just shuts off. I need to find a way to keep the content script on at all times. Here's part of the code:
// content.js:
function run(fn) {
if(typeof(Worker) !== "undefined") {
if(typeof(w) == "undefined") {
w = new Worker(URL.createObjectURL(new Blob(['('+fn+')()'])));
}
w.onmessage = function(event) {
if (isNaN(grabbedmin) && ID) {
bump() // Note: the bump function refreshes in the page.
w.terminate();
}
if ($("[href='/server/bump/" + ID + "']").text().includes("Bump")) {
bump()
w.terminate();
}
document.getElementById("bumpcount").innerHTML = "Autobumper Enabled: " + getCurrentTimestamp();
if (numberwow == grabbedmin) {
bump()
w.terminate();
}
};
}
}
The code above basically gets run every minute by this worker:
// content.js:
const worker = run(function() {
var i = 0;
function timedCount() {
i = i + 1;
postMessage(i);
setTimeout(function(){timedCount()},1000);
}
timedCount();
});
Is there a way for background.js to detect that content.js is not running (or that the page is unloaded) when it should be and then reload it?
Note: You can find the script here: https://github.com/Theblockbuster1/disboard-auto-bump
After looking through the docs and looking at examples, I put this together:
chrome.tabs.query({
url: ["*://disboard.org/*dashboard/servers", "*://disboard.org/*dashboard/servers/"] // finds matching tabs
}, function(tabs) {
tabs.forEach(tab => {
chrome.tabs.update(tab.id,{autoDiscardable:false});
});
});
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
// checks if the browser automatically re-enabled autoDiscarding and disables it
if (changeInfo.autoDiscardable == true) chrome.tabs.update(tabId,{autoDiscardable:false});
});

Prestashop set catalog mode ON/OFF if user unlogged/logged

I'm working on a prestashop module to set the catalog mode ON or OFF if user is unlogged or logged.
Works great but got a problem.
I don't want unlogged users see prices at all and allowed to order. But with the solution I found, when first connection (mode catalog OFF) unlogged user load the page, the catalog mod turn ON, but he can see prices (has to reload to hide prices) So, first load set catalog mode ON and second load display real catalog mode.
I found a js script to reload automatically to take effect with the new mode but obviously, loading time of the page is two times longer.
Here is the function :
public function hookHeader()
{
$logged = $this->context->customer->isLogged();
if (!$logged) {
Configuration::updateValue('PS_CATALOG_MODE', true);
} else {
Configuration::updateValue('PS_CATALOG_MODE', false);
}
// reload the page once more
echo '
<script type="text/javascript">
(function() {
if( window.localStorage ) {
if( !localStorage.getItem( "firstLoad" ) ) {
localStorage[ "firstLoad" ] = true;
window.location.reload();
} else {
localStorage.removeItem( "firstLoad" );
}
}
})();
</script>
';
}
Hope somebody could help me with this. Thank you.
Your solution has a problem.
You're updating the value inside the database: if multiple users are browsing the site, the value will be turned on/off/on/off/..., in other words it's "unstable".
The next customer that visits the site will get the current value (can be on and off).
Instead, you should toggle the value only for that customer. I wrote an override for Configuration class, that check if you're trying to get PS_CATALOG_MODE, then check if you'er logged in and returns 0 or 1. Be careful to cache this value using static variables (so you don't have to check multiple times).
But this solution has a flaw too. It checks the key of the request configuration variable everytime.
A better solution would be to change the value of this during the session. Configuration variables are actually held in a PHP array during the session.
You should change it here:
https://github.com/PrestaShop/PrestaShop/blob/1.6.1.x/classes/Configuration.php#L203
possibly by overridding
https://github.com/PrestaShop/PrestaShop/blob/1.6.1.x/classes/Configuration.php#L140
This is what I had in mind by overriding loadConfiguration:
<?php
// placed in /override/classes/Configuration.php
class Configuration extends ConfigurationCore
{
public static function loadConfiguration()
{
parent::loadConfiguration();
// 'global' because I assume you're not runing multishop
self::$_cache[self::$definition['table']][0]['global']['PS_CATALOG_MODE'] = !Context::getContext()->customer->isLogged();
}
}
I wrote this from memeroy so be sure to check the anmes, etc. I assume you're running > PS1.6
Why don't you just use the group settings? Customer group settings allow you to set the "show prices" option to "false" for visitors, and "true" for customers, for example.
The solution we find with gskema is to override the get() function of the Configuration class :
<?php
// placed in /override/classes/Configuration.php
class Configuration extends ConfigurationCore
{
public static function get($key, $id_lang = null, $id_shop_group = null, $id_shop = null)
{
if (defined('_PS_DO_NOT_LOAD_CONFIGURATION_') && _PS_DO_NOT_LOAD_CONFIGURATION_) {
return false;
}
// If conf if not initialized, try manual query
if (!isset(self::$_cache[self::$definition['table']])) {
Configuration::loadConfiguration();
if (!self::$_cache[self::$definition['table']]) {
return Db::getInstance()->getValue('SELECT `value` FROM `'._DB_PREFIX_.bqSQL(self::$definition['table']).'` WHERE `name` = "'.pSQL($key).'"');
}
}
$id_lang = (int)$id_lang;
if ($id_shop === null || !Shop::isFeatureActive()) {
$id_shop = Shop::getContextShopID(true);
}
if ($id_shop_group === null || !Shop::isFeatureActive()) {
$id_shop_group = Shop::getContextShopGroupID(true);
}
if (!isset(self::$_cache[self::$definition['table']][$id_lang])) {
$id_lang = 0;
}
if ($id_shop && Configuration::hasKey($key, $id_lang, null, $id_shop)) {
if($key == 'PS_CATALOG_MODE' && Context::getContext()->controller->controller_type == 'front')
{
return !Context::getContext()->customer->isLogged() || self::$_cache[self::$definition['table']][$id_lang]['shop'][$id_shop][$key];
} else {
return self::$_cache[self::$definition['table']][$id_lang]['shop'][$id_shop][$key];
}
} elseif ($id_shop_group && Configuration::hasKey($key, $id_lang, $id_shop_group)) {
if($key == 'PS_CATALOG_MODE' && Context::getContext()->controller->controller_type == 'front')
{
return !Context::getContext()->customer->isLogged() || self::$_cache[self::$definition['table']][$id_lang]['group'][$id_shop_group][$key];
} else {
return self::$_cache[self::$definition['table']][$id_lang]['group'][$id_shop_group][$key];
}
} elseif (Configuration::hasKey($key, $id_lang)) {
if($key == 'PS_CATALOG_MODE' && Context::getContext()->controller->controller_type == 'front')
{
return !Context::getContext()->customer->isLogged() || self::$_cache[self::$definition['table']][$id_lang]['global'][$key];
} else {
return self::$_cache[self::$definition['table']][$id_lang]['global'][$key];
}
}
return false;
}
}
/!\ still comparing the key value every time someone tries to get a config variable, which may slow down the shop just slightly.
EDIT
Add a condition if Context is front office to fixe back office issue 'Call isLogged on NULL'

Categories